Mercurial
diff infinite_canvas/agent_service_copilot.c @ 280:49e9e591c9bb
Add persistent dictation, prewarmed WebRTC speech input, Copilot SDK routing, animated conversation lifecycle controls, parking, and architecture coverage.
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 18 Aug 2026 19:14:53 -0700 |
| parents | |
| children | c57149ad216e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/infinite_canvas/agent_service_copilot.c Tue Aug 18 19:14:53 2026 -0700 @@ -0,0 +1,276 @@ +#include "infinite_canvas/agent_service.h" + +#include "mrjunejune/inference_bridge.h" + +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define CANVAS_AGENT_WARM_SESSION_COUNT 4 + +typedef struct { + pthread_mutex_t mutex; + Canvas_Agent_Service *p_service; + Inference_Bridge *p_bridge; + uint32 request_sequence; + uint32 warmed_sessions; + char request_id[64]; + char turn_content[CANVAS_AGENT_RESPONSE_CAPACITY]; + char turn_error[512]; + char turn_prompt[ + CANVAS_CONTEXT_MAX_LENGTH + CANVAS_ENTITY_TEXT_CAPACITY + 512]; +} Canvas_Agent_Copilot; + +static boolean Canvas_Agent_Event_Matches( + const Canvas_Agent_Copilot *p_copilot, + const Inference_Event *p_event) +{ + return p_event->request_id && + strcmp(p_copilot->request_id, p_event->request_id) == 0; +} + +static void Canvas_Agent_Handle_Event( + const Inference_Event *p_event, + void *p_user_data) +{ + Canvas_Agent_Copilot *p_copilot = + (Canvas_Agent_Copilot *)p_user_data; + if (!p_copilot || !p_event) return; + + pthread_mutex_lock(&p_copilot->mutex); + if (strcmp(p_event->type, "bridge.closed") == 0) { + if (p_copilot->p_service->status == CANVAS_AGENT_WORKING) { + snprintf( + p_copilot->p_service->error, + sizeof(p_copilot->p_service->error), + "%s", + p_event->error_message && p_event->error_message[0] + ? p_event->error_message + : "Copilot orchestration sidecar closed"); + p_copilot->p_service->status = CANVAS_AGENT_ERROR; + } + pthread_mutex_unlock(&p_copilot->mutex); + return; + } + if (strcmp(p_event->type, "session.warmed") == 0 && + p_event->request_id && + strncmp(p_event->request_id, "canvas-warm-", 12) == 0) { + p_copilot->warmed_sessions++; + pthread_mutex_unlock(&p_copilot->mutex); + return; + } + if (!Canvas_Agent_Event_Matches(p_copilot, p_event)) { + pthread_mutex_unlock(&p_copilot->mutex); + return; + } + + if (strcmp(p_event->type, "assistant.completed") == 0) { + snprintf( + p_copilot->turn_content, + sizeof(p_copilot->turn_content), + "%s", + p_event->content ? p_event->content : ""); + } else if (strcmp(p_event->type, "turn.error") == 0) { + snprintf( + p_copilot->turn_error, + sizeof(p_copilot->turn_error), + "%s", + p_event->error_message && p_event->error_message[0] + ? p_event->error_message + : "Copilot orchestration failed"); + } else if (strcmp(p_event->type, "turn.done") == 0) { + if (p_event->failed || p_copilot->turn_error[0]) { + snprintf( + p_copilot->p_service->error, + sizeof(p_copilot->p_service->error), + "%s", + p_copilot->turn_error[0] + ? p_copilot->turn_error + : "Copilot orchestration failed"); + p_copilot->p_service->status = CANVAS_AGENT_ERROR; + } else if (p_copilot->turn_content[0]) { + snprintf( + p_copilot->p_service->response, + sizeof(p_copilot->p_service->response), + "%s", + p_copilot->turn_content); + p_copilot->p_service->status = CANVAS_AGENT_READY; + } else { + snprintf( + p_copilot->p_service->error, + sizeof(p_copilot->p_service->error), + "Copilot returned an empty response"); + p_copilot->p_service->status = CANVAS_AGENT_ERROR; + } + } + pthread_mutex_unlock(&p_copilot->mutex); +} + +boolean Canvas_Agent_Service_Init( + Canvas_Agent_Service *p_service, + Dowa_Arena *p_arena) +{ + memset(p_service, 0, sizeof(*p_service)); + p_service->p_arena = p_arena; + const char *p_sidecar = + getenv("INFINITE_CANVAS_COPILOT_SIDECAR_PATH"); + const char *p_cli = + getenv("INFINITE_CANVAS_COPILOT_CLI_PATH"); + if (!p_sidecar || !p_sidecar[0] || !p_cli || !p_cli[0]) { + return FALSE; + } + + Canvas_Agent_Copilot *p_copilot = (Canvas_Agent_Copilot *) + Dowa_Arena_Allocate(p_arena, sizeof(*p_copilot)); + if (!p_copilot) return FALSE; + memset(p_copilot, 0, sizeof(*p_copilot)); + if (pthread_mutex_init(&p_copilot->mutex, NULL) != 0) { + return FALSE; + } + p_copilot->p_service = p_service; + p_copilot->p_bridge = Inference_Bridge_Create( + p_sidecar, + p_cli, + Canvas_Agent_Handle_Event, + p_copilot); + if (!p_copilot->p_bridge || + !Inference_Bridge_Start(p_copilot->p_bridge)) { + if (p_copilot->p_bridge) { + Inference_Bridge_Destroy(p_copilot->p_bridge); + } + pthread_mutex_destroy(&p_copilot->mutex); + return FALSE; + } + const char *warm_sessions[CANVAS_AGENT_WARM_SESSION_COUNT] = { + "infinite-canvas-orchestrator", + "infinite-canvas-worker-1", + "infinite-canvas-worker-2", + "infinite-canvas-worker-3", + }; + for (uint32 index = 0; + index < CANVAS_AGENT_WARM_SESSION_COUNT; + index++) { + char request_id[32]; + snprintf( + request_id, + sizeof(request_id), + "canvas-warm-%u", + index); + if (!Inference_Bridge_Warm_Conversation( + p_copilot->p_bridge, + request_id, + warm_sessions[index], + INFERENCE_PROMPT_PROFILE_CANVAS_ORCHESTRATOR, + 1, + 1, + index == 0 ? TRUE : FALSE)) { + Inference_Bridge_Destroy(p_copilot->p_bridge); + pthread_mutex_destroy(&p_copilot->mutex); + return FALSE; + } + } + for (int32 attempt = 0; attempt < 2000; attempt++) { + pthread_mutex_lock(&p_copilot->mutex); + boolean ready = + p_copilot->warmed_sessions == CANVAS_AGENT_WARM_SESSION_COUNT; + pthread_mutex_unlock(&p_copilot->mutex); + if (ready) break; + usleep(10000); + } + p_service->p_native = p_copilot; + p_service->status = CANVAS_AGENT_IDLE; + return TRUE; +} + +boolean Canvas_Agent_Service_Submit( + Canvas_Agent_Service *p_service, + const char *p_prompt, + const char *p_context) +{ + Canvas_Agent_Copilot *p_copilot = + (Canvas_Agent_Copilot *)p_service->p_native; + if (!p_copilot || !p_prompt || !p_prompt[0]) return FALSE; + + pthread_mutex_lock(&p_copilot->mutex); + if (p_service->status == CANVAS_AGENT_WORKING) { + pthread_mutex_unlock(&p_copilot->mutex); + return FALSE; + } + p_copilot->request_sequence++; + snprintf( + p_copilot->request_id, + sizeof(p_copilot->request_id), + "canvas-%u", + p_copilot->request_sequence); + snprintf( + p_copilot->turn_prompt, + sizeof(p_copilot->turn_prompt), + "Visible canvas context:\n%s\n\nNew user thought:\n%s", + p_context ? p_context : "", + p_prompt); + p_copilot->turn_content[0] = '\0'; + p_copilot->turn_error[0] = '\0'; + p_service->response[0] = '\0'; + p_service->error[0] = '\0'; + p_service->status = CANVAS_AGENT_WORKING; + char request_id[sizeof(p_copilot->request_id)]; + snprintf(request_id, sizeof(request_id), "%s", p_copilot->request_id); + pthread_mutex_unlock(&p_copilot->mutex); + + if (!Inference_Bridge_Start_Turn( + p_copilot->p_bridge, + request_id, + "infinite-canvas-orchestrator", + p_copilot->turn_prompt, + INFERENCE_PROMPT_PROFILE_CANVAS_ORCHESTRATOR, + 1, + 1, + NULL, + 0)) { + pthread_mutex_lock(&p_copilot->mutex); + snprintf( + p_service->error, + sizeof(p_service->error), + "Unable to start the Copilot orchestration turn"); + p_service->status = CANVAS_AGENT_ERROR; + pthread_mutex_unlock(&p_copilot->mutex); + return FALSE; + } + return TRUE; +} + +Canvas_Agent_Status Canvas_Agent_Service_Poll( + Canvas_Agent_Service *p_service, + char *p_response, + size_t response_capacity, + char *p_error, + size_t error_capacity) +{ + Canvas_Agent_Copilot *p_copilot = + (Canvas_Agent_Copilot *)p_service->p_native; + if (!p_copilot) return CANVAS_AGENT_ERROR; + pthread_mutex_lock(&p_copilot->mutex); + Canvas_Agent_Status status = p_service->status; + if (status == CANVAS_AGENT_READY) { + snprintf(p_response, response_capacity, "%s", p_service->response); + p_service->status = CANVAS_AGENT_IDLE; + } else if (status == CANVAS_AGENT_ERROR) { + snprintf(p_error, error_capacity, "%s", p_service->error); + p_service->status = CANVAS_AGENT_IDLE; + } + pthread_mutex_unlock(&p_copilot->mutex); + return status; +} + +void Canvas_Agent_Service_Shutdown(Canvas_Agent_Service *p_service) +{ + Canvas_Agent_Copilot *p_copilot = + (Canvas_Agent_Copilot *)p_service->p_native; + if (!p_copilot) return; + Inference_Bridge_Destroy(p_copilot->p_bridge); + p_copilot->p_bridge = NULL; + pthread_mutex_destroy(&p_copilot->mutex); + p_service->p_native = NULL; +}