#include "cst_metrics.h" #include "cJSON.h" #include "cst_agent.h" #include "os_compat.h" #include #include #include #include struct cst_metrics { FILE *fp; char path[OS_PATH_MAX]; }; cst_metrics_t *cst_metrics_open(const char *workspace, const char *session_path) { const char *off = getenv("CST_METRICS"); if (off && strcmp(off, "0") == 0) return NULL; cst_metrics_t *m = calloc(1, sizeof(*m)); if (!m) return NULL; const char *override = getenv("CST_METRICS_PATH"); if (override && override[0]) { snprintf(m->path, sizeof(m->path), "%s", override); } else { if (!workspace) workspace = "."; char dir[OS_PATH_MAX], agent[OS_PATH_MAX]; os_path_join(agent, sizeof(agent), workspace, ".agent"); os_mkdir(agent, 0755); os_path_join(dir, sizeof(dir), agent, "metrics"); os_mkdir(dir, 0755); /* One file per session: the id of the transcript's path (.jsonl, or * /session.jsonl in the store's own layout). */ char leaf[256]; if (cst_agent_store_id_of_path(session_path, leaf, sizeof(leaf)) != 0) snprintf(leaf, sizeof(leaf), "session"); char file[300]; snprintf(file, sizeof(file), "%s.jsonl", leaf); os_path_join(m->path, sizeof(m->path), dir, file); } m->fp = fopen(m->path, "ab"); if (!m->fp) { free(m); return NULL; } return m; } void cst_metrics_close(cst_metrics_t *m) { if (!m) return; if (m->fp) fclose(m->fp); free(m); } void cst_metrics_emit(cst_metrics_t *m, void *obj) { cJSON *o = obj; if (!o) return; if (m && m->fp) { if (!cJSON_GetObjectItemCaseSensitive(o, "ts")) cJSON_AddNumberToObject(o, "ts", (double)time(NULL)); char *txt = cJSON_PrintUnformatted(o); if (txt) { fputs(txt, m->fp); fputc('\n', m->fp); fflush(m->fp); free(txt); } } cJSON_Delete(o); } const char *cst_metrics_path(const cst_metrics_t *m) { return m ? m->path : NULL; }