///|
fn canonicalize_json_value(value : Json) -> Json {
match value {
Array(values) =>
Json::array(values.map(fn(value) { canonicalize_json_value(value) }))
Object(obj) => {
let keys = obj.keys().collect()
keys.sort_by(ascii_lex_compare)
let sorted : Map[String, Json] = {}
for key in keys {
match obj.get(key) {
Some(value) => sorted[key] = canonicalize_json_value(value)
None => ()
}
}
Json::object(sorted)
}
_ => value
}
}
///|
fn canonical_json_string(value : Json) -> String {
canonicalize_json_value(value).stringify()
}
///|
fn canonicalize_json_string_if_parseable(value : String) -> String {
let trimmed = value.trim()
if trimmed.is_empty() {
return value
}
let parsed = @json.parse(trimmed) catch { _ => return value }
canonical_json_string(parsed)
}
///|
fn canonicalize_tool_arguments_string(value : String) -> String {
if value.trim().is_empty() {
return "{}"
}
canonicalize_json_string_if_parseable(value)
}
///|
fn canonicalize_tool_arguments(value : Json?) -> String {
match value {
Some(String(value)) => canonicalize_tool_arguments_string(value)
Some(value) => canonical_json_string(value)
None => "{}"
}
}
///|
fn short_value_hash(value : Json?) -> String {
match value {
None => "absent"
Some(value) =>
short_sha256_hex(@encoding/utf8.encode(canonical_json_string(value)))
}
}
///|
fn short_sha256_hex(bytes : Bytes) -> String {
let digest = @crypto.sha256(bytes)
let builder = StringBuilder::new()
for i in 0..<8 {
let value = digest[i].to_int()
builder.write_char(moon_json_hex_digit(value / 16))
builder.write_char(moon_json_hex_digit(value % 16))
}
builder.to_string()
}
///|
fn moon_json_hex_digit(value : Int) -> Char {
if value < 10 {
('0'.to_int() + value).unsafe_to_char()
} else {
('a'.to_int() + value - 10).unsafe_to_char()
}
}
///|
fn prompt_cache_trace_message(
app_type : String,
provider_id : String,
endpoint : String,
api_format : String?,
body : Json,
session_client_provided : Bool,
) -> String {
let prompt_cache_key = match body {
Object(obj) =>
match obj.get("prompt_cache_key") {
Some(String(key)) => "present(len=\{key.length()})"
_ => "absent"
}
_ => "absent"
}
let store = json_field_for_cache_trace(body, "store")
let stream = json_field_for_cache_trace(body, "stream")
let format = api_format.unwrap_or("native")
"[CacheTrace] app=\{app_type}, provider=\{provider_id}, endpoint=\{endpoint}, api_format=\{format}, session_client_provided=\{session_client_provided}, prompt_cache_key=\{prompt_cache_key}, store=\{store}, stream=\{stream}, instructions_hash=\{short_value_hash(json_field(body, "instructions"))}, tools_hash=\{short_value_hash(json_field(body, "tools"))}, input_hash=\{short_value_hash(json_field(body, "input"))}, include_hash=\{short_value_hash(json_field(body, "include"))}, body_hash=\{short_value_hash(Some(body))}"
}
///|
fn json_field(body : Json, key : String) -> Json? {
match body {
Object(obj) => obj.get(key)
_ => None
}
}
///|
fn json_field_for_cache_trace(body : Json, key : String) -> String {
match json_field(body, key) {
Some(value) => json_value_for_cache_trace(value)
None => "absent"
}
}
///|
fn json_value_for_cache_trace(value : Json) -> String {
match value {
String(value) => value
Array(values) => "array(len=\{values.length()})"
Object(values) => "object(len=\{values.keys().count()})"
_ => value.stringify()
}
}