diff infinite_canvas/agent_response.c @ 281:c57149ad216e default tip

Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
author MrJuneJune <me@mrjunejune.com>
date Tue, 18 Aug 2026 22:18:15 -0700
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/infinite_canvas/agent_response.c	Tue Aug 18 22:18:15 2026 -0700
@@ -0,0 +1,237 @@
+#include "infinite_canvas/agent_response.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+static boolean Canvas_Agent_Entity_Type(
+    const char *p_kind,
+    Canvas_Entity_Type *p_type)
+{
+    struct {
+        const char *p_name;
+        Canvas_Entity_Type type;
+    } types[] = {
+        {"text", CANVAS_ENTITY_TEXT},
+        {"button", CANVAS_ENTITY_BUTTON},
+        {"text_area", CANVAS_ENTITY_TEXT_AREA},
+        {"accordion", CANVAS_ENTITY_ACCORDION},
+        {"card", CANVAS_ENTITY_CARD},
+        {"calendar", CANVAS_ENTITY_CALENDAR},
+        {"table", CANVAS_ENTITY_TABLE},
+        {"notification", CANVAS_ENTITY_NOTIFICATION},
+        {"scroll_area", CANVAS_ENTITY_SCROLL_AREA},
+        {"image", CANVAS_ENTITY_IMAGE},
+        {"web_content", CANVAS_ENTITY_WEB_CONTENT},
+    };
+    for (size_t index = 0; index < sizeof(types) / sizeof(types[0]); index++) {
+        if (strcmp(p_kind, types[index].p_name) == 0) {
+            *p_type = types[index].type;
+            return TRUE;
+        }
+    }
+    return FALSE;
+}
+
+static boolean Canvas_Agent_JSON_Number(
+    Dowa_JSON_Entry *p_object,
+    const char *p_key,
+    float *p_value,
+    boolean *p_present)
+{
+    Dowa_JSON_Value *p_json = Dowa_JSON_Get(p_object, p_key);
+    *p_present = p_json ? TRUE : FALSE;
+    if (!p_json) return TRUE;
+    if (p_json->type != DOWA_JSON_NUMBER ||
+        !isfinite(p_json->num_val) ||
+        fabs(p_json->num_val) > 10000000.0) {
+        return FALSE;
+    }
+    *p_value = (float)p_json->num_val;
+    return TRUE;
+}
+
+static boolean Canvas_Agent_URL_Allowed(const char *p_value)
+{
+    return !p_value[0] ||
+        strncmp(p_value, "https://", 8) == 0 ||
+        strncmp(p_value, "http://", 7) == 0;
+}
+
+boolean Canvas_Agent_Response_Parse(
+    const char *p_json,
+    Canvas_Agent_Response *p_response)
+{
+    if (!p_json || !p_response) return FALSE;
+    memset(p_response, 0, sizeof(*p_response));
+    Dowa_Arena *p_arena = Dowa_Arena_Create(128 * 1024);
+    if (!p_arena) return FALSE;
+    Dowa_JSON_Value root = Dowa_JSON_Parse(
+        p_json, (int32)strlen(p_json), p_arena);
+    if (root.type != DOWA_JSON_OBJECT) {
+        Dowa_Arena_Free(p_arena);
+        return FALSE;
+    }
+    Dowa_JSON_Entry *p_object = root.object_val;
+    char *p_action = Dowa_JSON_Get_String(p_object, "action");
+    char *p_presentation =
+        Dowa_JSON_Get_String(p_object, "presentation");
+    char *p_title = Dowa_JSON_Get_String(p_object, "title");
+    char *p_text = Dowa_JSON_Get_String(p_object, "response");
+    Dowa_JSON_Value *p_conversation =
+        Dowa_JSON_Get(p_object, "conversation_id");
+    if (!p_action ||
+        (strcmp(p_action, "create") != 0 &&
+            strcmp(p_action, "append") != 0) ||
+        !p_presentation ||
+        (strcmp(p_presentation, "conversation") != 0 &&
+            strcmp(p_presentation, "action") != 0) ||
+        (strcmp(p_action, "append") == 0 &&
+            strcmp(p_presentation, "conversation") != 0) ||
+        !p_text ||
+        !p_conversation ||
+        p_conversation->type != DOWA_JSON_NUMBER ||
+        floor(p_conversation->num_val) != p_conversation->num_val ||
+        p_conversation->num_val < 0 ||
+        p_conversation->num_val > 4294967295.0) {
+        Dowa_Arena_Free(p_arena);
+        return FALSE;
+    }
+    snprintf(
+        p_response->action,
+        sizeof(p_response->action),
+        "%s",
+        p_action);
+    snprintf(
+        p_response->presentation,
+        sizeof(p_response->presentation),
+        "%s",
+        p_presentation);
+    snprintf(
+        p_response->title,
+        sizeof(p_response->title),
+        "%s",
+        p_title ? p_title : "");
+    snprintf(
+        p_response->response,
+        sizeof(p_response->response),
+        "%s",
+        p_text);
+    p_response->conversation_id = (uint32)p_conversation->num_val;
+
+    Dowa_JSON_Value *p_entities = Dowa_JSON_Get(p_object, "entities");
+    if (p_entities) {
+        if (p_entities->type != DOWA_JSON_ARRAY ||
+            Dowa_Array_Length(p_entities->array_val) >
+                CANVAS_AGENT_MAX_CREATED_ENTITIES) {
+            Dowa_Arena_Free(p_arena);
+            return FALSE;
+        }
+        for (size_t index = 0;
+            index < Dowa_Array_Length(p_entities->array_val);
+            index++) {
+            Dowa_JSON_Value *p_value = &p_entities->array_val[index];
+            if (p_value->type != DOWA_JSON_OBJECT) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            Dowa_JSON_Entry *p_entity_object = p_value->object_val;
+            char *p_kind =
+                Dowa_JSON_Get_String(p_entity_object, "kind");
+            Canvas_Agent_Entity_Spec *p_spec =
+                &p_response->entities[p_response->entity_count];
+            if (!p_kind ||
+                !Canvas_Agent_Entity_Type(p_kind, &p_spec->type)) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            char *p_label =
+                Dowa_JSON_Get_String(p_entity_object, "label");
+            char *p_entity_text =
+                Dowa_JSON_Get_String(p_entity_object, "text");
+            snprintf(
+                p_spec->label,
+                sizeof(p_spec->label),
+                "%s",
+                p_label ? p_label : "");
+            snprintf(
+                p_spec->text,
+                sizeof(p_spec->text),
+                "%s",
+                p_entity_text ? p_entity_text : "");
+            if ((p_spec->type == CANVAS_ENTITY_IMAGE ||
+                    p_spec->type == CANVAS_ENTITY_WEB_CONTENT) &&
+                !Canvas_Agent_URL_Allowed(p_spec->text)) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            float x = 0.0f;
+            float y = 0.0f;
+            boolean has_x = FALSE;
+            boolean has_y = FALSE;
+            if (!Canvas_Agent_JSON_Number(
+                    p_entity_object, "x", &x, &has_x) ||
+                !Canvas_Agent_JSON_Number(
+                    p_entity_object, "y", &y, &has_y)) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            if (has_x != has_y) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            if ((has_x && (fabsf(x) > 1000000.0f ||
+                    fabsf(y) > 1000000.0f))) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            p_spec->has_position = has_x && has_y;
+            p_spec->position = (Vector2){x, y};
+            float width = 0.0f;
+            float height = 0.0f;
+            boolean has_width = FALSE;
+            boolean has_height = FALSE;
+            if (!Canvas_Agent_JSON_Number(
+                    p_entity_object,
+                    "width",
+                    &width,
+                    &has_width) ||
+                !Canvas_Agent_JSON_Number(
+                    p_entity_object,
+                    "height",
+                    &height,
+                    &has_height)) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            if (has_width != has_height ||
+                (has_width &&
+                    (width < 32.0f ||
+                        height < 24.0f ||
+                        width > 5000.0f ||
+                        height > 5000.0f))) {
+                Dowa_Arena_Free(p_arena);
+                return FALSE;
+            }
+            p_spec->has_size = has_width && has_height;
+            p_spec->size = (Vector2){width, height};
+            Dowa_JSON_Value *p_entity_value =
+                Dowa_JSON_Get(p_entity_object, "value");
+            if (p_entity_value) {
+                if (p_entity_value->type != DOWA_JSON_NUMBER ||
+                    floor(p_entity_value->num_val) !=
+                        p_entity_value->num_val ||
+                    p_entity_value->num_val < -2147483648.0 ||
+                    p_entity_value->num_val > 2147483647.0) {
+                    Dowa_Arena_Free(p_arena);
+                    return FALSE;
+                }
+                p_spec->value = (int32)p_entity_value->num_val;
+                p_spec->has_value = TRUE;
+            }
+            p_response->entity_count++;
+        }
+    }
+    Dowa_Arena_Free(p_arena);
+    return TRUE;
+}