use std::collections::{BTreeSet, HashSet};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use contextgraph_types::{
Capabilities, ContentFidelity, ContentRef, ContextFrame, ContextQuery, ContextQueryResult,
DataFlow, EgressScope, FrameKind, FrameVerdict, InlineContentRequirement, Provenance,
ProviderInfo, QueryCapability, Representation, Transform, Verdict, VerifyRequest,
VerifyResponse, budget_tokens, is_protocol_timestamp,
};
use crate::error::HostError;
use crate::provider::{ContextProvider, frame_kind_name};
const COMPACT_MIN_TOKENS: u32 = 64;
const LOG_CONTEXT: usize = 2;
const LOG_HEAD: usize = 8;
const LOG_TAIL: usize = 4;
const TABLE_SAMPLE: usize = 5;
const MIN_AMBIGUOUS_TABLE_ROWS: usize = 3;
const MAX_CELL_WORDS: usize = 4;
const STACK_FRAMES: usize = 8;
const CODE_HEAD: usize = 20;
const CODE_TAIL: usize = 8;
const TRANSFORM_VERSION: &str = "1";
const TRANSFORM_IMPL: &str = "contextgraph-host/ingest";
pub const DEFAULT_PROVIDER_ID: &str = "prompt-ingest";
fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
let mut hex = String::with_capacity(64);
for byte in digest {
hex.push(char::from_digit((byte >> 4) as u32, 16).unwrap());
hex.push(char::from_digit((byte & 0x0f) as u32, 16).unwrap());
}
hex
}
fn sha256_digest(s: &str) -> String {
format!("sha256:{}", sha256_hex(s.as_bytes()))
}
fn short_hash(digest: &str) -> &str {
let hex = digest.strip_prefix("sha256:").unwrap_or(digest);
&hex[..hex.len().min(12)]
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PasteIngest {
pub intent: String,
#[serde(default)]
pub anchors: Vec<String>,
#[serde(default)]
pub attachments: Vec<String>,
}
impl PasteIngest {
pub fn new(intent: impl Into<String>, attachment: impl Into<String>) -> Self {
Self {
intent: intent.into(),
anchors: Vec::new(),
attachments: vec![attachment.into()],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IngestConfig {
pub provider_id: String,
}
impl Default for IngestConfig {
fn default() -> Self {
Self {
provider_id: DEFAULT_PROVIDER_ID.to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SegmentKind {
Log,
StackTrace,
Table,
Code,
Prose,
PathRef,
}
impl SegmentKind {
fn frame_kind(self) -> Option<FrameKind> {
match self {
SegmentKind::Log | SegmentKind::StackTrace => Some(FrameKind::Episode),
SegmentKind::Table => Some(FrameKind::Fact),
SegmentKind::Code => Some(FrameKind::Snippet),
SegmentKind::Prose => Some(FrameKind::Doc),
SegmentKind::PathRef => None,
}
}
fn citation_label(self) -> &'static str {
match self {
SegmentKind::Log => "pasted log",
SegmentKind::StackTrace => "pasted stack trace",
SegmentKind::Table => "pasted table",
SegmentKind::Code => "pasted code",
SegmentKind::Prose => "pasted note",
SegmentKind::PathRef => "pasted path",
}
}
fn score(self) -> f32 {
match self {
SegmentKind::StackTrace => 0.85,
SegmentKind::Log => 0.8,
SegmentKind::Code => 0.75,
SegmentKind::Table => 0.7,
SegmentKind::Prose => 0.5,
SegmentKind::PathRef => 0.0,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "outcome", rename_all = "snake_case")]
pub enum SegmentOutcome {
Anchor { uri: String },
Frame {
id: String,
representation: Representation,
inline_tokens: u32,
source_tokens: u32,
},
Duplicate { id: String },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SegmentReport {
pub kind: SegmentKind,
pub summary: String,
pub became: SegmentOutcome,
}
pub struct IngestBundle {
pub query: ContextQuery,
pub provider: IngestProvider,
pub report: Vec<SegmentReport>,
}
pub fn ingest_paste(input: PasteIngest, config: IngestConfig) -> IngestBundle {
let PasteIngest {
intent,
mut anchors,
attachments,
} = input;
let mut artifacts: Vec<Artifact> = Vec::new();
let mut report: Vec<SegmentReport> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for attachment in &attachments {
for block in split_blocks(attachment) {
let text = block.text();
if text.trim().is_empty() {
continue;
}
let kind = classify(&block);
if kind == SegmentKind::PathRef {
let uri = text.trim().to_string();
report.push(SegmentReport {
kind,
summary: format!("anchor · {uri}"),
became: SegmentOutcome::Anchor { uri: uri.clone() },
});
if !anchors.contains(&uri) {
anchors.push(uri);
}
continue;
}
let artifact = Artifact::build(kind, text);
if seen.contains(&artifact.id) {
report.push(SegmentReport {
kind,
summary: format!("duplicate · deduplicated to {}", artifact.id),
became: SegmentOutcome::Duplicate { id: artifact.id },
});
continue;
}
seen.insert(artifact.id.clone());
report.push(SegmentReport {
kind,
summary: artifact.summary.clone(),
became: SegmentOutcome::Frame {
id: artifact.id.clone(),
representation: Representation::Compact,
inline_tokens: budget_tokens(&artifact.inline_content),
source_tokens: budget_tokens(&artifact.full_content),
},
});
artifacts.push(artifact);
}
}
artifacts.sort_by(|a, b| a.id.cmp(&b.id));
let provider = IngestProvider::new(config.provider_id, artifacts);
let query = ContextQuery {
goal: intent,
query_text: None,
embedding: None,
kinds: Vec::new(),
anchors,
max_frames: provider.artifacts.len() as u32,
max_tokens: provider.default_budget_tokens(),
as_of: None,
representation_preferences: vec![Representation::Compact, Representation::Full],
};
IngestBundle {
query,
provider,
report,
}
}
struct RawBlock {
lines: Vec<String>,
fenced_code: bool,
}
impl RawBlock {
fn text(&self) -> String {
self.lines.join("\n")
}
}
fn flush_block(buf: &mut Vec<String>, fenced_code: bool, blocks: &mut Vec<RawBlock>) {
if !buf.is_empty() {
blocks.push(RawBlock {
lines: std::mem::take(buf),
fenced_code,
});
}
}
fn split_blocks(text: &str) -> Vec<RawBlock> {
let mut blocks = Vec::new();
let mut current: Vec<String> = Vec::new();
let mut fence: Vec<String> = Vec::new();
let mut in_fence = false;
for line in text.lines() {
if line.trim_start().starts_with("```") {
if in_fence {
flush_block(&mut fence, true, &mut blocks);
in_fence = false;
} else {
flush_block(&mut current, false, &mut blocks);
in_fence = true;
}
continue;
}
if in_fence {
fence.push(line.to_string());
} else if line.trim().is_empty() {
flush_block(&mut current, false, &mut blocks);
} else {
current.push(line.to_string());
}
}
flush_block(&mut fence, in_fence, &mut blocks);
flush_block(&mut current, false, &mut blocks);
blocks
}
fn classify(block: &RawBlock) -> SegmentKind {
if block.fenced_code {
return SegmentKind::Code;
}
let lines: Vec<&str> = block.lines.iter().map(String::as_str).collect();
if lines.len() == 1 && looks_like_path(lines[0]) {
return SegmentKind::PathRef;
}
if looks_like_stack_trace(&lines) {
return SegmentKind::StackTrace;
}
if looks_like_timestamped_log(&lines) {
return SegmentKind::Log;
}
if delimited_table_delimiter(&lines).is_some() {
return SegmentKind::Table;
}
if looks_like_log(&lines) {
return SegmentKind::Log;
}
if aligned_table_delimiter(&lines).is_some() {
return SegmentKind::Table;
}
if looks_like_code(&lines) {
return SegmentKind::Code;
}
SegmentKind::Prose
}
const PATH_EXTENSIONS: &[&str] = &[
"rs", "ts", "tsx", "js", "jsx", "py", "go", "rb", "java", "kt", "c", "h", "cc", "cpp", "hpp",
"cs", "md", "toml", "json", "yaml", "yml", "txt", "sh", "sql", "lock", "cfg", "ini",
];
fn looks_like_path(line: &str) -> bool {
let s = line.trim();
if s.is_empty() || s.chars().any(char::is_whitespace) {
return false;
}
if s.starts_with("http://") || s.starts_with("https://") {
return false;
}
if s.starts_with("file://") {
return true;
}
let rooted =
s.starts_with("./") || s.starts_with("../") || s.starts_with("~/") || s.starts_with('/');
let has_extension = s
.rsplit('/')
.next()
.and_then(|name| name.rsplit_once('.'))
.is_some_and(|(_, ext)| PATH_EXTENSIONS.contains(&ext));
let host_like =
!rooted && !has_extension && s.split('/').next().is_some_and(|first| first.contains('.'));
if host_like {
return false;
}
(s.contains('/') && (rooted || has_extension || s.matches('/').count() >= 1))
|| (rooted && !s.contains(' '))
|| has_extension
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TableDelimiter {
Pipe,
Tab,
Comma,
Whitespace,
}
fn delimited_table_delimiter(lines: &[&str]) -> Option<TableDelimiter> {
if lines.len() < 2 {
return None;
}
if rows_agree_on_delimiter_count(lines, '|') {
return Some(TableDelimiter::Pipe);
}
let indented = lines.iter().filter(|l| l.starts_with('\t')).count();
if rows_agree_on_delimiter_count(lines, '\t') && indented * 10 < lines.len() * 7 {
return Some(TableDelimiter::Tab);
}
if lines.len() >= MIN_AMBIGUOUS_TABLE_ROWS
&& rows_agree_on_delimiter_count(lines, ',')
&& rows_read_as_values(lines, TableDelimiter::Comma)
{
return Some(TableDelimiter::Comma);
}
None
}
fn aligned_table_delimiter(lines: &[&str]) -> Option<TableDelimiter> {
if lines.len() < MIN_AMBIGUOUS_TABLE_ROWS {
return None;
}
let counts: Vec<usize> = lines
.iter()
.map(|l| split_row(l, TableDelimiter::Whitespace).len())
.collect();
let common = most_common(&counts)?;
if common < 2 || !majority_agrees(&counts, common) {
return None;
}
rows_read_as_values(lines, TableDelimiter::Whitespace).then_some(TableDelimiter::Whitespace)
}
fn table_delimiter(lines: &[&str]) -> Option<TableDelimiter> {
delimited_table_delimiter(lines).or_else(|| aligned_table_delimiter(lines))
}
fn rows_agree_on_delimiter_count(lines: &[&str], delimiter: char) -> bool {
let counts: Vec<usize> = lines.iter().map(|l| l.matches(delimiter).count()).collect();
most_common(&counts).is_some_and(|common| common >= 1 && majority_agrees(&counts, common))
}
fn majority_agrees(counts: &[usize], common: usize) -> bool {
let agree = counts.iter().filter(|&&c| c == common).count();
agree * 10 >= counts.len() * 7
}
fn rows_read_as_values(lines: &[&str], delimiter: TableDelimiter) -> bool {
lines.iter().all(|line| {
split_row(line, delimiter)
.iter()
.all(|cell| cell.split_whitespace().count() <= MAX_CELL_WORDS)
})
}
fn split_row(line: &str, delimiter: TableDelimiter) -> Vec<String> {
match delimiter {
TableDelimiter::Pipe => {
let mut cells: Vec<String> = line.split('|').map(|c| c.trim().to_string()).collect();
if cells.first().is_some_and(String::is_empty) {
cells.remove(0);
}
if cells.last().is_some_and(String::is_empty) {
cells.pop();
}
cells
}
TableDelimiter::Tab => line.split('\t').map(|c| c.trim().to_string()).collect(),
TableDelimiter::Comma => line.split(',').map(|c| c.trim().to_string()).collect(),
TableDelimiter::Whitespace => line
.split(" ")
.map(str::trim)
.filter(|c| !c.is_empty())
.map(str::to_string)
.collect(),
}
}
const LOG_LEVELS: &[&str] = &[
"ERROR", "ERR", "WARN", "WARNING", "INFO", "DEBUG", "TRACE", "FATAL", "CRITICAL", "CRIT",
"PANIC", "PANICKED", "SEVERE", "NOTICE",
];
const ALERT_LEVELS: &[&str] = &[
"ERROR", "ERR", "WARN", "WARNING", "FATAL", "CRITICAL", "CRIT", "PANIC", "PANICKED", "SEVERE",
];
const STACK_MARKERS: &[&str] = &[
"at ",
"File \"",
"Traceback",
"panicked at",
"-->",
"Caused by",
"thread '",
];
fn looks_like_log(lines: &[&str]) -> bool {
let non_empty: Vec<&str> = non_empty_lines(lines);
if non_empty.is_empty() {
return false;
}
let matched = non_empty.iter().filter(|l| is_log_line(l)).count();
matched * 2 >= non_empty.len()
}
fn looks_like_timestamped_log(lines: &[&str]) -> bool {
let non_empty: Vec<&str> = non_empty_lines(lines);
if non_empty.is_empty() {
return false;
}
let stamped = non_empty
.iter()
.filter(|l| leading_timestamp(l).is_some())
.count();
stamped * 2 >= non_empty.len() && non_empty.iter().any(|l| has_level_token(l, LOG_LEVELS))
}
fn non_empty_lines<'a>(lines: &[&'a str]) -> Vec<&'a str> {
lines
.iter()
.copied()
.filter(|l| !l.trim().is_empty())
.collect()
}
fn is_log_line(line: &str) -> bool {
let t = line.trim_start();
if t.is_empty() {
return false;
}
if STACK_MARKERS.iter().any(|m| t.starts_with(m)) {
return true;
}
if has_level_token(t, LOG_LEVELS) {
return true;
}
if leading_timestamp(t).is_some() {
return true;
}
let first = t.split_whitespace().next().unwrap_or("");
if first.starts_with('[') {
return true;
}
first.chars().next().is_some_and(|c| c.is_ascii_digit())
&& (first.contains(':') || first.contains('-'))
}
fn looks_like_stack_trace(lines: &[&str]) -> bool {
let non_empty = non_empty_lines(lines).len();
if non_empty == 0 {
return false;
}
let frames = lines.iter().filter(|l| is_stack_frame_line(l)).count();
frames >= 2 && frames * 4 >= non_empty && lines.iter().any(|l| is_exception_header(l))
}
fn is_exception_header(line: &str) -> bool {
let t = strip_log_prefix(line.trim());
if t.starts_with("Traceback (most recent call last)")
|| t.starts_with("thread '")
|| t.contains("panicked at")
|| t.starts_with("Caused by")
|| (t.starts_with("goroutine ") && t.contains("[running]"))
{
return true;
}
let head = t.split_once(':').map_or(t, |(before, _)| before);
let words: Vec<&str> = head.split_whitespace().collect();
if words.is_empty() || words.len() > 2 {
return false;
}
let name = words[words.len() - 1];
let name = name.rsplit('.').next().unwrap_or(name);
name.ends_with("Error") || name.ends_with("Exception")
}
fn strip_log_prefix(line: &str) -> &str {
let mut rest = line.trim_start();
for _ in 0..4 {
let ceremonial = leading_timestamp(rest).is_some()
|| rest.starts_with('[')
|| rest
.split_whitespace()
.next()
.is_some_and(|token| has_level_token(token, LOG_LEVELS));
if !ceremonial {
break;
}
let Some((_, tail)) = rest.split_once(char::is_whitespace) else {
break;
};
rest = tail.trim_start();
}
rest
}
fn is_stack_frame_line(line: &str) -> bool {
let t = line.trim_start();
if t.starts_with("at ") {
return true;
}
if t.starts_with("File \"") {
return true;
}
if t.starts_with("from ") && t.contains(':') {
return true;
}
if line.starts_with('\t') && t.contains(".go:") {
return true;
}
let digits = t.bytes().take_while(u8::is_ascii_digit).count();
digits > 0 && t[digits..].starts_with(": ")
}
fn is_alert_line(line: &str) -> bool {
has_level_token(line.trim_start(), ALERT_LEVELS)
}
fn has_level_token(s: &str, set: &[&str]) -> bool {
s.split(|c: char| !c.is_ascii_alphanumeric())
.filter(|w| !w.is_empty())
.any(|w| set.contains(&w.to_ascii_uppercase().as_str()))
}
fn looks_like_code(lines: &[&str]) -> bool {
if lines.len() < 3 {
return false;
}
const PREFIXES: &[&str] = &[
"fn ",
"def ",
"class ",
"import ",
"const ",
"let ",
"var ",
"pub ",
"function ",
"#include",
"package ",
"func ",
"return ",
"if ",
"for ",
"while ",
"@",
];
let codey = lines
.iter()
.filter(|l| {
let t = l.trim();
let te = l.trim_end();
te.ends_with(';')
|| te.ends_with('{')
|| te.ends_with('}')
|| te.ends_with("=>")
|| te.ends_with("):")
|| PREFIXES.iter().any(|p| t.starts_with(p))
})
.count();
codey * 2 >= lines.len()
}
fn most_common(values: &[usize]) -> Option<usize> {
let mut best: Option<(usize, usize)> = None; for &v in values {
let count = values.iter().filter(|&&x| x == v).count();
match best {
Some((_, bc)) if bc >= count => {}
_ => best = Some((v, count)),
}
}
best.map(|(v, _)| v)
}
fn plural<'a>(count: usize, one: &'a str, many: &'a str) -> &'a str {
if count == 1 { one } else { many }
}
struct LogRun<'a> {
text: &'a str,
repeats: usize,
}
fn collapse_runs<'a>(lines: &[&'a str]) -> Vec<LogRun<'a>> {
let mut runs: Vec<LogRun<'a>> = Vec::new();
for &line in lines {
match runs.last_mut() {
Some(run) if run.text == line => run.repeats += 1,
_ => runs.push(LogRun {
text: line,
repeats: 1,
}),
}
}
runs
}
fn distill_log(full: &str) -> (String, Option<String>, Option<String>) {
let lines: Vec<&str> = full.lines().collect();
let source_lines = lines.len();
if source_lines == 0 {
return (String::new(), None, None);
}
let runs = collapse_runs(&lines);
let total = runs.len();
let alerts: Vec<usize> = (0..total)
.filter(|&i| is_alert_line(runs[i].text))
.collect();
let mut keep: BTreeSet<usize> = BTreeSet::new();
keep.insert(0);
keep.insert(total - 1);
if alerts.is_empty() {
for i in 0..LOG_HEAD.min(total) {
keep.insert(i);
}
for i in total.saturating_sub(LOG_TAIL)..total {
keep.insert(i);
}
} else {
for &a in &alerts {
let lo = a.saturating_sub(LOG_CONTEXT);
let hi = (a + LOG_CONTEXT).min(total - 1);
for i in lo..=hi {
keep.insert(i);
}
}
}
let mut out = String::new();
let alert_note = if alerts.is_empty() {
String::new()
} else {
let alert_lines: usize = alerts.iter().map(|&i| runs[i].repeats).sum();
format!(", {alert_lines} error/warn line(s)")
};
out.push_str(&format!("[{source_lines}-line log{alert_note}]\n"));
let mut prev: Option<usize> = None;
for &i in &keep {
if let Some(p) = prev
&& i > p + 1
{
let elided: usize = runs[p + 1..i].iter().map(|r| r.repeats).sum();
out.push_str(&format!(
"… ({elided} {} elided) …\n",
plural(elided, "line", "lines")
));
}
out.push_str(runs[i].text);
out.push('\n');
if runs[i].repeats > 1 {
out.push_str(&format!("… (×{})\n", runs[i].repeats));
}
prev = Some(i);
}
let (valid_from, valid_to) = temporal_window(&lines);
(out.trim_end().to_string(), valid_from, valid_to)
}
fn distill_stack_trace(full: &str) -> String {
let lines: Vec<&str> = full.lines().collect();
let frames: BTreeSet<usize> = (0..lines.len())
.filter(|&i| is_stack_frame_line(lines[i]))
.collect();
let (Some(&first), Some(&last)) = (frames.first(), frames.last()) else {
return full.to_string();
};
let kept: BTreeSet<usize> = frames.iter().take(STACK_FRAMES).copied().collect();
let elided = frames.len() - kept.len();
let mut out = String::new();
let mut previous_kept = true;
let mut noted = false;
for (i, line) in lines.iter().enumerate() {
let keep = if i < first || i > last {
true
} else if frames.contains(&i) {
kept.contains(&i)
} else {
previous_kept
};
if keep {
out.push_str(line);
out.push('\n');
} else if !noted && elided > 0 {
out.push_str(&format!(
"… ({elided} more {})\n",
plural(elided, "frame", "frames")
));
noted = true;
}
previous_kept = keep;
}
out.trim_end().to_string()
}
fn temporal_window(lines: &[&str]) -> (Option<String>, Option<String>) {
let first = leading_instant(lines.first().copied());
let last = leading_instant(lines.last().copied());
match (&first, &last) {
(Some(f), Some(t)) if f > t => (last, first),
_ => (first, last),
}
}
fn leading_instant(line: Option<&str>) -> Option<String> {
leading_timestamp(line?)?.normalized
}
struct LeadingTimestamp {
normalized: Option<String>,
}
fn leading_timestamp(line: &str) -> Option<LeadingTimestamp> {
let t = line.trim_start();
let candidate = match t.strip_prefix('[') {
Some(rest) => rest.split_once(']')?.0,
None => t,
};
let candidate = candidate.trim_start();
if let Some(dated) = parse_dated_timestamp(candidate) {
return Some(dated);
}
if is_syslog_timestamp(candidate) || parse_clock(candidate).is_some() {
return Some(LeadingTimestamp { normalized: None });
}
None
}
fn parse_dated_timestamp(s: &str) -> Option<LeadingTimestamp> {
let b = s.as_bytes();
if b.len() < 10 {
return None;
}
let separator = b[4];
if (separator != b'-' && separator != b'/') || b[7] != separator {
return None;
}
if !b[..4].iter().all(u8::is_ascii_digit)
|| !b[5..7].iter().all(u8::is_ascii_digit)
|| !b[8..10].iter().all(u8::is_ascii_digit)
{
return None;
}
let date = format!("{}-{}-{}", &s[..4], &s[5..7], &s[8..10]);
let unnormalized = Some(LeadingTimestamp { normalized: None });
let Some(after_separator) = s[10..].strip_prefix(['T', 't', ' ']) else {
return unnormalized;
};
let Some((clock, tail)) = parse_clock(after_separator) else {
return unnormalized;
};
if !zone_is_utc(tail) {
return unnormalized;
}
let candidate = format!("{date}T{clock}Z");
if is_protocol_timestamp(&candidate) {
return Some(LeadingTimestamp {
normalized: Some(candidate),
});
}
unnormalized
}
fn parse_clock(s: &str) -> Option<(String, &str)> {
let b = s.as_bytes();
if b.len() < 8 || b[2] != b':' || b[5] != b':' {
return None;
}
if !(b[..2].iter().all(u8::is_ascii_digit)
&& b[3..5].iter().all(u8::is_ascii_digit)
&& b[6..8].iter().all(u8::is_ascii_digit))
{
return None;
}
let mut clock = s[..8].to_string();
let mut rest = &s[8..];
if let Some(fraction) = rest.strip_prefix(['.', ',']) {
let digits = fraction.bytes().take_while(u8::is_ascii_digit).count();
if digits > 0 {
clock.push('.');
clock.push_str(&fraction[..digits]);
rest = &fraction[digits..];
}
}
Some((clock, rest))
}
fn zone_is_utc(tail: &str) -> bool {
let t = tail.trim_start();
if t.is_empty() {
return true;
}
let ends_token = |rest: &str| rest.is_empty() || rest.starts_with(char::is_whitespace);
if let Some(rest) = t.strip_prefix(['Z', 'z']) {
return ends_token(rest);
}
for utc in ["+00:00", "-00:00", "+0000", "-0000"] {
if let Some(rest) = t.strip_prefix(utc) {
return ends_token(rest);
}
}
if t.starts_with(['+', '-']) {
return false;
}
if let Some(rest) = t.strip_prefix("UTC").or_else(|| t.strip_prefix("GMT")) {
return ends_token(rest);
}
true
}
const MONTH_ABBREVIATIONS: &[&str] = &[
"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec",
];
fn is_syslog_timestamp(s: &str) -> bool {
let mut tokens = s.split_whitespace();
let Some(month) = tokens.next() else {
return false;
};
if !MONTH_ABBREVIATIONS.contains(&month.to_ascii_lowercase().as_str()) {
return false;
}
let Some(day) = tokens.next() else {
return false;
};
if day.is_empty() || day.len() > 2 || !day.bytes().all(|b| b.is_ascii_digit()) {
return false;
}
tokens
.next()
.is_some_and(|clock| parse_clock(clock).is_some())
}
fn distill_table(full: &str) -> String {
let lines: Vec<&str> = full.lines().filter(|l| !l.trim().is_empty()).collect();
let delimiter = table_delimiter(&lines).unwrap_or(if lines.iter().any(|l| l.contains('|')) {
TableDelimiter::Pipe
} else {
TableDelimiter::Tab
});
let mut rows: Vec<Vec<String>> = lines.iter().map(|l| split_row(l, delimiter)).collect();
rows.retain(|r| !r.iter().all(|c| is_separator_cell(c)));
if rows.is_empty() {
return full.to_string();
}
let header = rows.remove(0);
let cols = header.len();
let data = rows;
let mut column_summaries: Vec<String> = Vec::with_capacity(cols);
for (idx, name) in header.iter().enumerate() {
let cells: Vec<&str> = data
.iter()
.map(|r| r.get(idx).map_or("", String::as_str))
.collect();
column_summaries.push(format!("{name} ({})", infer_column_type(&cells)));
}
let mut out = String::new();
out.push_str(&format!("[{} rows × {cols} columns]\n", data.len()));
out.push_str(&format!("columns: {}\n", column_summaries.join(", ")));
out.push_str("sample:\n");
out.push_str(&header.join(" | "));
out.push('\n');
for row in data.iter().take(TABLE_SAMPLE) {
out.push_str(&row.join(" | "));
out.push('\n');
}
if data.len() > TABLE_SAMPLE {
out.push_str(&format!("… ({} more rows)", data.len() - TABLE_SAMPLE));
}
out.trim_end().to_string()
}
fn is_separator_cell(cell: &str) -> bool {
let c = cell.trim();
!c.is_empty() && c.chars().all(|ch| ch == '-' || ch == ':')
}
fn infer_column_type(cells: &[&str]) -> String {
let values: Vec<&str> = cells.iter().copied().filter(|c| !is_null_cell(c)).collect();
let nullable = values.len() < cells.len();
if values.is_empty() {
return "empty".to_string();
}
let all = |predicate: fn(&str) -> bool| values.iter().all(|s| predicate(s));
let base = if all(is_percent) {
"percent"
} else if all(is_currency) {
"currency"
} else if all(|s| number_shape(s) == Some(NumberShape::Integer)) {
"int"
} else if all(|s| number_shape(s).is_some()) {
"float"
} else if all(|s| matches!(s.to_ascii_lowercase().as_str(), "true" | "false")) {
"bool"
} else if all(looks_like_datetime) {
"timestamp"
} else {
"text"
};
if nullable {
format!("{base}?")
} else {
base.to_string()
}
}
fn is_null_cell(cell: &str) -> bool {
let c = cell.trim();
c.is_empty()
|| matches!(
c.to_ascii_lowercase().as_str(),
"null" | "nil" | "none" | "n/a" | "na" | "nan" | "-" | "—"
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NumberShape {
Integer,
Fractional,
}
fn number_shape(s: &str) -> Option<NumberShape> {
let body = s.trim();
let body = body.strip_prefix(['-', '+']).unwrap_or(body);
let (integer, fraction) = match body.split_once('.') {
Some((integer, fraction)) => (integer, Some(fraction)),
None => (body, None),
};
if !is_grouped_digits(integer) {
return None;
}
match fraction {
None => Some(NumberShape::Integer),
Some(f) if !f.is_empty() && f.bytes().all(|b| b.is_ascii_digit()) => {
Some(NumberShape::Fractional)
}
Some(_) => None,
}
}
fn is_grouped_digits(s: &str) -> bool {
if s.is_empty() {
return false;
}
if !s.contains(',') {
return s.bytes().all(|b| b.is_ascii_digit());
}
let mut groups = s.split(',');
let head = groups.next().unwrap_or("");
if head.is_empty() || head.len() > 3 || !head.bytes().all(|b| b.is_ascii_digit()) {
return false;
}
groups.all(|g| g.len() == 3 && g.bytes().all(|b| b.is_ascii_digit()))
}
fn is_percent(s: &str) -> bool {
s.trim()
.strip_suffix('%')
.is_some_and(|number| number_shape(number).is_some())
}
const CURRENCY_SYMBOLS: &[char] = &['$', '€', '£', '¥', '₹', '₽'];
fn is_currency(s: &str) -> bool {
let t = s.trim();
let t = t
.strip_prefix('(')
.and_then(|inner| inner.strip_suffix(')'))
.unwrap_or(t);
let body = t.strip_prefix(['-', '+']).unwrap_or(t);
if let Some(rest) = body.strip_prefix(CURRENCY_SYMBOLS) {
return number_shape(rest.trim_start()).is_some();
}
if let Some(rest) = body.strip_suffix(CURRENCY_SYMBOLS) {
return number_shape(rest.trim_end()).is_some();
}
body.rsplit_once(' ').is_some_and(|(number, code)| {
code.len() == 3
&& code.bytes().all(|b| b.is_ascii_uppercase())
&& number_shape(number).is_some()
})
}
fn looks_like_datetime(s: &str) -> bool {
if is_protocol_timestamp(s) {
return true;
}
let b = s.as_bytes();
b.len() >= 8 && b[..4].iter().all(u8::is_ascii_digit) && b.get(4) == Some(&b'-')
}
fn distill_code(full: &str) -> String {
let lines: Vec<&str> = full.lines().collect();
let total = lines.len();
if total <= CODE_HEAD + CODE_TAIL {
return full.to_string();
}
let mut out = String::new();
for line in &lines[..CODE_HEAD] {
out.push_str(line);
out.push('\n');
}
out.push_str(&format!(
"… ({} lines elided) …\n",
total - CODE_HEAD - CODE_TAIL
));
for line in &lines[total - CODE_TAIL..] {
out.push_str(line);
out.push('\n');
}
out.trim_end().to_string()
}
struct Artifact {
id: String,
kind: FrameKind,
title: String,
citation_label: String,
score: f32,
full_content: String,
address_hash: String,
inline_content: String,
transform: Transform,
fidelity: ContentFidelity,
compacted: bool,
valid_from: Option<String>,
valid_to: Option<String>,
summary: String,
}
impl Artifact {
fn build(kind: SegmentKind, full_content: String) -> Self {
let frame_kind = kind
.frame_kind()
.expect("PathRef is routed to anchors before build");
let address_hash = sha256_digest(&full_content);
let id = format!("frm_{}", short_hash(&address_hash));
let (distilled, verbatim_transform, distilled_transform, distilled_fidelity, vf, vt) =
match kind {
SegmentKind::Log => {
let (inline, vf, vt) = distill_log(&full_content);
(
inline,
verbatim_transform(),
transform("extractive_summary"),
ContentFidelity::Summarized,
vf,
vt,
)
}
SegmentKind::StackTrace => {
let at = leading_instant(full_content.lines().next());
(
distill_stack_trace(&full_content),
verbatim_transform(),
transform("stack_frame_head"),
ContentFidelity::Summarized,
at.clone(),
at,
)
}
SegmentKind::Table => (
distill_table(&full_content),
verbatim_transform(),
transform("tabular_sample"),
ContentFidelity::Summarized,
None,
None,
),
SegmentKind::Code => (
distill_code(&full_content),
verbatim_transform(),
transform("truncation"),
ContentFidelity::Summarized,
None,
None,
),
SegmentKind::Prose | SegmentKind::PathRef => (
full_content.clone(),
verbatim_transform(),
verbatim_transform(),
ContentFidelity::Exact,
None,
None,
),
};
let full_tokens = budget_tokens(&full_content);
let worth_compacting = kind != SegmentKind::Prose
&& full_tokens > COMPACT_MIN_TOKENS
&& budget_tokens(&distilled) < full_tokens;
let (inline_content, transform, fidelity, compacted) = if worth_compacting {
(distilled, distilled_transform, distilled_fidelity, true)
} else {
(
full_content.clone(),
verbatim_transform,
ContentFidelity::Exact,
false,
)
};
let line_count = full_content.lines().count();
let title = match kind {
SegmentKind::Log => format!("log · {line_count} lines"),
SegmentKind::StackTrace => format!("stack trace · {line_count} lines"),
SegmentKind::Table => format!("table · {line_count} lines"),
SegmentKind::Code => format!("code · {line_count} lines"),
SegmentKind::Prose => "note".to_string(),
SegmentKind::PathRef => "path".to_string(),
};
let summary = if compacted {
format!(
"{title} · {} → {} tokens",
full_tokens,
budget_tokens(&inline_content)
)
} else {
format!("{title} · {full_tokens} tokens")
};
Self {
id,
kind: frame_kind,
title,
citation_label: kind.citation_label().to_string(),
score: kind.score(),
full_content,
address_hash,
inline_content,
transform,
fidelity,
compacted,
valid_from: vf,
valid_to: vt,
summary,
}
}
fn inline_tokens(&self) -> u32 {
budget_tokens(&self.inline_content)
}
fn content_ref(&self, provider_id: &str) -> ContentRef {
ContentRef {
provider_id: provider_id.to_string(),
uri: format!("context://{provider_id}/artifacts/{}", self.address_hash),
expires_at: None,
}
}
fn provenance(&self) -> Provenance {
Provenance {
kind: "derivation".to_string(),
uri: None,
range: None,
digest: None,
method: Some("paste".to_string()),
by: Some(TRANSFORM_IMPL.to_string()),
}
}
fn served_digests(&self) -> Vec<String> {
let mut digests = vec![self.address_hash.clone()];
if self.compacted {
let inline = sha256_digest(&self.inline_content);
if inline != self.address_hash {
digests.push(inline);
}
}
digests
}
fn apply_common(&self, frame: &mut ContextFrame) {
frame.citation_label = Some(self.citation_label.clone());
frame.provenance = vec![self.provenance()];
frame.inline_content_requirement =
Some(InlineContentRequirement::ResolvableReferenceAllowed);
frame.valid_from = self.valid_from.clone();
frame.valid_to = self.valid_to.clone();
}
fn as_full(&self) -> ContextFrame {
let content = self.full_content.clone();
let cost = budget_tokens(&content);
let mut frame = ContextFrame::full(
self.id.clone(),
self.kind,
self.title.clone(),
content,
self.score,
cost,
);
frame.content_digest = Some(self.address_hash.clone());
frame.content_fidelity = Some(ContentFidelity::Exact);
self.apply_common(&mut frame);
frame
}
fn as_compact(&self, provider_id: &str) -> ContextFrame {
let inline = self.inline_content.clone();
let cost = budget_tokens(&inline);
let mut frame = ContextFrame::full(
self.id.clone(),
self.kind,
self.title.clone(),
inline.clone(),
self.score,
cost,
);
frame.representation = Representation::Compact;
frame.content_digest = Some(sha256_digest(&inline));
frame.canonical_content_hash = Some(self.address_hash.clone());
frame.canonical_token_cost = Some(budget_tokens(&self.full_content));
frame.transform = Some(self.transform.clone());
frame.content_ref = Some(self.content_ref(provider_id));
frame.content_fidelity = Some(self.fidelity);
self.apply_common(&mut frame);
frame
}
fn as_reference(&self, provider_id: &str) -> ContextFrame {
let mut frame = ContextFrame::reference(
self.id.clone(),
self.kind,
self.title.clone(),
self.content_ref(provider_id),
self.address_hash.clone(),
self.score,
);
frame.canonical_token_cost = Some(budget_tokens(&self.full_content));
frame.content_fidelity = Some(ContentFidelity::Omitted);
self.apply_common(&mut frame);
frame
}
fn as_representation(&self, provider_id: &str, representation: Representation) -> ContextFrame {
match representation {
Representation::Full => self.as_full(),
Representation::Compact => self.as_compact(provider_id),
Representation::Reference => self.as_reference(provider_id),
}
}
}
fn transform(method: &str) -> Transform {
Transform {
method: method.to_string(),
implementation: TRANSFORM_IMPL.to_string(),
version: TRANSFORM_VERSION.to_string(),
}
}
fn verbatim_transform() -> Transform {
transform("verbatim")
}
pub struct IngestProvider {
id: String,
info: ProviderInfo,
capabilities: Capabilities,
artifacts: Vec<Artifact>,
}
impl IngestProvider {
fn new(id: impl Into<String>, artifacts: Vec<Artifact>) -> Self {
let id = id.into();
let mut kinds: Vec<String> = artifacts
.iter()
.map(|a| frame_kind_name(a.kind).to_string())
.collect();
kinds.sort();
kinds.dedup();
let info = ProviderInfo {
name: DEFAULT_PROVIDER_ID.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
data_flow: DataFlow {
reads: true,
writes: false,
egress: false,
egress_scopes: vec![EgressScope::LocalOnly],
},
};
let capabilities = Capabilities {
query: QueryCapability { kinds },
correlation: false,
graph: false,
embeddings_fingerprint: None,
verify: true,
representations: vec![
Representation::Full,
Representation::Compact,
Representation::Reference,
],
resolve: true,
};
Self {
id,
info,
capabilities,
artifacts,
}
}
fn default_budget_tokens(&self) -> u32 {
self.artifacts.iter().map(Artifact::inline_tokens).sum()
}
pub fn len(&self) -> usize {
self.artifacts.len()
}
pub fn is_empty(&self) -> bool {
self.artifacts.is_empty()
}
}
#[async_trait]
impl ContextProvider for IngestProvider {
fn id(&self) -> &str {
&self.id
}
fn info(&self) -> &ProviderInfo {
&self.info
}
fn capabilities(&self) -> &Capabilities {
&self.capabilities
}
async fn query(&self, query: &ContextQuery) -> Result<ContextQueryResult, HostError> {
let representation = query
.select_representation(&[
Representation::Full,
Representation::Compact,
Representation::Reference,
])
.unwrap_or(Representation::Full);
let mut candidates: Vec<ContextFrame> = self
.artifacts
.iter()
.filter(|a| query.kinds.is_empty() || query.kinds.contains(&a.kind))
.map(|a| a.as_representation(&self.id, representation))
.collect();
candidates.sort_by(|a, b| {
b.score
.partial_cmp(&a.score)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a.id.cmp(&b.id))
});
let mut frames: Vec<ContextFrame> = Vec::new();
let mut used: u64 = 0;
let mut dropped: u32 = 0;
for frame in candidates {
if frames.len() as u32 >= query.max_frames {
dropped += 1;
continue;
}
let cost = frame.token_cost as u64;
if used + cost > query.max_tokens as u64 {
dropped += 1;
continue;
}
used += cost;
frames.push(frame);
}
Ok(ContextQueryResult {
frames,
truncated: dropped > 0,
dropped_estimate: (dropped > 0).then_some(dropped),
})
}
async fn verify(&self, request: &VerifyRequest) -> Result<VerifyResponse, HostError> {
let verdicts = request
.frames
.iter()
.map(|held| {
let verdict = match self.artifacts.iter().find(|a| a.id == held.frame_id) {
Some(artifact) => match &held.content_digest {
Some(digest) if artifact.served_digests().contains(digest) => {
Verdict::Valid
}
Some(_) => Verdict::Stale {
replacement_digest: Some(artifact.address_hash.clone()),
},
None => Verdict::Unknown,
},
None => Verdict::Gone,
};
FrameVerdict::new(held.clone(), verdict)
})
.collect();
Ok(VerifyResponse::new(verdicts))
}
}
#[cfg(test)]
mod tests;