aphrodite 1.4.2

aphrodite: Chat Completions proxy with CCR, tool relay, and programmatic CCR for Hermes agent integration.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
//! CCR marker generation - 1:1 port of plugins/aphrodite/_marker/marker.py
//!
//! Generates <<<CCR:hash|type|size>>> markers with TOML-driven templates.

use std::collections::HashMap;

/// Normalize a hash argument handed to a retrieve-style entry point.
///
/// LLMs sometimes echo back a whole marker body (`hash|type|size`) instead of
/// the bare hash, or wrap it in incidental whitespace (report 05 F3). Strip
/// everything from the first `|` onward and trim surrounding whitespace so
/// every retrieval site tolerates the same inputs `resolve_one` already does.
/// Idempotent: normalizing an already-bare hash is a no-op.
pub fn normalize_hash(raw: &str) -> &str {
	raw.split('|').next().unwrap_or(raw).trim()
}

/// Check if a string is a valid CCR hash (>=24 hex chars, or `i:` prefix with
/// >=6 hex chars).
pub fn is_valid_ccr_hash(h: &str) -> bool {
	if h.len() < 8 {
		return false;
	}
	let h = h.to_lowercase();
	if let Some(stripped) = h.strip_prefix("i:") {
		stripped.len() >= 6 && stripped.chars().all(|c| c.is_ascii_hexdigit())
	} else {
		h.len() >= 24 && h.chars().all(|c| c.is_ascii_hexdigit())
	}
}

/// Build a CCR output block.
///
/// - `hash_val`: the content hash
/// - `ccr_type`: content type string (e.g. "code_rust", "build")
/// - `size`: original content size in bytes
/// - `preview`: the formatted preview string
/// - `headroom_budget`: optional token budget for truncation
/// - `meta`: optional metadata key-value pairs
/// - `center`: optional center annotation
pub fn ccr_marker(
	hash_val: &str,
	ccr_type: &str,
	size: usize,
	preview: &str,
	headroom_budget: Option<u32>,
	meta: Option<&HashMap<String, String>>,
	center: Option<&str>,
) -> String {
	// Sanitize preview: newlines → spaces (| is safe — the marker is on its own line).
	let mut safe = preview.replace(['\n', '\r'], " ").trim().to_string();
	// Strip control chars
	safe = safe.chars().filter(|c| *c >= ' ').collect();

	// Headroom budget truncation
	if let Some(budget) = headroom_budget {
		safe = if budget < 25 {
			safe.chars().take(30).collect()
		} else if budget < 50 {
			safe.chars().take(60).collect()
		} else if budget < 75 {
			safe.chars().take(100).collect()
		} else {
			safe
		};
	}

	// Metadata string
	let meta_str = if let Some(m) = meta {
		let parts: Vec<String> = m
			.iter()
			.filter_map(|(k, v)| {
				let sv = v.replace('|', "/").replace('\n', " ").trim().to_string();
				if sv.is_empty() { None } else { Some(format!("{}={}", k, sv)) }
			})
			.collect();
		let mut s = parts.join(";");
		if s.len() > 300 {
			s = format!("{}...", crate::struct_extract::floor_boundary(&s, 297));
		}
		s
	} else {
		String::new()
	};

	// Build marker using the standard template
	render_marker(&safe, ccr_type, &meta_str, center, hash_val, size)
}

/// Render the marker using the canonical three-line format.
///
/// Doubling-bug fix (report 09 §5): `build_preview` already returns a
/// self-describing, fully bracketed preview like `[text:53L 1913B]` or
/// `[git:5M 2A | src/x.rs]`. Re-wrapping that in `[{center_str}:{preview}]`
/// produced the visible `[text:[text:53L 1913B]]` doubling. When the preview
/// is already a `[label:...]`-shaped string, emit it verbatim on the preview
/// line; only wrap bare previews (e.g. cache-mode raw excerpts) in the
/// `[{center_str}:...]` frame. A preview is thus produced exactly once.
fn render_marker(preview: &str, ccr_type: &str, meta: &str, center: Option<&str>, hash: &str, size: usize) -> String {
	let center_str = center.unwrap_or(ccr_type);
	let meta_part = if meta.is_empty() { String::new() } else { format!("\n[meta:{}]", meta) };

	let preview_line = if is_self_bracketed_preview(preview) {
		preview.to_string()
	} else {
		format!("[{}:{}]", center_str, preview)
	};

	format!("<<<CCR:{}|{}|{}>>>\n{}{}", hash, ccr_type, size, preview_line, meta_part)
}

/// True when `preview` is already a self-describing `[label:...]` preview (as
/// produced by `build_preview`), so `render_marker` must not wrap it again.
/// Requires a leading `[`, a matching trailing `]`, and a `[word:` label head
/// (`\[\w+:`) so a bare excerpt that merely happens to start with `[` isn't
/// mistaken for a preview.
fn is_self_bracketed_preview(preview: &str) -> bool {
	let p = preview.trim();
	if !p.starts_with('[') || !p.ends_with(']') {
		return false;
	}
	let inner = &p[1..];
	match inner.find(':') {
		Some(colon) => {
			let label = &inner[..colon];
			!label.is_empty() && label.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
		},
		None => false,
	}
}

/// Parse the preview field from a marker line.
pub fn parse_preview(marker_line: &str) -> Option<String> {
	let start = marker_line.find('[')?;
	let colon = marker_line[start..].find(':')?;
	let end = marker_line.rfind(']')?;
	if end > start + colon {
		Some(marker_line[start + colon + 1..end].to_string())
	} else {
		None
	}
}

/// Matches all four marker delimiter families this codebase (and the Python
/// plugin / docs) uses to wrap a CCR reference:
/// `<<<CCR:hash|type|size>>>`, `[CCR:hash|type]`, and the Unicode-glyph forms
/// opened by `⫷` (U+2AF7) or closed by `⫸` (U+2AF8). Compiled once (report 05
/// F7: the previous per-call `Regex::new(...).unwrap()` both recompiled the
/// pattern on every call and could panic on a bad literal - a `LazyLock`
/// makes the "never fails" invariant of a hardcoded pattern checked exactly
/// once, at first use, instead of on every call).
///
/// The hash class is anchored to `[0-9a-fA-F:i]{6,64}` - hex digits, or the
/// `i:` inline-hash prefix followed by hex - rather than "anything that
/// isn't a delimiter", which previously let the capture cross a newline
/// (`<<<CCR:` on one line, `>>>` several lines later, and everything
/// between - including other markers - matched as one "hash"). `\n` is also
/// excluded from the trailing metadata segment for the same reason. Missing
/// the `⫷` opener (previously accepted `⫸` as a closer but never `⫷` as an
/// opener) meant the Unicode-glyph marker style was silently never
/// extracted at all.
static HASH_RE: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
	regex::Regex::new(r"(?:<<<|\[|\u{2af7})CCR:([0-9a-fA-F:i]{6,64})(?:\|[^\]>\n]*?)?(?:\]|>>>|\u{2af8})").unwrap()
});

/// Extract all CCR hashes from text.
pub fn extract_hashes(text: &str) -> Vec<String> {
	HASH_RE
		.captures_iter(text)
		.filter_map(|cap| cap.get(1))
		.map(|m| m.as_str().to_string())
		.collect()
}

#[cfg(test)]
mod tests {
	use super::*;

	// ── T5 (F3): normalize_hash ────────────────────────────────────
	#[test]
	fn test_normalize_hash_bare_is_unchanged() {
		assert_eq!(normalize_hash("abc123"), "abc123");
	}

	#[test]
	fn test_normalize_hash_strips_pipe_suffix() {
		assert_eq!(normalize_hash("abc123|tool|1024"), "abc123");
	}

	#[test]
	fn test_normalize_hash_trims_whitespace() {
		assert_eq!(normalize_hash("  abc123  "), "abc123");
	}

	#[test]
	fn test_normalize_hash_is_idempotent() {
		let once = normalize_hash("  abc123|tool|1024  ");
		assert_eq!(normalize_hash(once), once);
	}

	#[test]
	fn test_valid_hash() {
		assert!(is_valid_ccr_hash("abc123def456abc123def456abc123def456"));
		assert!(is_valid_ccr_hash("i:abc123def456"));
		assert!(!is_valid_ccr_hash("short"));
		assert!(!is_valid_ccr_hash(""));
	}

	#[test]
	fn test_marker_format() {
		// Component assertions rather than a full-string snapshot, so this
		// test states the actual contract (a well-formed CCR marker line
		// followed by exactly one preview line). `build_preview` returns a
		// self-describing `[ct:...]` preview; `render_marker` must emit it
		// verbatim (report 09 §5 doubling-bug fix) rather than re-wrapping it
		// into `[ct:[ct:...]]`.
		let m = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"code_rust",
			1234,
			"[code_rust:3fns 42L]",
			None,
			None,
			None,
		);
		assert!(m.contains("<<<CCR:abc123def456abc123def456abc123def456|code_rust|1234>>>"));
		// The preview text must appear in the output exactly once.
		assert_eq!(m.matches("3fns 42L").count(), 1);
		// The marker line and the preview line are on separate lines.
		let mut lines = m.lines();
		assert!(lines.next().unwrap().starts_with("<<<CCR:"));
		let preview_line = lines.next().unwrap();
		assert!(preview_line.starts_with('['));
		// Doubling-bug fix: the preview line is the self-describing preview
		// verbatim, NOT re-wrapped into `[code_rust:[code_rust:...]]`.
		assert_eq!(preview_line, "[code_rust:3fns 42L]");
	}

	// ── Doubling-bug regression (report 09 §5): a self-describing preview from
	// `build_preview` (e.g. `[text:53L 1913B]`) must NEVER be re-wrapped into
	// `[type:[type:...]]`. The emitted marker's preview line must not match the
	// `\[\w+:\[` doubling signature for ANY content type. ──
	#[test]
	fn test_marker_preview_never_doubles_bracket_prefix() {
		let re = regex::Regex::new(r"\[\w+:\[").unwrap();
		for ty in [
			"text",
			"git",
			"ls",
			"test",
			"grep",
			"gitlog",
			"build",
			"diff",
			"code_rust",
			"json_array",
		] {
			let preview = crate::build_preview(ty, "M crates/aphrodite/src/preview.rs\nA src/new.rs\n?? tmp\n");
			let m = ccr_marker("abc123def456abc123def456abc123def456", ty, 42, &preview, None, None, None);
			assert!(
				!re.is_match(&m),
				"marker preview must not double the bracket prefix for type {ty:?}: {m:?}"
			);
		}
	}

	#[test]
	fn test_render_marker_wraps_bare_preview_but_not_bracketed() {
		// A bare (cache-mode) excerpt is wrapped once with the center label...
		let bare = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"text",
			5,
			"hello world",
			None,
			None,
			None,
		);
		assert!(bare.contains("\n[text:hello world]"));
		// ...but an already-bracketed preview is emitted verbatim.
		let bracketed = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"git",
			5,
			"[git:2M | a.rs]",
			None,
			None,
			None,
		);
		assert!(bracketed.contains("\n[git:2M | a.rs]"));
		assert!(!bracketed.contains("[git:[git:"));
	}

	#[test]
	fn test_marker_with_budget() {
		let preview = "a very long preview string that should be truncated under tight budget constraints";
		let m = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"text",
			100,
			preview,
			Some(20),
			None,
			None,
		);
		// Budget < 25 → truncate to 30 chars
		let preview_line = m.lines().nth(1).unwrap();
		let inner = preview_line.split(':').nth(1).unwrap().trim_end_matches(']');
		assert!(inner.len() <= 32); // ~30 + bracket
	}

	#[test]
	fn test_extract_hashes() {
		let text = "<<<CCR:aaa111|code|100>>>\nsome text\n<<<CCR:bbb222|diff|200>>>";
		let hashes = extract_hashes(text);
		assert_eq!(hashes, vec!["aaa111", "bbb222"]);
	}

	// ── T7: is_valid_ccr_hash boundary band ──────────────────────
	#[test]
	fn test_is_valid_ccr_hash_boundary_band() {
		// 8..23 hex chars: below the 24-char full-hash floor -> false.
		assert!(!is_valid_ccr_hash("abcdef12")); // 8 hex chars
		assert!(!is_valid_ccr_hash("abcdef0123456789abcdef")); // 22 hex chars
		assert!(!is_valid_ccr_hash("abcdef0123456789abcdeff")); // 23 hex chars
		// 24 hex chars: at the floor -> true.
		assert!(is_valid_ccr_hash("abcdef0123456789abcdef01")); // 24 hex chars
	}

	#[test]
	fn test_is_valid_ccr_hash_i_prefix_variants() {
		assert!(!is_valid_ccr_hash("i:xyz")); // not hex, too short
		assert!(is_valid_ccr_hash("i:abc123")); // 6 hex chars after i:
		assert!(!is_valid_ccr_hash("i:abc1")); // only 4 hex chars after i:
	}

	#[test]
	fn test_is_valid_ccr_hash_uppercase() {
		assert!(is_valid_ccr_hash("ABCDEF0123456789ABCDEF01"));
	}

	// ── T7: extract_hashes delimiter families ────────────────────
	#[test]
	fn test_extract_hashes_bracket_form() {
		let hashes = extract_hashes("[CCR:aaa111|code]");
		assert_eq!(hashes, vec!["aaa111"]);
	}

	#[test]
	fn test_extract_hashes_glyph_terminated_form() {
		// extract_hashes's regex accepts the glyph terminator \u{2af8} as an
		// alternative to `]`/`>>>`; the hash capture stops at the first `|`,
		// same as the `<<<CCR:...>>>` and `[CCR:...]` forms.
		let text = "<<<CCR:ccc333|text\u{2af8}";
		let hashes = extract_hashes(text);
		assert_eq!(hashes, vec!["ccc333"]);
	}

	#[test]
	fn test_extract_hashes_unterminated_no_match() {
		assert!(extract_hashes("<<<CCR:no_terminator_here").is_empty());
	}

	// ── T7 (F7): the `⫷` (U+2AF7) opening glyph was previously never
	// recognized - only its `⫸` (U+2AF8) closing counterpart was - so the
	// Unicode-glyph marker style was silently never extracted at all.
	#[test]
	fn test_extract_hashes_full_glyph_delimited_form() {
		let hashes = extract_hashes("\u{2af7}CCR:abc123\u{2af8}");
		assert_eq!(hashes, vec!["abc123"]);
	}

	// ── T7 (F7): the old hash class `[^|>\]\u{2af8}]+` matched across
	// newlines, so an unclosed `<<<CCR:` on one line and a `>>>` several
	// lines later (possibly past other, unrelated markers) would be
	// captured as one garbage "hash". The hash class is now anchored to
	// hex/`i:` characters only, which can never include a newline.
	#[test]
	fn test_extract_hashes_does_not_cross_newlines() {
		let text = "<<<CCR:foo\nbar>>>";
		assert!(extract_hashes(text).is_empty(), "must not capture a multi-line garbage hash");
	}

	#[test]
	fn test_parse_preview_on_garbage() {
		assert_eq!(parse_preview("no brackets here"), None);
		assert_eq!(parse_preview("[nocolon]"), None);
		assert_eq!(parse_preview("[code_rust:hello]"), Some("hello".to_string()));
	}

	// ── 04-T9: pathological-input coverage (UTF-8 boundary, literal
	// markers, interior NUL) for the marker module. ──

	#[test]
	fn test_ccr_marker_strips_interior_nul_bytes() {
		// The control-char filter (`*c >= ' '`) excludes NUL (0x00) along with
		// every other C0 control code - a literal embedded NUL in tool output
		// must not survive into the rendered marker.
		let preview = "before\0after";
		let m = ccr_marker("abc123def456abc123def456abc123def456", "text", 12, preview, None, None, None);
		assert!(!m.contains('\0'), "NUL byte must be stripped from the preview: {m:?}");
		assert!(m.contains("beforeafter"));
	}

	#[test]
	fn test_ccr_marker_truncates_multibyte_preview_on_char_boundary() {
		// budget < 25 truncates to 30 *chars* via `.chars().take(n)`, not a
		// byte slice - a preview packed with multi-byte UTF-8 (each 'é' is 2
		// bytes) must not panic or produce a str that isn't valid UTF-8 when
		// the char boundary and a naive byte boundary would disagree.
		let preview = "é".repeat(50);
		let m = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"text",
			100,
			&preview,
			Some(10), // budget < 25 -> take(30) chars
			None,
			None,
		);
		let preview_line = m.lines().nth(1).unwrap();
		let inner = parse_preview(preview_line).unwrap();
		assert_eq!(inner.chars().count(), 30, "must truncate to exactly 30 chars, not 30 bytes");
	}

	#[test]
	fn test_ccr_marker_preview_containing_literal_marker_syntax_does_not_confuse_extraction() {
		// Literal marker-shaped text in the preview: `extract_hashes` scans the
		// FULL output (including the preview line below the marker). Without `|`
		// sanitization, a literal `<<<CCR:hex|type|size>>>` in content will
		// survive intact, so `extract_hashes` would also match the literal token
		// alongside the real marker hash. This is acceptable: the real hash is
		// always first (line 1), and `|`-mangling harmed every enriched preview
		// (ls, git, test, grep — all use `|` as a visual separator in their
		// format) to defend against a vanishingly rare edge case (tool output
		// literally containing `<<<CCR:`-shaped text).
		let literal_marker_text = "example: <<<CCR:fake000|text|1>>>";
		let m = ccr_marker(
			"abc123def456abc123def456abc123def456",
			"text",
			999,
			literal_marker_text,
			None,
			None,
			None,
		);
		assert!(m.starts_with("<<<CCR:abc123def456abc123def456abc123def456|text|999>>>"));
		// Without | → `-` sanitization, the literal `<<<CCR:fake000|text|1>>>`
		// in the preview survives intact — but `fake000` contains `k` which is
		// outside [a-f], so `extract_hashes`'s hex-hash gate `[0-9a-fA-F]`
		// already rejects it. Only the real hash (pure hex) is extracted.
		assert!(
			m.contains("fake000|text|1"),
			"embedded literal text survives verbatim (pipes intact): {m:?}"
		);
		let hashes = extract_hashes(&m);
		assert_eq!(hashes, vec!["abc123def456abc123def456abc123def456"]);
	}
}