Mercurial
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) |