view 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 source

#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;
}