using System.Xml.Linq;namespace Lilith.Agent.ScenarioSystem;internal sealed record ScenarioXml( string Id, string Title, bool RunOnBoot, IReadOnlyList<ScenarioStepXml> Steps);internal abstract record ScenarioStepXml(string Kind);/// <summary>Chat step — same idea as TestManager <Step> prompts.</summary>internal sealed record ChatStepXml(string Prompt, string? ExpectContains, string? ExpectEquals) : ScenarioStepXml("chat");internal static class ScenarioXmlParser{ public static ScenarioXml Parse(string xml) { var doc = XDocument.Parse(xml); var root = doc.Root ?? throw new InvalidOperationException("Scenario XML missing root element."); if (!string.Equals(root.Name.LocalName, "Scenario", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("Scenario XML root must be <Scenario>."); string id = root.Attribute("id")?.Value?.Trim() ?? ""; string title = root.Attribute("title")?.Value?.Trim() ?? id; bool runOnBoot = string.Equals(root.Attribute("run_on")?.Value, "boot", StringComparison.OrdinalIgnoreCase); if (string.IsNullOrWhiteSpace(id)) throw new InvalidOperationException("<Scenario> requires id attribute."); var steps = new List<ScenarioStepXml>(); foreach (var el in root.Elements()) { if (!el.Name.LocalName.Equals("Step", StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException($"Scenario XML only supports <Step> elements (found <{el.Name}>)."); string prompt = el.Value.Trim(); if (string.IsNullOrWhiteSpace(prompt)) throw new InvalidOperationException("<Step> must not be empty."); steps.Add(new ChatStepXml( prompt, el.Attribute("expect_contains")?.Value?.Trim(), el.Attribute("expect_equals")?.Value?.Trim())); } if (steps.Count == 0) throw new InvalidOperationException("Scenario has no steps."); return new ScenarioXml(id, title, runOnBoot, steps); }}
Documentation
Lilith (Version 3)
Version 3 Lilith agent library. Entry point: Lilith.cs with partials under Boot/, Chat/, Ollama/, and SystemPrompt/.
Build the solution or run python ../Build-v3.py from this tree.