tea-context 0.1.0

Deterministic prompt modules, context providers, and compiler for tea-rs
Documentation
use crate::common;

use common::{module, segment};
use tea_context::{
    BudgetBehavior, ContextErrorCode, PromptAuthority, PromptBudget, PromptCompiler,
    PromptDiagnosticCode, SegmentDisposition, TRUNCATION_MARKER, estimate_tokens,
};

#[test]
fn required_overflow_fails_and_omit_is_diagnostic() {
    let required = PromptCompiler
        .compile(
            [module(
                "required",
                PromptAuthority::Kernel,
                0,
                vec![segment("required", "12345", BudgetBehavior::Required)],
            )],
            PromptBudget::new(4, 100).unwrap(),
        )
        .unwrap_err();
    assert_eq!(required.code(), ContextErrorCode::BudgetExceeded);

    let prompt = PromptCompiler
        .compile(
            [module(
                "mixed",
                PromptAuthority::Product,
                0,
                vec![
                    segment("keep", "ok", BudgetBehavior::Required),
                    segment("omit", "too long", BudgetBehavior::Omit),
                ],
            )],
            PromptBudget::new(4, 100).unwrap(),
        )
        .unwrap();
    assert_eq!(prompt.text(), "ok");
    assert_eq!(
        prompt.diagnostics()[0].code(),
        PromptDiagnosticCode::OmittedForBudget
    );
}

#[test]
fn truncation_is_utf8_safe_and_uses_fixed_marker() {
    let prompt = PromptCompiler
        .compile(
            [module(
                "truncate",
                PromptAuthority::Workspace,
                0,
                vec![segment(
                    "unicode",
                    "你好abcdefghijklmnop",
                    BudgetBehavior::Truncate,
                )],
            )],
            PromptBudget::new(TRUNCATION_MARKER.len() + 5, 100).unwrap(),
        )
        .unwrap();
    assert!(prompt.text().ends_with(TRUNCATION_MARKER));
    assert!(std::str::from_utf8(prompt.text().as_bytes()).is_ok());
    assert_eq!(
        prompt.diagnostics()[0].code(),
        PromptDiagnosticCode::TruncatedForBudget
    );
    assert_eq!(
        prompt.inspection()[0].disposition(),
        SegmentDisposition::Truncated
    );
}

#[test]
fn separators_count_toward_byte_and_token_budgets() {
    let modules = [module(
        "two",
        PromptAuthority::Product,
        0,
        vec![
            segment("one", "abc", BudgetBehavior::Required),
            segment("two", "def", BudgetBehavior::Required),
        ],
    )];
    let prompt = PromptCompiler
        .compile(modules.clone(), PromptBudget::new(8, 3).unwrap())
        .unwrap();
    assert_eq!(prompt.text(), "abc\n\ndef");
    assert_eq!(prompt.bytes(), 8);
    assert_eq!(prompt.estimated_tokens(), 3);
    assert_eq!(estimate_tokens(8), 3);
    assert_eq!(
        PromptCompiler
            .compile(modules, PromptBudget::new(7, 3).unwrap())
            .unwrap_err()
            .code(),
        ContextErrorCode::BudgetExceeded
    );
}

#[test]
fn fully_optional_prompt_may_compile_empty_with_diagnostics() {
    let prompt = PromptCompiler
        .compile(
            [module(
                "optional",
                PromptAuthority::Session,
                0,
                vec![segment("optional", "too long", BudgetBehavior::Omit)],
            )],
            PromptBudget::new(1, 1).unwrap(),
        )
        .unwrap();
    assert!(prompt.text().is_empty());
    assert_eq!(prompt.estimated_tokens(), 0);
    assert_eq!(
        prompt.diagnostics()[0].code(),
        PromptDiagnosticCode::OmittedForBudget
    );
}

#[test]
fn truncation_without_marker_space_omits_deterministically() {
    let prompt = PromptCompiler
        .compile(
            [module(
                "mixed",
                PromptAuthority::Product,
                0,
                vec![
                    segment("keep", "x", BudgetBehavior::Required),
                    segment("truncate", "abcdef", BudgetBehavior::Truncate),
                ],
            )],
            PromptBudget::new(4, 100).unwrap(),
        )
        .unwrap();
    assert_eq!(prompt.text(), "x");
    assert_eq!(
        prompt.inspection()[1].disposition(),
        SegmentDisposition::OmittedForBudget
    );
}