-- Hermes MCP stdio bridge body. -- SPARK_Mode Off: Ada.Text_IO is not analysable by SPARK. All trust-boundary -- screening happens in proven code (Ada_Medium / Trust_Boundary) before any -- procedure here is called. The global No_Exceptions restriction still applies, -- so I/O is written to avoid raising (End_Of_File guards, bounded Get_Line). with Ada.Text_IO; with Ada.Environment_Variables; package body Hermes_Protocol with SPARK_Mode => Off is -- ------------------------------------------------------------------- -- Buffer append helpers (truncate silently at Max_JSON_Length) procedure Append (Buf : in out JSON_Buffer; S : in String) is Avail : constant Natural := Max_JSON_Length - Buf.Length; N : constant Natural := (if S'Length <= Avail then S'Length else Avail); begin if N > 0 then Buf.Data (Buf.Length + 1 .. Buf.Length + N) := S (S'First .. S'First + N - 1); Buf.Length := Buf.Length + N; end if; end Append; procedure Append (Buf : in out JSON_Buffer; T : in Bounded_Text) is begin Append (Buf, T.Data (1 .. T.Length)); end Append; -- Append a string with the minimal JSON escaping needed for safety. procedure Append_Escaped (Buf : in out JSON_Buffer; S : in String) is begin for I in S'Range loop case S (I) is when '"' => Append (Buf, "\"""); when '\' => Append (Buf, "\\"); when ASCII.LF => Append (Buf, "\n"); when ASCII.CR => Append (Buf, "\r"); when ASCII.HT => Append (Buf, "\t"); when others => Append (Buf, String'(1 => S (I))); end case; end loop; end Append_Escaped; procedure Append_Escaped (Buf : in out JSON_Buffer; T : in Bounded_Text) is begin Append_Escaped (Buf, T.Data (1 .. T.Length)); end Append_Escaped; -- ------------------------------------------------------------------- procedure Read_Message (Buf : out JSON_Buffer; Status : out Operation_Status) is use Ada.Text_IO; Line : String (1 .. Max_JSON_Length); Last : Natural; begin Buf := (Data => (others => ' '), Length => 0); -- Guard EOF so Get_Line cannot raise End_Error under No_Exceptions. if End_Of_File then Status := Error_Invalid_State; -- stdin closed: caller shuts down return; end if; Get_Line (Line, Last); if Last > 0 then Buf.Data (1 .. Last) := Line (1 .. Last); Buf.Length := Last; end if; Status := OK; end Read_Message; procedure Write_Message (Buf : in JSON_Buffer) is use Ada.Text_IO; begin Put (Buf.Data (1 .. Buf.Length)); New_Line; Flush; end Write_Message; -- ------------------------------------------------------------------- -- Minimal `"key": "value"` extractor. Not a full JSON parser: it finds -- the first occurrence of the quoted key, the following colon, then the -- next quoted string, and copies that as the value. procedure Extract_Field (Buf : in JSON_Buffer; Key : in String; Value : out Bounded_Text) is Quoted : constant String := '"' & Key & '"'; I : Natural := 1; Found : Natural := 0; begin Value := (Data => (others => ' '), Length => 0); if Quoted'Length = 0 or else Buf.Length < Quoted'Length then return; end if; -- Locate the key. while I <= Buf.Length - Quoted'Length + 1 loop if Buf.Data (I .. I + Quoted'Length - 1) = Quoted then Found := I + Quoted'Length; exit; end if; I := I + 1; end loop; if Found = 0 then return; end if; -- Skip whitespace and the colon. I := Found; while I <= Buf.Length and then (Buf.Data (I) = ' ' or else Buf.Data (I) = ':' or else Buf.Data (I) = ASCII.HT) loop I := I + 1; end loop; -- Expect an opening quote. if I > Buf.Length or else Buf.Data (I) /= '"' then return; end if; I := I + 1; -- first char of the value -- Copy until the closing quote (honouring backslash escapes minimally). while I <= Buf.Length and then Buf.Data (I) /= '"' loop if Buf.Data (I) = '\' and then I < Buf.Length then I := I + 1; -- take the escaped char literally end if; if Value.Length < Max_Text_Length then Value.Length := Value.Length + 1; Value.Data (Value.Length) := Buf.Data (I); end if; I := I + 1; end loop; end Extract_Field; procedure Extract_Raw_Field (Buf : in JSON_Buffer; Key : in String; Value : out Bounded_Text) is Quoted : constant String := '"' & Key & '"'; I : Natural := 1; Found : Natural := 0; begin Value := Make_Text ("null"); if Quoted'Length = 0 or else Buf.Length < Quoted'Length then return; end if; while I <= Buf.Length - Quoted'Length + 1 loop if Buf.Data (I .. I + Quoted'Length - 1) = Quoted then Found := I + Quoted'Length; exit; end if; I := I + 1; end loop; if Found = 0 then return; end if; I := Found; while I <= Buf.Length and then (Buf.Data (I) = ' ' or else Buf.Data (I) = ':' or else Buf.Data (I) = ASCII.HT) loop I := I + 1; end loop; if I > Buf.Length then return; end if; Value := (Data => (others => ' '), Length => 0); if Buf.Data (I) = '"' then -- Quoted string: copy through the closing quote, inclusive. Value.Length := 1; Value.Data (1) := '"'; I := I + 1; while I <= Buf.Length and then Buf.Data (I) /= '"' loop if Buf.Data (I) = '\' and then I < Buf.Length then if Value.Length < Max_Text_Length then Value.Length := Value.Length + 1; Value.Data (Value.Length) := Buf.Data (I); end if; I := I + 1; end if; if Value.Length < Max_Text_Length then Value.Length := Value.Length + 1; Value.Data (Value.Length) := Buf.Data (I); end if; I := I + 1; end loop; if Value.Length < Max_Text_Length then Value.Length := Value.Length + 1; Value.Data (Value.Length) := '"'; end if; else -- Bare token: copy until a structural delimiter. while I <= Buf.Length and then Buf.Data (I) /= ',' and then Buf.Data (I) /= '}' and then Buf.Data (I) /= ' ' and then Buf.Data (I) /= ASCII.HT loop if Value.Length < Max_Text_Length then Value.Length := Value.Length + 1; Value.Data (Value.Length) := Buf.Data (I); end if; I := I + 1; end loop; end if; if Value.Length = 0 then Value := Make_Text ("null"); end if; end Extract_Raw_Field; -- ------------------------------------------------------------------- procedure Write_Soul_MD (Content : in Bounded_Text; Status : out Operation_Status) is use Ada.Text_IO; F : File_Type; begin Status := Error_Config; if not Ada.Environment_Variables.Exists ("HOME") then return; end if; declare Home : constant String := Ada.Environment_Variables.Value ("HOME"); Path : constant String := Home & "/.hermes/SOUL.md"; begin -- Assumes ~/.hermes exists (Hermes owns that directory). Create (F, Out_File, Path); Put (F, Content.Data (1 .. Content.Length)); Close (F); Status := OK; end; end Write_Soul_MD; -- ------------------------------------------------------------------- -- JSON-RPC response builders procedure Make_Init_Response (Id : in Bounded_Text; Buf : out JSON_Buffer) is begin Buf := (Data => (others => ' '), Length => 0); Append (Buf, "{""jsonrpc"":""2.0"",""id"":"); Append (Buf, Id); Append (Buf, ",""result"":{""protocolVersion"":""2024-11-05"","); Append (Buf, """capabilities"":{""tools"":{}},"); Append (Buf, """serverInfo"":{""name"":""mafiabot_core"",""version"":""gen03""}}}"); end Make_Init_Response; procedure Make_Tools_List_Response (Id : in Bounded_Text; Buf : out JSON_Buffer) is begin Buf := (Data => (others => ' '), Length => 0); Append (Buf, "{""jsonrpc"":""2.0"",""id"":"); Append (Buf, Id); Append (Buf, ",""result"":{""tools"":[{""name"":""infer"","); Append (Buf, """description"":""Run the Gen.03 23-step organ-systems inference " & "cycle over an input and return the enriched cognition context."","); Append (Buf, """inputSchema"":{""type"":""object"",""properties"":" & "{""input"":{""type"":""string"",""description"":" & """The user message to reason over.""}},""required"":[""input""]}"); Append (Buf, "}]}}"); end Make_Tools_List_Response; procedure Make_Tool_Result_Response (Id : in Bounded_Text; Result : in Bounded_Text; Buf : out JSON_Buffer) is begin Buf := (Data => (others => ' '), Length => 0); Append (Buf, "{""jsonrpc"":""2.0"",""id"":"); Append (Buf, Id); Append (Buf, ",""result"":{""content"":[{""type"":""text"",""text"":"""); Append_Escaped (Buf, Result); Append (Buf, """}]}}"); end Make_Tool_Result_Response; procedure Make_Error_Response (Id : in Bounded_Text; Code : in Integer; Message : in String; Buf : out JSON_Buffer) is Code_Img : constant String := Integer'Image (Code); -- Integer'Image leads with a space for non-negatives; strip it. Code_Str : constant String := (if Code_Img'Length > 0 and then Code_Img (Code_Img'First) = ' ' then Code_Img (Code_Img'First + 1 .. Code_Img'Last) else Code_Img); begin Buf := (Data => (others => ' '), Length => 0); Append (Buf, "{""jsonrpc"":""2.0"",""id"":"); Append (Buf, Id); Append (Buf, ",""error"":{""code"":"); Append (Buf, Code_Str); Append (Buf, ",""message"":"""); Append_Escaped (Buf, Message); Append (Buf, """}}"); end Make_Error_Response; end Hermes_Protocol;