diff mrjunejune/conversation_api.c @ 261:b401627fc49e

Add JRPG mock flows and interactive previews Add scripted mock SSE commands, custom event forwarding, animated chat turns, full-height message navigation, and a cyberpunk resume dossier. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Wed, 05 Aug 2026 20:38:32 -0700
parents 1f9877b637e9
children 04fee26ecce0
line wrap: on
line diff
--- a/mrjunejune/conversation_api.c	Wed Aug 05 09:19:41 2026 -0700
+++ b/mrjunejune/conversation_api.c	Wed Aug 05 20:38:32 2026 -0700
@@ -16,6 +16,8 @@
 #define CONVERSATION_PROMPT_MAX (32 * 1024)
 #define CONVERSATION_RESPONSE_MAX (256 * 1024)
 #define CONVERSATION_HISTORY_MAX (512 * 1024)
+#define CONVERSATION_CUSTOM_EVENT_MAX (3 * 1024)
+#define CONVERSATION_EVENT_NAME_MAX 64
 #define CONVERSATION_ACTIVE_MAX 4
 #define CONVERSATION_TURNS_PER_MINUTE 60
 
@@ -45,6 +47,40 @@
 
 static Pending_Turn *g_pending_turns = NULL;
 
+static boolean Conversation_API_Is_Handled_Event(const char *type)
+{
+  return
+      strcmp(type, "ready") == 0 ||
+      strcmp(type, "turn.accepted") == 0 ||
+      strcmp(type, "assistant.delta") == 0 ||
+      strcmp(type, "assistant.completed") == 0 ||
+      strcmp(type, "assistant.usage") == 0 ||
+      strcmp(type, "turn.error") == 0 ||
+      strcmp(type, "turn.done") == 0;
+}
+
+static boolean Conversation_API_Is_Safe_Event_Name(const char *type)
+{
+  if (!type || type[0] == '\0')
+    return FALSE;
+  size_t length = strlen(type);
+  if (length > CONVERSATION_EVENT_NAME_MAX)
+    return FALSE;
+  for (size_t i = 0; i < length; i++)
+  {
+    char character = type[i];
+    if (!(
+            (character >= 'a' && character <= 'z') ||
+            (character >= 'A' && character <= 'Z') ||
+            (character >= '0' && character <= '9') ||
+            character == '.' ||
+            character == '_' ||
+            character == '-'))
+      return FALSE;
+  }
+  return TRUE;
+}
+
 static boolean Conversation_API_Acquire_Turn_Slot(void)
 {
   time_t now = time(NULL);
@@ -316,6 +352,48 @@
     return;
   }
 
+  if (!Conversation_API_Is_Handled_Event(p_event->type))
+  {
+    if (!Conversation_API_Is_Safe_Event_Name(p_event->type) ||
+        !p_event->raw_json ||
+        p_event->raw_json_length == 0 ||
+        p_event->raw_json_length > CONVERSATION_CUSTOM_EVENT_MAX)
+    {
+      p_turn->failed = TRUE;
+      snprintf(
+          p_turn->error_message,
+          sizeof(p_turn->error_message),
+          "Invalid custom inference event");
+      Conversation_API_Send_Event(
+          p_turn,
+          "turn.error",
+          "{\"code\":\"invalid_custom_event\","
+          "\"message\":\"Invalid custom inference event\"}");
+      Conversation_API_Finalize_Pending(p_turn);
+    }
+    else if (Conversation_API_Send_Event(
+                 p_turn, p_event->type, p_event->raw_json) < 0)
+    {
+      p_turn->aborted = TRUE;
+      should_abort = TRUE;
+      snprintf(
+          abort_request_id,
+          sizeof(abort_request_id),
+          "%s",
+          p_turn->request_id);
+      snprintf(
+          abort_conversation_id,
+          sizeof(abort_conversation_id),
+          "%s",
+          p_turn->conversation_id);
+    }
+    pthread_mutex_unlock(&g_pending_mutex);
+    if (should_abort)
+      Inference_Bridge_Abort_Turn(
+          g_inference_bridge, abort_request_id, abort_conversation_id);
+    return;
+  }
+
   size_t event_text_length =
       strlen(p_event->delta ? p_event->delta : "") +
       strlen(p_event->content ? p_event->content : "") +