comparison mrjunejune/inference_bridge.c @ 265:056790c4fb0d

add role-aware Epi assistant prompts Add verified June knowledge, guest/member/admin Copilot profiles, profile-isolated session recovery, animated Epi greetings, and a single authoritative runtime config workflow for inference. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <me@mrjunejune.com>
date Fri, 07 Aug 2026 10:50:30 -0700
parents b401627fc49e
children
comparison
equal deleted inserted replaced
264:04fee26ecce0 265:056790c4fb0d
185 fflush(p_bridge->p_commands) == 0; 185 fflush(p_bridge->p_commands) == 0;
186 pthread_mutex_unlock(&p_bridge->write_mutex); 186 pthread_mutex_unlock(&p_bridge->write_mutex);
187 return success; 187 return success;
188 } 188 }
189 189
190 static const char *Inference_Bridge_Profile_Name(
191 Inference_Prompt_Profile profile)
192 {
193 switch (profile)
194 {
195 case INFERENCE_PROMPT_PROFILE_PUBLIC_VISITOR:
196 return "public_visitor";
197 case INFERENCE_PROMPT_PROFILE_INVITED_FRIEND:
198 return "invited_friend";
199 case INFERENCE_PROMPT_PROFILE_JUNE_ADMIN:
200 return "june_admin";
201 }
202 return NULL;
203 }
204
190 static boolean Inference_Bridge_Command( 205 static boolean Inference_Bridge_Command(
191 Inference_Bridge *p_bridge, 206 Inference_Bridge *p_bridge,
192 const char *command, 207 const char *command,
193 const char *request_id, 208 const char *request_id,
194 const char *conversation_id, 209 const char *conversation_id,
195 const char *prompt) 210 const char *prompt,
211 const char *prompt_profile,
212 uint32 prompt_version,
213 uint32 knowledge_version)
196 { 214 {
197 if (!command || !request_id) 215 if (!command || !request_id)
198 return FALSE; 216 return FALSE;
199 size_t input_length = 217 size_t input_length =
200 strlen(command) + strlen(request_id) + 218 strlen(command) + strlen(request_id) +
201 strlen(conversation_id ? conversation_id : "") + 219 strlen(conversation_id ? conversation_id : "") +
202 strlen(prompt ? prompt : ""); 220 strlen(prompt ? prompt : "") +
221 strlen(prompt_profile ? prompt_profile : "");
203 if (input_length > (((size_t)-1) - 4096) / 12) 222 if (input_length > (((size_t)-1) - 4096) / 12)
204 return FALSE; 223 return FALSE;
205 Dowa_Arena *p_arena = Dowa_Arena_Create(input_length * 12 + 4096); 224 Dowa_Arena *p_arena = Dowa_Arena_Create(input_length * 12 + 4096);
206 if (!p_arena) 225 if (!p_arena)
207 return FALSE; 226 return FALSE;
210 char *escaped_conversation = Dowa_JSON_Escape_String( 229 char *escaped_conversation = Dowa_JSON_Escape_String(
211 conversation_id ? conversation_id : "", 0, p_arena); 230 conversation_id ? conversation_id : "", 0, p_arena);
212 char *escaped_prompt = prompt 231 char *escaped_prompt = prompt
213 ? Dowa_JSON_Escape_String(prompt, 0, p_arena) 232 ? Dowa_JSON_Escape_String(prompt, 0, p_arena)
214 : NULL; 233 : NULL;
234 char *escaped_profile = prompt_profile
235 ? Dowa_JSON_Escape_String(prompt_profile, 0, p_arena)
236 : NULL;
215 if (!escaped_command || !escaped_request || !escaped_conversation || 237 if (!escaped_command || !escaped_request || !escaped_conversation ||
216 (prompt && !escaped_prompt)) 238 (prompt && !escaped_prompt) ||
239 (prompt_profile && !escaped_profile))
217 { 240 {
218 Dowa_Arena_Free(p_arena); 241 Dowa_Arena_Free(p_arena);
219 return FALSE; 242 return FALSE;
220 } 243 }
221 size_t capacity = 244 size_t capacity =
222 strlen(escaped_command) + strlen(escaped_request) + 245 strlen(escaped_command) + strlen(escaped_request) +
223 strlen(escaped_conversation) + 246 strlen(escaped_conversation) +
224 (escaped_prompt ? strlen(escaped_prompt) : 0) + 160; 247 (escaped_prompt ? strlen(escaped_prompt) : 0) +
248 (escaped_profile ? strlen(escaped_profile) : 0) + 256;
225 char *payload = Dowa_Arena_Allocate(p_arena, capacity); 249 char *payload = Dowa_Arena_Allocate(p_arena, capacity);
226 if (!payload) 250 if (!payload)
227 { 251 {
228 Dowa_Arena_Free(p_arena); 252 Dowa_Arena_Free(p_arena);
229 return FALSE; 253 return FALSE;
230 } 254 }
231 if (escaped_prompt) 255 if (escaped_prompt && escaped_profile)
232 { 256 {
233 snprintf( 257 snprintf(
234 payload, 258 payload,
235 capacity, 259 capacity,
236 "{\"command\":\"%s\",\"request_id\":\"%s\"," 260 "{\"command\":\"%s\",\"request_id\":\"%s\","
237 "\"conversation_id\":\"%s\",\"prompt\":\"%s\"}", 261 "\"conversation_id\":\"%s\",\"prompt\":\"%s\","
262 "\"prompt_profile\":\"%s\",\"prompt_version\":%u,"
263 "\"knowledge_version\":%u}",
238 escaped_command, 264 escaped_command,
239 escaped_request, 265 escaped_request,
240 escaped_conversation, 266 escaped_conversation,
241 escaped_prompt); 267 escaped_prompt,
268 escaped_profile,
269 prompt_version,
270 knowledge_version);
242 } 271 }
243 else 272 else
244 { 273 {
245 snprintf( 274 snprintf(
246 payload, 275 payload,
302 { 331 {
303 if (!p_bridge) 332 if (!p_bridge)
304 return; 333 return;
305 if (atomic_load(&p_bridge->running) && p_bridge->p_commands) 334 if (atomic_load(&p_bridge->running) && p_bridge->p_commands)
306 Inference_Bridge_Command( 335 Inference_Bridge_Command(
307 p_bridge, "shutdown", "server-shutdown", "", NULL); 336 p_bridge, "shutdown", "server-shutdown", "", NULL, NULL, 0, 0);
308 if (p_bridge->p_commands) 337 if (p_bridge->p_commands)
309 { 338 {
310 fclose(p_bridge->p_commands); 339 fclose(p_bridge->p_commands);
311 p_bridge->p_commands = NULL; 340 p_bridge->p_commands = NULL;
312 } 341 }
463 492
464 boolean Inference_Bridge_Start_Turn( 493 boolean Inference_Bridge_Start_Turn(
465 Inference_Bridge *p_bridge, 494 Inference_Bridge *p_bridge,
466 const char *request_id, 495 const char *request_id,
467 const char *conversation_id, 496 const char *conversation_id,
468 const char *prompt) 497 const char *prompt,
469 { 498 Inference_Prompt_Profile prompt_profile,
470 return Inference_Bridge_Command( 499 uint32 prompt_version,
471 p_bridge, "turn.start", request_id, conversation_id, prompt); 500 uint32 knowledge_version,
501 const Inference_Bridge_History_Message *p_history,
502 uint32 history_count)
503 {
504 const char *profile_name = Inference_Bridge_Profile_Name(prompt_profile);
505 if (!profile_name || prompt_version == 0 || knowledge_version == 0)
506 return FALSE;
507 if (history_count > INFERENCE_BRIDGE_HISTORY_MAX)
508 return FALSE;
509 /* Validate every history entry before touching the wire. */
510 for (uint32 i = 0; i < history_count; i++)
511 {
512 const Inference_Bridge_History_Message *m = &p_history[i];
513 if (!m->role || !m->content)
514 return FALSE;
515 if (strcmp(m->role, "user") != 0 && strcmp(m->role, "assistant") != 0)
516 return FALSE;
517 }
518
519 if (!request_id)
520 return FALSE;
521
522 /* Compute raw byte total to size the arena (12x expansion factor). */
523 size_t input_length =
524 strlen("turn.start") + strlen(request_id) +
525 strlen(conversation_id ? conversation_id : "") +
526 strlen(prompt ? prompt : "") +
527 strlen(profile_name);
528 for (uint32 i = 0; i < history_count; i++)
529 {
530 input_length += strlen(p_history[i].role) + strlen(p_history[i].content);
531 }
532 if (input_length > (((size_t)-1) - 4096) / 12)
533 return FALSE;
534
535 Dowa_Arena *p_arena = Dowa_Arena_Create(input_length * 12 + 4096);
536 if (!p_arena)
537 return FALSE;
538
539 char *escaped_command = Dowa_JSON_Escape_String("turn.start", 0, p_arena);
540 char *escaped_request = Dowa_JSON_Escape_String(request_id, 0, p_arena);
541 char *escaped_conversation = Dowa_JSON_Escape_String(
542 conversation_id ? conversation_id : "", 0, p_arena);
543 char *escaped_prompt = prompt
544 ? Dowa_JSON_Escape_String(prompt, 0, p_arena)
545 : NULL;
546 char *escaped_profile = Dowa_JSON_Escape_String(profile_name, 0, p_arena);
547
548 if (!escaped_command || !escaped_request || !escaped_conversation ||
549 (prompt && !escaped_prompt) || !escaped_profile)
550 {
551 Dowa_Arena_Free(p_arena);
552 return FALSE;
553 }
554
555 /* Escape history roles and contents. */
556 char *escaped_hist_role[INFERENCE_BRIDGE_HISTORY_MAX];
557 char *escaped_hist_content[INFERENCE_BRIDGE_HISTORY_MAX];
558 for (uint32 i = 0; i < history_count; i++)
559 {
560 escaped_hist_role[i] =
561 Dowa_JSON_Escape_String(p_history[i].role, 0, p_arena);
562 escaped_hist_content[i] =
563 Dowa_JSON_Escape_String(p_history[i].content, 0, p_arena);
564 if (!escaped_hist_role[i] || !escaped_hist_content[i])
565 {
566 Dowa_Arena_Free(p_arena);
567 return FALSE;
568 }
569 }
570
571 /* Compute payload capacity. */
572 size_t capacity =
573 strlen(escaped_command) + strlen(escaped_request) +
574 strlen(escaped_conversation) +
575 (escaped_prompt ? strlen(escaped_prompt) : 0) +
576 strlen(escaped_profile) + 256;
577 for (uint32 i = 0; i < history_count; i++)
578 {
579 capacity +=
580 strlen(escaped_hist_role[i]) + strlen(escaped_hist_content[i]) + 32;
581 }
582
583 char *payload = Dowa_Arena_Allocate(p_arena, capacity);
584 if (!payload)
585 {
586 Dowa_Arena_Free(p_arena);
587 return FALSE;
588 }
589
590 /* Write the base turn.start fields. */
591 int written = snprintf(
592 payload,
593 capacity,
594 "{\"command\":\"%s\",\"request_id\":\"%s\","
595 "\"conversation_id\":\"%s\",\"prompt\":\"%s\","
596 "\"prompt_profile\":\"%s\",\"prompt_version\":%u,"
597 "\"knowledge_version\":%u,\"history\":[",
598 escaped_command,
599 escaped_request,
600 escaped_conversation,
601 escaped_prompt ? escaped_prompt : "",
602 escaped_profile,
603 prompt_version,
604 knowledge_version);
605 if (written <= 0 || (size_t)written >= capacity)
606 {
607 Dowa_Arena_Free(p_arena);
608 return FALSE;
609 }
610 size_t offset = (size_t)written;
611
612 /* Append history entries. */
613 for (uint32 i = 0; i < history_count; i++)
614 {
615 int entry_written = snprintf(
616 payload + offset,
617 capacity - offset,
618 "%s{\"role\":\"%s\",\"content\":\"%s\"}",
619 i == 0 ? "" : ",",
620 escaped_hist_role[i],
621 escaped_hist_content[i]);
622 if (entry_written <= 0 || offset + (size_t)entry_written >= capacity)
623 {
624 Dowa_Arena_Free(p_arena);
625 return FALSE;
626 }
627 offset += (size_t)entry_written;
628 }
629
630 /* Close the history array and the outer object. */
631 if (offset + 2 >= capacity)
632 {
633 Dowa_Arena_Free(p_arena);
634 return FALSE;
635 }
636 payload[offset++] = ']';
637 payload[offset++] = '}';
638 payload[offset] = '\0';
639
640 boolean success = Inference_Bridge_Write(p_bridge, payload);
641 Dowa_Arena_Free(p_arena);
642 return success;
472 } 643 }
473 644
474 boolean Inference_Bridge_Abort_Turn( 645 boolean Inference_Bridge_Abort_Turn(
475 Inference_Bridge *p_bridge, 646 Inference_Bridge *p_bridge,
476 const char *request_id, 647 const char *request_id,
477 const char *conversation_id) 648 const char *conversation_id)
478 { 649 {
479 return Inference_Bridge_Command( 650 return Inference_Bridge_Command(
480 p_bridge, "turn.abort", request_id, conversation_id, NULL); 651 p_bridge, "turn.abort", request_id, conversation_id, NULL, NULL, 0, 0);
481 } 652 }
482 653
483 boolean Inference_Bridge_Delete_Conversation( 654 boolean Inference_Bridge_Delete_Conversation(
484 Inference_Bridge *p_bridge, 655 Inference_Bridge *p_bridge,
485 const char *request_id, 656 const char *request_id,
486 const char *conversation_id) 657 const char *conversation_id)
487 { 658 {
488 return Inference_Bridge_Command( 659 return Inference_Bridge_Command(
489 p_bridge, "conversation.delete", request_id, conversation_id, NULL); 660 p_bridge,
661 "conversation.delete",
662 request_id,
663 conversation_id,
664 NULL,
665 NULL,
666 0,
667 0);
490 } 668 }
491 669
492 void Inference_Bridge_Destroy(Inference_Bridge *p_bridge) 670 void Inference_Bridge_Destroy(Inference_Bridge *p_bridge)
493 { 671 {
494 if (!p_bridge) 672 if (!p_bridge)