Single-commit clean baseline after security scrub of niche-tells, project codenames, internal jargon, and contributor-email leaks. Contents: - 100 Rust crates (_primitives/_rust/) - 37 agent manifests (_manifests/) + generated specs (_generated/) - 67 user-invocable skills (skills/) - 33 hooks (hooks/) - Composition blocks (_blocks/) - Documentation (docs/, README.md) - TS adapter packages (_ts_packages/) - Assembler (_assembler/) - Roles (_roles/) - Templates (_templates/) - Forgejo CI (.forgejo/) Author: Denis Parfionovich <info@greendragon.info> License: see LICENSE.
88 lines
3.1 KiB
Rust
88 lines
3.1 KiB
Rust
//! Anthropic provider streaming wire-shape test.
|
|
//!
|
|
//! Stands up a wiremock fake of /v1/messages, replies with a hand-crafted SSE
|
|
//! body, asserts the StreamEvent sequence the parser produces.
|
|
|
|
use futures::StreamExt;
|
|
use kei_router::{AnthropicProvider, Message, Provider, StreamEvent};
|
|
use wiremock::matchers::{header, method, path};
|
|
use wiremock::{Mock, MockServer, ResponseTemplate};
|
|
|
|
fn fake_sse_body() -> &'static str {
|
|
"event: message_start\n\
|
|
data: {\"type\":\"message_start\"}\n\n\
|
|
event: content_block_delta\n\
|
|
data: {\"type\":\"content_block_delta\",\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello\"}}\n\n\
|
|
event: content_block_delta\n\
|
|
data: {\"type\":\"content_block_delta\",\"delta\":{\"type\":\"text_delta\",\"text\":\", world\"}}\n\n\
|
|
event: message_stop\n\
|
|
data: {\"type\":\"message_stop\"}\n\n"
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn streams_token_then_done() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("POST"))
|
|
.and(path("/v1/messages"))
|
|
.and(header("x-api-key", "test-key"))
|
|
.and(header("anthropic-version", "2023-06-01"))
|
|
.respond_with(
|
|
ResponseTemplate::new(200)
|
|
.insert_header("content-type", "text/event-stream")
|
|
.set_body_string(fake_sse_body()),
|
|
)
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let endpoint = format!("{}/v1/messages", server.uri());
|
|
let p = AnthropicProvider::with_endpoint(
|
|
"test-key".into(),
|
|
"claude-haiku-4-5".into(),
|
|
endpoint,
|
|
);
|
|
let messages = vec![Message { role: "user".into(), content: "hi".into() }];
|
|
let mut stream = p.stream_message("be brief", &messages, None).await.expect("open ok");
|
|
|
|
let mut tokens = Vec::new();
|
|
let mut got_done = false;
|
|
while let Some(ev) = stream.next().await {
|
|
match ev.expect("event ok") {
|
|
StreamEvent::Token(t) => tokens.push(t),
|
|
StreamEvent::Done => { got_done = true; break; }
|
|
_ => {}
|
|
}
|
|
}
|
|
assert_eq!(tokens, vec!["Hello".to_string(), ", world".to_string()]);
|
|
assert!(got_done, "expected message_stop -> Done");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn surfaces_429_as_rate_limit() {
|
|
let server = MockServer::start().await;
|
|
Mock::given(method("POST"))
|
|
.and(path("/v1/messages"))
|
|
.respond_with(ResponseTemplate::new(429).set_body_string("rate limited"))
|
|
.mount(&server)
|
|
.await;
|
|
|
|
let endpoint = format!("{}/v1/messages", server.uri());
|
|
let p = AnthropicProvider::with_endpoint("k".into(), "claude-haiku-4-5".into(), endpoint);
|
|
let err = p
|
|
.stream_message("", &[Message { role: "user".into(), content: "x".into() }], None)
|
|
.await
|
|
.err()
|
|
.expect("expected error");
|
|
assert!(matches!(err, kei_router::LlmError::RateLimit(_)), "got {err:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn cost_constants_are_pinned() {
|
|
let p = AnthropicProvider::with_endpoint(
|
|
"k".into(),
|
|
"claude-haiku-4-5".into(),
|
|
"http://stub".into(),
|
|
);
|
|
assert_eq!(p.name(), "anthropic");
|
|
assert_eq!(p.cost_per_m_tok_input_cents(), 100);
|
|
assert_eq!(p.cost_per_m_tok_output_cents(), 500);
|
|
}
|