Md agent
La classe MdAgent è un layer di manipolazione dei file Markdown (.md). Utilizza markdown-it-py per costruire un indice navigabile delle sezioni del documento (basato sugli heading), ma applica tutte le modifiche direttamente sul testo raw tramite slicing. Questo approccio garantisce che la formattazione originale dell'utente — indentazione, scelte stilistiche (* vs _), spaziatura — venga preservata al 100%.
Costruttore
MdAgent(file_bytes: bytes | None = None, encoding: str = "utf-8")
file_bytes(bytes | None): il contenuto binario del file.md. SeNone, viene creato un documento vuoto.encoding(str): encoding del file. Defaultutf-8.
Metodi
save
Restituisce il documento corrente come sequenza di bytes.
save() -> bytes
get_text
Restituisce il testo Markdown raw.
get_text() -> str
Operazione pura: la stringa restituita riflette esattamente lo stato corrente del documento.
get_structure
Restituisce una mappa testuale leggera del documento, mostrando l'albero gerarchico degli heading con i loro indici, livelli e numero di righe di body.
get_structure(preview_len: int = 40) -> str
preview_len(int): numero massimo di caratteri per i titoli delle sezioni.
L'output è una stringa con un formato simile a:
MARKDOWN STRUCTURE (4 sections, 20 lines)
────────────────────────────────────────────────────────────
Contains: 1 code blocks, 1 links, 1 images
[0 ] # Intro (19 lines)
[1 ] ## Contesto (12 lines)
[2 ] ### Dettagli (8 lines)
[3 ] ## Conclusioni (2 lines)
L'indentazione visiva riflette il livello dell'heading. I conteggi globali di code block, link e immagini vengono riportati se presenti.
Se il documento non contiene heading, viene mostrata un'anteprima del testo. Tutti i restanti metodi che lavorano per indice di sezione restituiscono liste vuote in quel caso.
get_toc
Restituisce la table of contents come lista piatta di dizionari.
get_toc() -> list[dict]
Esempio di output:
[
{"idx": 0, "level": 1, "title": "Intro"},
{"idx": 1, "level": 2, "title": "Contesto"},
{"idx": 2, "level": 3, "title": "Dettagli"},
{"idx": 3, "level": 2, "title": "Conclusioni"},
]
get_section
Restituisce la rappresentazione completa di una sezione, identificata dal suo indice.
get_section(idx: int) -> dict
idx(int): indice della sezione (corrisponde all'indice mostrato daget_structureeget_toc).
Il risultato ha questa forma:
{
"idx": 1,
"level": 2,
"title": "Contesto",
"heading_line": "## Contesto",
"content": "Qualche contesto qui. Vedi [il link](...)\n\n### Dettagli\n...",
"raw": "## Contesto\n\nQualche contesto qui...",
"line_start": 4,
"line_end": 16,
"children": [2]
}
contentcontiene tutto il body della sezione (escluso l'heading stesso), incluse eventuali sotto-sezioni.rawcontiene heading + body completo.childrenè la lista degli indici delle sotto-sezioni dirette e indirette contenute nel range.
La sezione "termina" dove inizia il prossimo heading di livello uguale o inferiore. Una ## Sezione include quindi tutte le sue ### figlie.
get_code_blocks
Restituisce tutti i blocchi di codice (fenced o indented) presenti nel documento.
get_code_blocks() -> list[dict]
Esempio di output:
[
{
"language": "python",
"code": "def hello():\n print('hi')\n",
"line": 12,
"section_idx": 2
}
]
languageè la stringa di info del fence (vuota per indented code).section_idxè l'indice della sezione più profonda che contiene il blocco.
get_links
Restituisce tutti i link inline [text](url) presenti nel documento.
get_links() -> list[dict]
Esempio di output:
[
{
"text": "il link",
"url": "https://example.com",
"line": 6,
"section_idx": 1
}
]
I link reference-style ([text][ref] con definizione separata) non sono attualmente riconosciuti.
get_images
Restituisce tutte le immagini  presenti nel documento.
get_images() -> list[dict]
Esempio di output:
[
{
"alt": "logo",
"url": "logo.png",
"line": 19,
"section_idx": 3
}
]
edit_section
Sostituisce il body di una sezione preservando l'heading originale.
edit_section(idx: int, new_content: str) -> None
idx(int): indice della sezione.new_content(str): nuovo contenuto della sezione. Non deve includere l'heading.
Se la sezione conteneva sotto-sezioni (es. una ## Sezione con figlie ###), anche quelle vengono sostituite dal nuovo contenuto. Per modifiche più chirurgiche, agire sulla sotto-sezione specifica.
edit_heading
Modifica titolo e/o livello dell'heading di una sezione.
edit_heading(idx: int, new_title: str, new_level: int | None = None) -> None
idx(int): indice della sezione.new_title(str): nuovo titolo (senza i#).new_level(int | None): nuovo livello (1-6). SeNone, il livello rimane invariato.
Solleva ValueError se new_level è fuori dal range 1-6.
add_section
Inserisce una nuova sezione nel documento.
add_section(
after_idx: int | None,
level: int,
title: str,
content: str = "",
) -> int
after_idx(int | None): la sezione viene inserita dopo questa, alla fine del suo range (incluse le sotto-sezioni). SeNone, viene aggiunta in coda al documento.level(int): livello dell'heading (1-6).title(str): titolo della sezione.content(str): contenuto del body (opzionale).
Restituisce l'indice della nuova sezione.
Dopo un add_section o un remove_section, gli indici delle sezioni successive cambiano. È necessario richiamare get_toc o get_structure per ottenere gli indici aggiornati.
remove_section
Rimuove una sezione dal documento.
remove_section(idx: int, recursive: bool = True) -> None
idx(int): indice della sezione.recursive(bool): seTrue(default), rimuove anche tutte le sotto-sezioni. SeFalse, rimuove solo l'heading e il suo body diretto, lasciando le sotto-sezioni come "orfane" al loro livello originale.
append
Aggiunge testo in coda al documento.
append(text: str) -> None
text(str): testo da appendere. Viene preservato così com'è (markdown raw).
prepend
Aggiunge testo in testa al documento.
prepend(text: str) -> None
text(str): testo da anteporre. Viene preservato così com'è (markdown raw).
replace
Sostituzione globale stringa-per-stringa nel testo raw.
replace(old: str, new: str, count: int = -1) -> int
old(str): stringa da cercare.new(str): stringa di sostituzione.count(int): numero massimo di sostituzioni.-1significa tutte.
Restituisce il numero di sostituzioni effettuate.
Operazione text-based: utile per refactoring rapidi (es. rinominare un termine in tutto il documento) senza passare per la navigazione strutturata.
search
Cerca una stringa in tutto il documento, con contesto circostante e localizzazione per riga e sezione.
search(query: str, context_chars: int = 30) -> list[dict]
query(str): testo da cercare (case-insensitive).context_chars(int): numero di caratteri di contesto prima e dopo il match.
Restituisce una lista di dizionari:
[
{
"line": 6,
"col": 23,
"section_idx": 1,
"snippet": "…Vedi [**il link**](https://…"
}
]
La ricerca non richiede alcun LLM ed è eseguita direttamente sul testo raw.