Mercurial
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 260:1f9877b637e9 | 261:b401627fc49e |
|---|---|
| 14 | 14 |
| 15 #define CONVERSATION_TITLE_MAX 200 | 15 #define CONVERSATION_TITLE_MAX 200 |
| 16 #define CONVERSATION_PROMPT_MAX (32 * 1024) | 16 #define CONVERSATION_PROMPT_MAX (32 * 1024) |
| 17 #define CONVERSATION_RESPONSE_MAX (256 * 1024) | 17 #define CONVERSATION_RESPONSE_MAX (256 * 1024) |
| 18 #define CONVERSATION_HISTORY_MAX (512 * 1024) | 18 #define CONVERSATION_HISTORY_MAX (512 * 1024) |
| 19 #define CONVERSATION_CUSTOM_EVENT_MAX (3 * 1024) | |
| 20 #define CONVERSATION_EVENT_NAME_MAX 64 | |
| 19 #define CONVERSATION_ACTIVE_MAX 4 | 21 #define CONVERSATION_ACTIVE_MAX 4 |
| 20 #define CONVERSATION_TURNS_PER_MINUTE 60 | 22 #define CONVERSATION_TURNS_PER_MINUTE 60 |
| 21 | 23 |
| 22 static Conversation_Store *g_conversation_store = NULL; | 24 static Conversation_Store *g_conversation_store = NULL; |
| 23 static Inference_Bridge *g_inference_bridge = NULL; | 25 static Inference_Bridge *g_inference_bridge = NULL; |
| 42 char error_message[512]; | 44 char error_message[512]; |
| 43 struct Pending_Turn *p_next; | 45 struct Pending_Turn *p_next; |
| 44 } Pending_Turn; | 46 } Pending_Turn; |
| 45 | 47 |
| 46 static Pending_Turn *g_pending_turns = NULL; | 48 static Pending_Turn *g_pending_turns = NULL; |
| 49 | |
| 50 static boolean Conversation_API_Is_Handled_Event(const char *type) | |
| 51 { | |
| 52 return | |
| 53 strcmp(type, "ready") == 0 || | |
| 54 strcmp(type, "turn.accepted") == 0 || | |
| 55 strcmp(type, "assistant.delta") == 0 || | |
| 56 strcmp(type, "assistant.completed") == 0 || | |
| 57 strcmp(type, "assistant.usage") == 0 || | |
| 58 strcmp(type, "turn.error") == 0 || | |
| 59 strcmp(type, "turn.done") == 0; | |
| 60 } | |
| 61 | |
| 62 static boolean Conversation_API_Is_Safe_Event_Name(const char *type) | |
| 63 { | |
| 64 if (!type || type[0] == '\0') | |
| 65 return FALSE; | |
| 66 size_t length = strlen(type); | |
| 67 if (length > CONVERSATION_EVENT_NAME_MAX) | |
| 68 return FALSE; | |
| 69 for (size_t i = 0; i < length; i++) | |
| 70 { | |
| 71 char character = type[i]; | |
| 72 if (!( | |
| 73 (character >= 'a' && character <= 'z') || | |
| 74 (character >= 'A' && character <= 'Z') || | |
| 75 (character >= '0' && character <= '9') || | |
| 76 character == '.' || | |
| 77 character == '_' || | |
| 78 character == '-')) | |
| 79 return FALSE; | |
| 80 } | |
| 81 return TRUE; | |
| 82 } | |
| 47 | 83 |
| 48 static boolean Conversation_API_Acquire_Turn_Slot(void) | 84 static boolean Conversation_API_Acquire_Turn_Slot(void) |
| 49 { | 85 { |
| 50 time_t now = time(NULL); | 86 time_t now = time(NULL); |
| 51 pthread_mutex_lock(&g_admission_mutex); | 87 pthread_mutex_lock(&g_admission_mutex); |
| 311 pthread_mutex_lock(&g_pending_mutex); | 347 pthread_mutex_lock(&g_pending_mutex); |
| 312 Pending_Turn *p_turn = Conversation_API_Find_Pending(p_event->request_id); | 348 Pending_Turn *p_turn = Conversation_API_Find_Pending(p_event->request_id); |
| 313 if (!p_turn) | 349 if (!p_turn) |
| 314 { | 350 { |
| 315 pthread_mutex_unlock(&g_pending_mutex); | 351 pthread_mutex_unlock(&g_pending_mutex); |
| 352 return; | |
| 353 } | |
| 354 | |
| 355 if (!Conversation_API_Is_Handled_Event(p_event->type)) | |
| 356 { | |
| 357 if (!Conversation_API_Is_Safe_Event_Name(p_event->type) || | |
| 358 !p_event->raw_json || | |
| 359 p_event->raw_json_length == 0 || | |
| 360 p_event->raw_json_length > CONVERSATION_CUSTOM_EVENT_MAX) | |
| 361 { | |
| 362 p_turn->failed = TRUE; | |
| 363 snprintf( | |
| 364 p_turn->error_message, | |
| 365 sizeof(p_turn->error_message), | |
| 366 "Invalid custom inference event"); | |
| 367 Conversation_API_Send_Event( | |
| 368 p_turn, | |
| 369 "turn.error", | |
| 370 "{\"code\":\"invalid_custom_event\"," | |
| 371 "\"message\":\"Invalid custom inference event\"}"); | |
| 372 Conversation_API_Finalize_Pending(p_turn); | |
| 373 } | |
| 374 else if (Conversation_API_Send_Event( | |
| 375 p_turn, p_event->type, p_event->raw_json) < 0) | |
| 376 { | |
| 377 p_turn->aborted = TRUE; | |
| 378 should_abort = TRUE; | |
| 379 snprintf( | |
| 380 abort_request_id, | |
| 381 sizeof(abort_request_id), | |
| 382 "%s", | |
| 383 p_turn->request_id); | |
| 384 snprintf( | |
| 385 abort_conversation_id, | |
| 386 sizeof(abort_conversation_id), | |
| 387 "%s", | |
| 388 p_turn->conversation_id); | |
| 389 } | |
| 390 pthread_mutex_unlock(&g_pending_mutex); | |
| 391 if (should_abort) | |
| 392 Inference_Bridge_Abort_Turn( | |
| 393 g_inference_bridge, abort_request_id, abort_conversation_id); | |
| 316 return; | 394 return; |
| 317 } | 395 } |
| 318 | 396 |
| 319 size_t event_text_length = | 397 size_t event_text_length = |
| 320 strlen(p_event->delta ? p_event->delta : "") + | 398 strlen(p_event->delta ? p_event->delta : "") + |