using System; using System.Text.RegularExpressions; using Newtonsoft.Json.Linq; namespace Mrleo1nid.SwarmAssistent; /// Parsing and normalization of the JSON patch the model emits inside fenced code blocks. public partial class SwarmAssistentExtension { static readonly Regex JsonFenceRe = new(@"```(?:json)?\s*([\s\S]*?)```", RegexOptions.IgnoreCase | RegexOptions.Compiled); static readonly string[] PatchKeys = [ "prompt", "loras", "width", "height", "steps", "cfg", "seed", "sigma_shift", "sampler", "actions", "search_query", "civitai_query", "use_init_image", "clear_init_image", "init_creativity", "denoise", "use_mask_image", "clear_mask_image", "mask_blur", "mask_grow", "look_at", "vision_from", "vision_slots", "slot_to_init", "slot_to_mask", "snapshot_generate", "select_slot", "aspect", "images", "batch", "vary", "lock_seed", "creativity", "intensity", "complexity", "movement", "clear_prompt_images", "slot_to_prompt_image", "pack", "memories", "memory", "memory_query", "memory_kind", "tag_query", "user_prefs", "inventory_query", "skills", "persona_shelves", "controls", ]; static bool HasValue(JObject obj, string key) { JToken token = obj?[key]; return token is not null && token.Type != JTokenType.Null; } /// Maps legacy/alias patch fields onto their canonical names. Aliases are kept so older consumers still work. public static JObject NormalizePatch(JObject patch) { if (patch is null) { return null; } if (!HasValue(patch, "search_query") && HasValue(patch, "civitai_query")) { patch["search_query"] = patch["civitai_query"]; } if (!HasValue(patch, "init_creativity") && HasValue(patch, "denoise")) { patch["init_creativity"] = patch["denoise"]; } if (!HasValue(patch, "look_at")) { if (HasValue(patch, "vision_from")) { patch["look_at"] = patch["vision_from"]; } else if (HasValue(patch, "vision_slots")) { patch["look_at"] = patch["vision_slots"]; } } return patch; } static JObject TryParsePatch(string reply) { if (string.IsNullOrWhiteSpace(reply)) { return null; } foreach (Match match in JsonFenceRe.Matches(reply)) { string raw = match.Groups[1].Value.Trim(); try { JObject obj = JObject.Parse(raw); if (obj is not null && Array.Exists(PatchKeys, k => obj[k] is not null)) { return NormalizePatch(obj); } } catch { // not json } } return null; } static string ExtractSearchQuery(JObject patch) { if (patch is null) { return null; } string q = (patch["search_query"] ?? patch["civitai_query"])?.ToString()?.Trim(); return string.IsNullOrWhiteSpace(q) ? null : q; } static bool ActionsContain(JObject patch, string action) { if (patch?["actions"] is not JArray acts) { return false; } foreach (JToken a in acts) { if (string.Equals(a?.ToString(), action, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } static string ExtractMemoryQuery(JObject patch) { string q = patch?["memory_query"]?.ToString()?.Trim(); if (!string.IsNullOrWhiteSpace(q)) { return q; } return ActionsContain(patch, "memory_search") ? ExtractSearchQuery(patch) : null; } static string ExtractTagQuery(JObject patch) { string q = patch?["tag_query"]?.ToString()?.Trim(); if (!string.IsNullOrWhiteSpace(q)) { return q; } return ActionsContain(patch, "lookup_tags") ? ExtractSearchQuery(patch) : null; } static string NextToolHop(JObject patch) { if (patch is null) { return null; } if (ActionsContain(patch, "memory_get")) { return "memory_get"; } if (ActionsContain(patch, "memory_search") || !string.IsNullOrWhiteSpace(patch["memory_query"]?.ToString())) { return "memory_search"; } if (ActionsContain(patch, "lookup_tags") || !string.IsNullOrWhiteSpace(patch["tag_query"]?.ToString())) { return "lookup_tags"; } if (ActionsContain(patch, "list_inventory") || !string.IsNullOrWhiteSpace(patch["inventory_query"]?.ToString())) { return "list_inventory"; } if (ActionsContain(patch, "skill_load")) { return "skill_load"; } if (ActionsContain(patch, "persona_read")) { return "persona_read"; } if (ActionsContain(patch, "search_civitai") || !string.IsNullOrWhiteSpace(ExtractSearchQuery(patch))) { return "civitai"; } return null; } static bool WantsCivitaiSearch(JObject patch) => NextToolHop(patch) == "civitai"; }