pi_agent_rust 0.8.0

Native AI coding agent CLI - Rust port of Pi Agent
use super::*;

fn position(character: u32) -> Position {
    Position { line: 0, character }
}
fn range(start: u32, end: u32) -> Value {
    json!({"start":position(start),"end":position(end)})
}
fn edit(start: u32, end: u32, text: &str) -> Value {
    json!({"range":range(start, end),"newText":text})
}
fn completion() -> Value {
    json!({"label":"alpha","textEdit":edit(0, 2, "alpha")})
}

#[test]
fn plain_primary_edit_uses_server_range_not_caller_fallback() {
    let edits = edits(
        &completion(),
        "al rest",
        position(2),
        Some(Range {
            start: position(2),
            end: position(2),
        }),
    )
    .unwrap();
    assert_eq!(edits, vec![edit(0, 2, "alpha")]);
}

#[test]
fn omitted_text_edit_requires_explicit_language_independent_replacement() {
    let item = json!({"label":"label","insertText":"alpha"});
    assert!(
        edits(&item, "al", position(2), None)
            .unwrap_err()
            .to_string()
            .contains("RANGE_REQUIRED")
    );
    let fallback = Range {
        start: position(0),
        end: position(2),
    };
    assert_eq!(
        edits(&item, "al", position(2), Some(fallback)).unwrap(),
        vec![edit(0, 2, "alpha")]
    );
}

#[test]
fn default_edit_range_uses_text_edit_text_or_label_not_insert_text() {
    let defaults = json!({"editRange":range(0,2),"data":{"opaque":[1,"token"]}});
    let item = materialize(
        &json!({"label":"alpha","insertText":"wrong","textEditText":"right"}),
        Some(&defaults),
    )
    .unwrap();
    assert_eq!(item["textEdit"], edit(0, 2, "right"));
    assert_eq!(item["data"], defaults["data"]);
    let item = materialize(
        &json!({"label":"alpha","insertText":"wrong"}),
        Some(&defaults),
    )
    .unwrap();
    assert_eq!(item["textEdit"], edit(0, 2, "alpha"));
}

#[test]
fn explicit_item_properties_override_defaults_and_preserve_opaque_data() {
    let item = materialize(
        &json!({"label":"x","data":{"id":"item"},"insertTextFormat":1,"textEdit":edit(0,1,"x")}),
        Some(&json!({"editRange":range(0,2),"insertTextFormat":2,"data":{"id":"default"}})),
    )
    .unwrap();
    assert_eq!(item["data"], json!({"id":"item"}));
    assert_eq!(item["insertTextFormat"], 1);
    assert_eq!(item["textEdit"], edit(0, 1, "x"));
}

#[test]
fn insert_replace_edit_defaults_select_the_complete_replace_span() {
    let item = materialize(
        &json!({"label":"alpha"}),
        Some(&json!({"editRange":{"insert":range(0,2),"replace":range(0,4)}})),
    )
    .unwrap();
    assert_eq!(
        edits(&item, "alxx", position(2), None).unwrap(),
        vec![edit(0, 4, "alpha")]
    );
}

#[test]
fn invalid_insert_replace_relations_cannot_choose_an_unrelated_span() {
    for (insert, replace) in [
        (range(1, 2), range(0, 4)),
        (range(0, 4), range(0, 2)),
        (range(0, 1), range(0, 4)),
    ] {
        let item = json!({"label":"alpha","textEdit":{"insert":insert,"replace":replace,"newText":"alpha"}});
        assert!(edits(&item, "alxx", position(2), None).is_err());
    }
}

#[test]
fn main_edit_must_contain_cursor_and_stay_on_one_line() {
    assert!(edits(&completion(), "al rest", position(5), None).is_err());
    let item = json!({"label":"x","textEdit":{"range":{"start":position(0),"end":{"line":1,"character":1}},"newText":"x"}});
    assert!(edits(&item, "al\nx", position(1), None).is_err());
}

#[test]
fn unicode_and_crlf_boundaries_are_exact_not_clamped() {
    for invalid in [range(0, 1), range(0, 99), range(3, 2)] {
        let item = json!({"label":"x","textEdit":{"range":invalid,"newText":"x"}});
        assert!(edits(&item, "😀a\r\nb", position(2), None).is_err());
    }
    let valid = json!({"label":"x","textEdit":edit(2,3,"x")});
    assert!(edits(&valid, "😀a\r\nb", position(3), None).is_ok());
}

#[test]
fn auto_import_edits_are_preserved_in_original_document_coordinates() {
    let item = json!({"label":"Type","textEdit":{"range":{"start":{"line":1,"character":0},"end":{"line":1,"character":2}},"newText":"Type"},
        "additionalTextEdits":[edit(0,0,"use module::Type;\n")]});
    let got = edits(
        &item,
        "// comment\nTy",
        Position {
            line: 1,
            character: 2,
        },
        None,
    )
    .unwrap();
    assert_eq!(got.len(), 2);
    assert_eq!(got[1], edit(0, 0, "use module::Type;\n"));
}

#[test]
fn overlaps_including_duplicate_insert_points_are_rejected() {
    for extra in [
        vec![edit(1, 3, "bad")],
        vec![edit(0, 0, "bad")],
        vec![edit(4, 4, "one"), edit(4, 4, "two")],
    ] {
        let mut item = completion();
        item["additionalTextEdits"] = json!(extra);
        assert!(edits(&item, "al rest", position(2), None).is_err());
    }
}

#[test]
fn adjacent_nonoverlapping_edits_remain_supported() {
    let mut item = completion();
    item["additionalTextEdits"] = json!([edit(2, 3, "_")]);
    assert_eq!(edits(&item, "al rest", position(2), None).unwrap().len(), 2);
}

#[test]
fn annotated_or_resource_shaped_additional_edits_are_not_accepted() {
    for extra in [
        json!({"range":range(4,4),"newText":"x","uri":"file:///outside"}),
        json!({"kind":"delete","uri":"file:///outside"}),
        json!({"range":range(4,4),"newText":"x","annotationId":"confirm"}),
    ] {
        let mut item = completion();
        item["additionalTextEdits"] = json!([extra]);
        assert!(edits(&item, "al rest", position(2), None).is_err());
    }
}

#[test]
fn snippet_command_and_indentation_modes_fail_before_application() {
    for extra in [
        json!({"insertTextFormat":2}),
        json!({"insertTextMode":2}),
        json!({"command":{"command":"server.exec"}}),
    ] {
        let mut item = completion();
        item.as_object_mut()
            .unwrap()
            .extend(extra.as_object().unwrap().clone());
        assert!(
            edits(&item, "al", position(2), None)
                .unwrap_err()
                .to_string()
                .contains("UNSUPPORTED")
        );
    }
}

#[test]
fn lazy_resolve_can_add_auto_imports_and_documentation() {
    let original = completion();
    let mut after = original.clone();
    after["detail"] = json!("resolved type");
    after["documentation"] = json!({"kind":"markdown","value":"help"});
    after["additionalTextEdits"] = json!([edit(4, 4, "import")]);
    assert_eq!(resolved(&original, &after).unwrap(), after);
    assert_eq!(original, completion());
}

#[test]
fn lazy_resolve_cannot_substitute_insertion_identity_or_commands() {
    let original = completion();
    for (property, value) in [
        ("label", json!("other")),
        ("textEdit", edit(0, 2, "other")),
        ("filterText", json!("other")),
        ("data", json!({"id":"other"})),
        ("command", json!({"command":"server.exec"})),
    ] {
        let mut after = original.clone();
        after[property] = value;
        assert!(resolved(&original, &after).is_err(), "{property}");
    }
    let mut after = original.clone();
    after.as_object_mut().unwrap().remove("textEdit");
    assert!(resolved(&original, &after).is_err());
}

#[test]
fn additional_edits_and_wire_items_have_admission_bounds() {
    let mut item = completion();
    item["additionalTextEdits"] = json!(vec![edit(4, 4, "x"); 129]);
    assert!(
        edits(&item, "al rest", position(2), None)
            .unwrap_err()
            .to_string()
            .contains("LIMIT")
    );
    assert!(
        materialize(
            &json!({"label":"x","data":"x".repeat(MAX_ITEM_BYTES)}),
            None
        )
        .is_err()
    );
    assert!(
        materialize(
            &json!({"label":"x"}),
            Some(&json!({"commitCharacters":["."]}))
        )
        .is_err()
    );
}