mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 19:59:50 +08:00
refactor(workspace): enforce layered crate boundaries
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub use self::state::KiroToClaudeCliStreamState;
|
||||
|
||||
mod state;
|
||||
|
||||
pub const KIRO_CONTEXT_WINDOW_TOKENS: f64 = 200_000.0;
|
||||
pub const KIRO_MAX_THINKING_BUFFER: usize = 1024 * 1024;
|
||||
|
||||
const KIRO_QUOTE_CHARS: &str = "`\"'\\#!@$%^&*()-_=+[]{};:<>,.?/";
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct KiroStreamCacheUsage {
|
||||
pub cache_creation_input_tokens: usize,
|
||||
pub cache_read_input_tokens: usize,
|
||||
}
|
||||
|
||||
impl KiroStreamCacheUsage {
|
||||
fn has_cache_tokens(self) -> bool {
|
||||
self.cache_creation_input_tokens > 0 || self.cache_read_input_tokens > 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encode_kiro_sse_events(events: Vec<Value>) -> Result<Vec<u8>, serde_json::Error> {
|
||||
let mut output = Vec::new();
|
||||
for event in events {
|
||||
output.extend(encode_kiro_sse_event(&event)?);
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub fn encode_kiro_sse_event(event: &Value) -> Result<Vec<u8>, serde_json::Error> {
|
||||
let encoded = serde_json::to_string(event)?;
|
||||
if let Some(event_type) = event.get("type").and_then(Value::as_str) {
|
||||
Ok(format!("event: {event_type}\ndata: {encoded}\n\n").into_bytes())
|
||||
} else {
|
||||
Ok(format!("data: {encoded}\n\n").into_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_kiro_initial_sse_events(
|
||||
message_id: &str,
|
||||
model: &str,
|
||||
estimated_input_tokens: usize,
|
||||
cache_usage: Option<KiroStreamCacheUsage>,
|
||||
) -> Vec<Value> {
|
||||
let usage = build_kiro_usage_payload(estimated_input_tokens, 1, cache_usage);
|
||||
vec![json!({
|
||||
"type": "message_start",
|
||||
"message": {
|
||||
"id": message_id,
|
||||
"type": "message",
|
||||
"role": "assistant",
|
||||
"content": [],
|
||||
"model": model,
|
||||
"stop_reason": Value::Null,
|
||||
"stop_sequence": Value::Null,
|
||||
"usage": usage,
|
||||
}
|
||||
})]
|
||||
}
|
||||
|
||||
pub fn build_kiro_stream_error_sse_events(error_type: &str, message: &str) -> Vec<Value> {
|
||||
vec![json!({
|
||||
"type": "error",
|
||||
"error": {
|
||||
"type": error_type,
|
||||
"message": message,
|
||||
}
|
||||
})]
|
||||
}
|
||||
|
||||
pub fn build_kiro_final_message_sse_events(
|
||||
stop_reason: &str,
|
||||
input_tokens: usize,
|
||||
output_tokens: usize,
|
||||
cache_usage: Option<KiroStreamCacheUsage>,
|
||||
) -> Vec<Value> {
|
||||
let usage = build_kiro_usage_payload(input_tokens, output_tokens, cache_usage);
|
||||
vec![
|
||||
json!({
|
||||
"type": "message_delta",
|
||||
"delta": {
|
||||
"stop_reason": stop_reason,
|
||||
"stop_sequence": Value::Null,
|
||||
},
|
||||
"usage": usage
|
||||
}),
|
||||
json!({"type": "message_stop"}),
|
||||
]
|
||||
}
|
||||
|
||||
fn build_kiro_usage_payload(
|
||||
input_tokens: usize,
|
||||
output_tokens: usize,
|
||||
cache_usage: Option<KiroStreamCacheUsage>,
|
||||
) -> Value {
|
||||
let billed_input_tokens = cache_usage
|
||||
.filter(|usage| usage.has_cache_tokens())
|
||||
.map(|usage| {
|
||||
input_tokens
|
||||
.saturating_sub(usage.cache_creation_input_tokens)
|
||||
.saturating_sub(usage.cache_read_input_tokens)
|
||||
})
|
||||
.unwrap_or(input_tokens);
|
||||
let mut usage = json!({
|
||||
"input_tokens": billed_input_tokens as u64,
|
||||
"output_tokens": output_tokens as u64,
|
||||
});
|
||||
if let Some(cache_usage) = cache_usage.filter(|usage| usage.has_cache_tokens()) {
|
||||
usage["cache_creation_input_tokens"] =
|
||||
json!(cache_usage.cache_creation_input_tokens as u64);
|
||||
usage["cache_read_input_tokens"] = json!(cache_usage.cache_read_input_tokens as u64);
|
||||
}
|
||||
usage
|
||||
}
|
||||
|
||||
pub fn calculate_kiro_context_input_tokens(percentage: f64) -> usize {
|
||||
((percentage * KIRO_CONTEXT_WINDOW_TOKENS) / 100.0) as usize
|
||||
}
|
||||
|
||||
pub fn estimate_kiro_tokens(text: &str) -> usize {
|
||||
if text.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let mut chinese = 0usize;
|
||||
let mut other = 0usize;
|
||||
for ch in text.chars() {
|
||||
if ('\u{4e00}'..='\u{9fff}').contains(&ch) {
|
||||
chinese += 1;
|
||||
} else {
|
||||
other += 1;
|
||||
}
|
||||
}
|
||||
let chinese_tokens = (chinese * 2).div_ceil(3);
|
||||
let other_tokens = other.div_ceil(4);
|
||||
(chinese_tokens + other_tokens).max(1)
|
||||
}
|
||||
|
||||
pub fn find_kiro_real_thinking_start_tag(buffer: &str) -> Option<usize> {
|
||||
let tag = "<thinking>";
|
||||
let mut search = 0usize;
|
||||
loop {
|
||||
let pos = buffer[search..].find(tag).map(|value| value + search)?;
|
||||
let has_before = pos > 0 && is_kiro_quote_char(buffer, pos - 1);
|
||||
let after_pos = pos + tag.len();
|
||||
let has_after = is_kiro_quote_char(buffer, after_pos);
|
||||
if !has_before && !has_after {
|
||||
return Some(pos);
|
||||
}
|
||||
search = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_kiro_real_thinking_end_tag(buffer: &str) -> Option<usize> {
|
||||
let tag = "</thinking>";
|
||||
let mut search = 0usize;
|
||||
loop {
|
||||
let pos = buffer[search..].find(tag).map(|value| value + search)?;
|
||||
let has_before = pos > 0 && is_kiro_quote_char(buffer, pos - 1);
|
||||
let after_pos = pos + tag.len();
|
||||
let has_after = is_kiro_quote_char(buffer, after_pos);
|
||||
if has_before || has_after {
|
||||
search = pos + 1;
|
||||
continue;
|
||||
}
|
||||
let after = &buffer[after_pos..];
|
||||
if after.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
if after.starts_with("\n\n") {
|
||||
return Some(pos);
|
||||
}
|
||||
search = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_kiro_real_thinking_end_tag_at_buffer_end(buffer: &str) -> Option<usize> {
|
||||
let tag = "</thinking>";
|
||||
let mut search = 0usize;
|
||||
loop {
|
||||
let pos = buffer[search..].find(tag).map(|value| value + search)?;
|
||||
let has_before = pos > 0 && is_kiro_quote_char(buffer, pos - 1);
|
||||
let after_pos = pos + tag.len();
|
||||
let has_after = is_kiro_quote_char(buffer, after_pos);
|
||||
if has_before || has_after {
|
||||
search = pos + 1;
|
||||
continue;
|
||||
}
|
||||
if buffer[after_pos..].trim().is_empty() {
|
||||
return Some(pos);
|
||||
}
|
||||
search = pos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kiro_crc32(data: &[u8]) -> u32 {
|
||||
let mut crc = 0xffff_ffffu32;
|
||||
for &byte in data {
|
||||
crc ^= byte as u32;
|
||||
for _ in 0..8 {
|
||||
let mask = if crc & 1 == 1 { 0xedb8_8320 } else { 0 };
|
||||
crc = (crc >> 1) ^ mask;
|
||||
}
|
||||
}
|
||||
!crc
|
||||
}
|
||||
|
||||
fn is_kiro_quote_char(buffer: &str, pos: usize) -> bool {
|
||||
buffer
|
||||
.as_bytes()
|
||||
.get(pos)
|
||||
.map(|byte| KIRO_QUOTE_CHARS.as_bytes().contains(byte))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
Reference in New Issue
Block a user