1pub mod actions;
5pub mod wire;
6
7use crate::llm::action_helper::call_llm;
8use crate::llm::actions::protocol_trait::ActionResult;
9use crate::logging::emit::Log;
10use crate::protocol::{Event, SpawnContext};
11use crate::server::{
12 accept_bounded::{accept_bounded, ConnectionLimiter, DEFAULT_MAX_CONNECTIONS},
13 connection::ConnectionId,
14};
15use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
16use anyhow::{ensure, Result};
17use bytes::Bytes;
18use http_body_util::{BodyExt, Full, Limited};
19use hyper::{
20 body::Incoming,
21 header::{HeaderValue, CONTENT_TYPE},
22 server::conn::http1,
23 service::service_fn,
24 Method, Request, Response, StatusCode,
25};
26use hyper_util::rt::{TokioIo, TokioTimer};
27use serde_json::{json, Value};
28use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
29
30pub const HEADER_TIMEOUT: Duration = Duration::from_secs(30);
31pub const BODY_TIMEOUT: Duration = Duration::from_secs(60);
32pub const MAX_HEADER_BYTES: usize = 64 * 1024;
33pub const MAX_HEADERS: usize = 100;
34pub const MAX_REQUESTS_PER_CONNECTION: usize = 1000;
36const CAP_REFUSAL: &[u8] = b"HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\nConnection: close\r\nRetry-After: 5\r\n\r\n";
37
38type Reply = Response<Full<Bytes>>;
39
40struct Config {
41 api_key: Option<String>,
42 models: Vec<String>,
43}
44
45pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
46 let params = ctx.startup_params.as_ref();
47 let api_key = params
48 .map(|p| p.get_optional_string("api_key"))
49 .transpose()?
50 .flatten()
51 .filter(|k| !k.is_empty());
52 let models: Vec<String> = match params
53 .map(|p| p.get_optional_array("models"))
54 .transpose()?
55 .flatten()
56 {
57 Some(list) => list
58 .iter()
59 .map(|m| m.as_str().map(str::to_string))
60 .collect::<Option<_>>()
61 .ok_or_else(|| anyhow::anyhow!("models must be an array of strings"))?,
62 None => vec![wire::DEFAULT_MODEL.to_string()],
63 };
64 ensure!(
65 !models.is_empty()
66 && models.len() <= 100
67 && models.iter().all(|m| !m.is_empty() && m.len() <= 256),
68 "models must list 1..=100 non-empty ids"
69 );
70 let config = Arc::new(Config { api_key, models });
71 let listener = tokio::net::TcpListener::bind(ctx.legacy_listen_addr()).await?;
72 let local = listener.local_addr()?;
73 Log::new(Some(&ctx.status_tx)).info(format!("Anthropic Messages API listening on {local}"));
74 let server_id = ctx.server_id;
75 let state = ctx.state.clone();
76 let accept = tokio::spawn(async move {
77 let limiter = ConnectionLimiter::new(DEFAULT_MAX_CONNECTIONS);
78 loop {
79 let (stream, peer, permit) = match accept_bounded(
80 &listener,
81 &limiter,
82 CAP_REFUSAL,
83 "Anthropic",
84 Some(&ctx.status_tx),
85 )
86 .await
87 {
88 Ok(v) => v,
89 Err(_) => break,
90 };
91 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
92 let now = crate::utils::clock::Instant::now();
93 ctx.state
94 .add_connection_to_server(
95 server_id,
96 ConnectionState {
97 id,
98 remote_addr: peer,
99 local_addr: local,
100 bytes_sent: 0,
101 bytes_received: 0,
102 packets_sent: 0,
103 packets_received: 0,
104 last_activity: now,
105 status: ConnectionStatus::Active,
106 status_changed_at: now,
107 protocol_info: ProtocolConnectionInfo::empty(),
108 },
109 )
110 .await;
111 let child = ctx.clone();
112 let config = config.clone();
113 ctx.state
114 .spawn_server_task(server_id, async move {
115 let _permit = permit;
116 let request_ctx = child.clone();
117 let served = Arc::new(std::sync::atomic::AtomicUsize::new(0));
118 let service = service_fn(move |request| {
119 let ctx = request_ctx.clone();
120 let config = config.clone();
121 let served = served.clone();
122 async move {
123 let n = served.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
124 let mut r = handle(request, id, &ctx, &config).await;
125 if n >= MAX_REQUESTS_PER_CONNECTION {
126 r.headers_mut().insert(
127 hyper::header::CONNECTION,
128 HeaderValue::from_static("close"),
129 );
130 }
131 Ok::<_, Infallible>(r)
132 }
133 });
134 let mut builder = http1::Builder::new();
135 builder
136 .keep_alive(true)
137 .timer(TokioTimer::new())
138 .header_read_timeout(HEADER_TIMEOUT)
139 .max_headers(MAX_HEADERS)
140 .max_buf_size(MAX_HEADER_BYTES);
141 if let Err(e) = builder
142 .serve_connection(TokioIo::new(stream), service)
143 .await
144 {
145 Log::new(Some(&child.status_tx))
146 .warn(format!("Anthropic connection {id} HTTP error: {e}"));
147 }
148 child
149 .state
150 .update_connection_status(server_id, id, ConnectionStatus::Closed)
151 .await;
152 let _ = child.status_tx.send("__UPDATE_UI__".into());
153 })
154 .await;
155 }
156 });
157 state.register_server_task(server_id, accept).await;
158 Ok(local)
159}
160
161fn reply(status: u16, content_type: &'static str, body: Vec<u8>, request_id: &str) -> Reply {
162 let mut r = Response::new(Full::new(Bytes::from(body)));
163 *r.status_mut() = StatusCode::from_u16(status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
164 let h = r.headers_mut();
165 h.insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
166 if let Ok(v) = HeaderValue::from_str(request_id) {
167 h.insert("request-id", v);
168 }
169 if content_type == "text/event-stream" {
170 h.insert(
171 hyper::header::CACHE_CONTROL,
172 HeaderValue::from_static("no-cache"),
173 );
174 }
175 r
176}
177
178fn json_reply(v: &Value, request_id: &str) -> Reply {
179 reply(
180 200,
181 "application/json",
182 serde_json::to_vec(v).unwrap_or_default(),
183 request_id,
184 )
185}
186
187fn error(error_type: &str, message: &str, request_id: &str) -> Reply {
188 let status = wire::status_of(error_type).unwrap_or(500);
189 let body = wire::error_body(error_type, message, request_id);
190 reply(
191 status,
192 "application/json",
193 serde_json::to_vec(&body).unwrap_or_default(),
194 request_id,
195 )
196}
197
198fn outcome(ctx: &SpawnContext, id: ConnectionId, decision: &str) {
199 let line = format!("Anthropic connection {id} operation=messages decision={decision}");
200 let log = Log::new(Some(&ctx.status_tx));
201 if decision.starts_with("fail_closed") || decision == "model_silent" {
202 log.error(line)
203 } else {
204 log.info(line)
205 }
206}
207
208fn failure(e: Option<&anyhow::Error>, request_id: &str) -> Reply {
211 let category = e
212 .map(crate::utils::WireFailure::classify)
213 .unwrap_or(crate::utils::WireFailure::Unavailable);
214 let kind = if category.is_overloaded() {
215 "overloaded_error"
216 } else {
217 "api_error"
218 };
219 error(kind, category.prefixed_text(), request_id)
220}
221
222async fn ask(
224 ctx: &SpawnContext,
225 id: ConnectionId,
226 event: Event,
227 request_id: &str,
228) -> std::result::Result<Value, Reply> {
229 let result = match call_llm(
230 &ctx.llm_client,
231 &ctx.state,
232 ctx.server_id,
233 Some(id),
234 &event,
235 &actions::AnthropicProtocol,
236 )
237 .await
238 {
239 Ok(r) => r,
240 Err(e) => {
241 outcome(ctx, id, "fail_closed_llm_error");
242 return Err(failure(Some(&e), request_id));
243 }
244 };
245 if !result.failures.is_empty() {
246 outcome(ctx, id, "fail_closed_invalid_reply");
247 return Err(failure(None, request_id));
248 }
249 let mut answers = Vec::new();
250 let mut stack = result.protocol_results;
251 while let Some(r) = stack.pop() {
252 match r {
253 ActionResult::Custom { name, data } if name.starts_with("anthropic_") => {
254 answers.push(data)
255 }
256 ActionResult::Multiple(items) => stack.extend(items),
257 _ => {}
258 }
259 }
260 match answers.len() {
261 1 => {
262 let a = answers.pop().unwrap_or_default();
263 if a["type"] == "anthropic_error" {
264 outcome(ctx, id, "model_reject");
265 return Err(error(
266 a["error_type"].as_str().unwrap_or("api_error"),
267 a["message"].as_str().unwrap_or_default(),
268 request_id,
269 ));
270 }
271 outcome(ctx, id, "model_answer");
272 Ok(a)
273 }
274 0 => {
275 outcome(ctx, id, "model_silent");
276 Err(failure(None, request_id))
277 }
278 _ => {
279 outcome(ctx, id, "fail_closed_invalid_reply");
280 Err(failure(None, request_id))
281 }
282 }
283}
284
285fn key_of(parts: &hyper::http::request::Parts) -> Option<String> {
286 if let Some(k) = parts.headers.get("x-api-key").and_then(|v| v.to_str().ok()) {
287 return Some(k.to_string());
288 }
289 parts
290 .headers
291 .get(hyper::header::AUTHORIZATION)
292 .and_then(|v| v.to_str().ok())
293 .and_then(|v| v.strip_prefix("Bearer "))
294 .map(str::to_string)
295}
296
297fn same(a: &str, b: &str) -> bool {
299 a.len() == b.len()
300 && a.bytes()
301 .zip(b.bytes())
302 .fold(0u8, |acc, (x, y)| acc | (x ^ y))
303 == 0
304}
305
306async fn handle(
307 request: Request<Incoming>,
308 id: ConnectionId,
309 ctx: &SpawnContext,
310 config: &Config,
311) -> Reply {
312 let request_id = wire::random_id("req_");
313 let (parts, body) = request.into_parts();
314 let bytes = match tokio::time::timeout(
315 BODY_TIMEOUT,
316 Limited::new(body, wire::MAX_BODY).collect(),
317 )
318 .await
319 {
320 Err(_) => {
321 return error(
322 "invalid_request_error",
323 "request body deadline exceeded",
324 &request_id,
325 )
326 }
327 Ok(Err(e)) => {
328 return if e
329 .downcast_ref::<http_body_util::LengthLimitError>()
330 .is_some()
331 {
332 error(
333 "request_too_large",
334 "Request exceeds the maximum allowed number of bytes.",
335 &request_id,
336 )
337 } else {
338 error("invalid_request_error", "incomplete HTTP body", &request_id)
339 }
340 }
341 Ok(Ok(b)) => b.to_bytes(),
342 };
343 ctx.state
344 .update_connection_stats(
345 ctx.server_id,
346 id,
347 Some(bytes.len() as u64),
348 None,
349 Some(1),
350 None,
351 )
352 .await;
353 let key = key_of(&parts);
354 if let Some(expected) = &config.api_key {
355 if !key.as_deref().is_some_and(|k| same(k, expected)) {
356 outcome(ctx, id, "auth_reject");
357 return error("authentication_error", "invalid x-api-key", &request_id);
358 }
359 }
360 let path = parts.uri.path();
361 let parsed = || serde_json::from_slice::<Value>(&bytes);
362 let unparsable = |e: serde_json::Error| {
363 error(
364 "invalid_request_error",
365 &format!("There was an issue with your request body: {e}"),
366 &request_id,
367 )
368 };
369 let r = match (&parts.method, path) {
370 (&Method::POST, "/v1/messages") => {
371 let body = match parsed() {
372 Ok(b) => b,
373 Err(e) => return unparsable(e),
374 };
375 messages(ctx, id, &body, key.is_some(), &request_id).await
376 }
377 (&Method::POST, "/v1/messages/count_tokens") => {
378 let body = match parsed() {
379 Ok(b) => b,
380 Err(e) => return unparsable(e),
381 };
382 match wire::request_summary(&body, true) {
383 Ok(_) => json_reply(
384 &json!({"input_tokens": wire::estimate_tokens(&wire::request_text(&body))}),
385 &request_id,
386 ),
387 Err(e) => error("invalid_request_error", &e.to_string(), &request_id),
388 }
389 }
390 (&Method::GET, "/v1/models") => {
391 let data: Vec<Value> = config
392 .models
393 .iter()
394 .map(|m| wire::model_object(m))
395 .collect();
396 json_reply(
397 &json!({"data": data, "has_more": false, "first_id": config.models.first(), "last_id": config.models.last()}),
398 &request_id,
399 )
400 }
401 (&Method::GET, p) if p.starts_with("/v1/models/") => {
402 let wanted = &p["/v1/models/".len()..];
403 match config.models.iter().find(|m| *m == wanted) {
404 Some(m) => json_reply(&wire::model_object(m), &request_id),
405 None => error("not_found_error", &format!("model: {wanted}"), &request_id),
406 }
407 }
408 _ => error("not_found_error", "Not found", &request_id),
409 };
410 let sent = hyper::body::Body::size_hint(r.body()).exact().unwrap_or(0);
411 ctx.state
412 .update_connection_stats(ctx.server_id, id, None, Some(sent), None, Some(1))
413 .await;
414 r
415}
416
417async fn messages(
418 ctx: &SpawnContext,
419 id: ConnectionId,
420 body: &Value,
421 has_key: bool,
422 request_id: &str,
423) -> Reply {
424 let mut summary = match wire::request_summary(body, false) {
425 Ok(s) => s,
426 Err(e) => return error("invalid_request_error", &e.to_string(), request_id),
427 };
428 summary["api_key_present"] = json!(has_key);
429 let stream = summary["stream"].as_bool().unwrap_or(false);
430 let model = summary["model"].as_str().unwrap_or_default().to_string();
431 let answer = match ask(
432 ctx,
433 id,
434 Event::new(&actions::MESSAGE_EVENT, summary),
435 request_id,
436 )
437 .await
438 {
439 Ok(a) => a,
440 Err(r) => return r,
441 };
442 let content = match wire::reply_content(&answer) {
444 Ok(c) => c,
445 Err(_) => return failure(None, request_id),
446 };
447 let stop = match wire::stop_reason(&answer, &content) {
448 Ok(s) => s,
449 Err(_) => return failure(None, request_id),
450 };
451 let output_text: String = content
452 .iter()
453 .map(|b| match b["type"].as_str() {
454 Some("tool_use") => b["input"].to_string(),
455 _ => b["text"].as_str().unwrap_or_default().to_string(),
456 })
457 .collect();
458 let input_tokens = answer["input_tokens"]
459 .as_u64()
460 .unwrap_or_else(|| wire::estimate_tokens(&wire::request_text(body)));
461 let output_tokens = answer["output_tokens"]
462 .as_u64()
463 .unwrap_or_else(|| wire::estimate_tokens(&output_text).max(1));
464 let message = wire::message(
465 &wire::random_id("msg_"),
466 &model,
467 content,
468 &stop,
469 answer["stop_sequence"].as_str(),
470 input_tokens,
471 output_tokens,
472 );
473 if stream {
474 reply(
475 200,
476 "text/event-stream",
477 wire::event_stream(&message).into_bytes(),
478 request_id,
479 )
480 } else {
481 json_reply(&message, request_id)
482 }
483}