Mercurial
view 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 source
#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; }