uncloseai.com/public/languages/c/curl/uncloseai.c

467 lines
14 KiB
C

// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.permacomputer.com
/*
* UncloseAI C Library using libcurl
* OpenAI-compatible API client with streaming support
* Compatible with vLLM, Ollama, and OpenAI-compatible endpoints
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#define MAX_ENDPOINTS 100
#define MAX_MODELS 100
#define MAX_URL_LEN 512
#define MAX_MODEL_LEN 256
#define MAX_CONTENT_LEN 1024
// Structure to hold response data
struct MemoryStruct {
char *memory;
size_t size;
};
// Structure to hold discovered model info
struct ModelInfo {
char id[MAX_MODEL_LEN];
char endpoint[MAX_URL_LEN];
int max_tokens;
};
// UncloseAI Client structure
typedef struct {
struct ModelInfo *models;
int model_count;
char tts_endpoints[MAX_ENDPOINTS][MAX_URL_LEN];
int tts_count;
int timeout;
} UncloseAIClient;
// Callback function type for streaming
typedef void (*StreamCallback)(const char *content, void *userdata);
// Structure for streaming context
struct StreamContext {
StreamCallback callback;
void *userdata;
char buffer[8192];
size_t buffer_pos;
};
/*************************************************************
* LIBRARY API - Callback function to capture response data
*************************************************************/
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
char *ptr = realloc(mem->memory, mem->size + realsize + 1);
if(!ptr) {
printf("Not enough memory (realloc returned NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
/*************************************************************
* LIBRARY API - Extract content from SSE data chunk
*************************************************************/
static void extract_sse_content(const char *data, char *content, size_t max_len) {
// Look for "content":"..." pattern
const char *content_marker = "\"content\":\"";
const char *start = strstr(data, content_marker);
if(!start) return;
start += strlen(content_marker);
const char *end = start;
// Find closing quote, handling escaped quotes
while(*end && *end != '"') {
if(*end == '\\' && *(end+1)) {
end += 2;
} else {
end++;
}
}
size_t len = end - start;
if(len > max_len - 1) len = max_len - 1;
strncpy(content, start, len);
content[len] = '\0';
}
/*************************************************************
* LIBRARY API - Streaming callback for curl
*************************************************************/
static size_t StreamWriteCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct StreamContext *ctx = (struct StreamContext *)userp;
// Append to buffer
char *data = (char *)contents;
for(size_t i = 0; i < realsize; i++) {
if(ctx->buffer_pos >= sizeof(ctx->buffer) - 1) {
// Buffer full, skip
continue;
}
ctx->buffer[ctx->buffer_pos++] = data[i];
// Check for line ending
if(data[i] == '\n' && ctx->buffer_pos >= 2 &&
ctx->buffer[ctx->buffer_pos-2] == '\n') {
ctx->buffer[ctx->buffer_pos] = '\0';
// Process SSE line
if(strncmp(ctx->buffer, "data: ", 6) == 0) {
const char *json_data = ctx->buffer + 6;
// Check for [DONE]
if(strncmp(json_data, "[DONE]", 6) == 0) {
ctx->buffer_pos = 0;
break;
}
// Extract content
char content[MAX_CONTENT_LEN];
extract_sse_content(json_data, content, sizeof(content));
if(strlen(content) > 0 && ctx->callback) {
ctx->callback(content, ctx->userdata);
}
}
ctx->buffer_pos = 0;
}
}
return realsize;
}
/*************************************************************
* LIBRARY API - Extract model IDs from JSON
*************************************************************/
static void extract_model_ids(UncloseAIClient *client, const char *json, const char *endpoint) {
const char *search = json;
const char *id_marker = "\"id\":\"";
while((search = strstr(search, id_marker)) != NULL &&
client->model_count < MAX_MODELS) {
search += strlen(id_marker);
const char *end = strchr(search, '"');
if(end) {
size_t len = end - search;
if(len < MAX_MODEL_LEN) {
// Skip modelperm-* entries
if(strncmp(search, "modelperm-", 10) == 0) {
search = end + 1;
continue;
}
strncpy(client->models[client->model_count].id, search, len);
client->models[client->model_count].id[len] = '\0';
strncpy(client->models[client->model_count].endpoint, endpoint, MAX_URL_LEN-1);
client->models[client->model_count].max_tokens = 8192;
client->model_count++;
}
}
search = end + 1;
}
}
/*************************************************************
* LIBRARY API - Initialize client and discover models
*************************************************************/
UncloseAIClient* uncloseai_init(int timeout) {
UncloseAIClient *client = (UncloseAIClient*)malloc(sizeof(UncloseAIClient));
if(!client) return NULL;
client->models = (struct ModelInfo*)malloc(MAX_MODELS * sizeof(struct ModelInfo));
if(!client->models) {
free(client);
return NULL;
}
client->model_count = 0;
client->tts_count = 0;
client->timeout = timeout;
printf("Initializing UncloseAI client...\n");
// Discover chat/code models
for(int i = 1; i < 10000; i++) {
char var_name[32];
snprintf(var_name, sizeof(var_name), "MODEL_ENDPOINT_%d", i);
char *endpoint = getenv(var_name);
if(!endpoint) break;
printf("Endpoint %d: %s\n", i, endpoint);
// Fetch models
char url[MAX_URL_LEN];
snprintf(url, sizeof(url), "%s/models", endpoint);
CURL *curl = curl_easy_init();
if(curl) {
struct MemoryStruct chunk = {NULL, 0};
chunk.memory = malloc(1);
chunk.size = 0;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
CURLcode res = curl_easy_perform(curl);
if(res == CURLE_OK && chunk.memory) {
extract_model_ids(client, chunk.memory, endpoint);
}
free(chunk.memory);
curl_easy_cleanup(curl);
}
}
// Discover TTS endpoints
for(int i = 1; i < 10000; i++) {
char var_name[32];
snprintf(var_name, sizeof(var_name), "TTS_ENDPOINT_%d", i);
char *endpoint = getenv(var_name);
if(!endpoint) break;
strncpy(client->tts_endpoints[client->tts_count++], endpoint, MAX_URL_LEN-1);
}
printf("Discovered %d models, %d TTS endpoints\n\n", client->model_count, client->tts_count);
return client;
}
/*************************************************************
* LIBRARY API - Non-streaming chat completion
*************************************************************/
int uncloseai_chat(UncloseAIClient *client, int model_idx, const char *prompt,
struct MemoryStruct *response) {
if(model_idx >= client->model_count) return -1;
char url[MAX_URL_LEN];
char json[2048];
snprintf(url, sizeof(url), "%s/chat/completions",
client->models[model_idx].endpoint);
snprintf(json, sizeof(json),
"{\"model\":\"%s\","
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
"\"stream\":false,"
"\"temperature\":0.7,"
"\"max_tokens\":100}",
client->models[model_idx].id, prompt);
CURL *curl = curl_easy_init();
if(!curl) return -1;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)response);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return (res == CURLE_OK) ? 0 : -1;
}
/*************************************************************
* LIBRARY API - Streaming chat completion
*************************************************************/
int uncloseai_chat_stream(UncloseAIClient *client, int model_idx, const char *prompt,
StreamCallback callback, void *userdata) {
if(model_idx >= client->model_count) return -1;
char url[MAX_URL_LEN];
char json[2048];
snprintf(url, sizeof(url), "%s/chat/completions",
client->models[model_idx].endpoint);
snprintf(json, sizeof(json),
"{\"model\":\"%s\","
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}],"
"\"stream\":true,"
"\"temperature\":0.7,"
"\"max_tokens\":500}",
client->models[model_idx].id, prompt);
CURL *curl = curl_easy_init();
if(!curl) return -1;
struct StreamContext ctx;
ctx.callback = callback;
ctx.userdata = userdata;
ctx.buffer_pos = 0;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, StreamWriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&ctx);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);
CURLcode res = curl_easy_perform(curl);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return (res == CURLE_OK) ? 0 : -1;
}
/*************************************************************
* LIBRARY API - Text-to-speech generation
*************************************************************/
int uncloseai_tts(UncloseAIClient *client, const char *text, const char *voice,
const char *output_file) {
if(client->tts_count == 0) return -1;
char url[MAX_URL_LEN];
char json[2048];
snprintf(url, sizeof(url), "%s/audio/speech", client->tts_endpoints[0]);
snprintf(json, sizeof(json),
"{\"model\":\"tts-1\","
"\"voice\":\"%s\","
"\"input\":\"%s\"}",
voice, text);
CURL *curl = curl_easy_init();
if(!curl) return -1;
struct MemoryStruct chunk = {NULL, 0};
chunk.memory = malloc(1);
chunk.size = 0;
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)client->timeout);
CURLcode res = curl_easy_perform(curl);
int result = -1;
if(res == CURLE_OK) {
FILE *fp = fopen(output_file, "wb");
if(fp) {
fwrite(chunk.memory, 1, chunk.size, fp);
fclose(fp);
result = 0;
}
}
free(chunk.memory);
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return result;
}
/*************************************************************
* LIBRARY API - Free client resources
*************************************************************/
void uncloseai_free(UncloseAIClient *client) {
if(client) {
if(client->models) free(client->models);
free(client);
}
}
/*************************************************************
* DEMO PROGRAM - Shows library usage
*************************************************************/
// Callback for streaming
void stream_callback(const char *content, void *userdata) {
printf("%s", content);
fflush(stdout);
}
int main(void) {
printf("=== UncloseAI C Client (with Streaming) ===\n\n");
curl_global_init(CURL_GLOBAL_ALL);
// Initialize client
UncloseAIClient *client = uncloseai_init(30);
if(!client || client->model_count == 0) {
printf("ERROR: No models discovered\n");
curl_global_cleanup();
return 1;
}
// Non-streaming chat example
printf("=== Non-Streaming Chat ===\n");
printf("Model: %s\n", client->models[0].id);
struct MemoryStruct response = {NULL, 0};
response.memory = malloc(1);
response.size = 0;
if(uncloseai_chat(client, 0, "Explain quantum computing in one sentence",
&response) == 0) {
printf("Response: (%zu bytes received)\n", response.size);
}
free(response.memory);
printf("\n");
// Streaming chat example
int model_idx = (client->model_count >= 2) ? 1 : 0;
printf("=== Streaming Chat ===\n");
printf("Model: %s\n", client->models[model_idx].id);
printf("Response: ");
uncloseai_chat_stream(client, model_idx,
"Write a hello world program in C",
stream_callback, NULL);
printf("\n\n");
// TTS example
if(client->tts_count > 0) {
printf("=== TTS Speech Generation ===\n");
printf("Model: tts-1\n");
if(uncloseai_tts(client, "Hello from UncloseAI C client!",
"alloy", "/tmp/speech.mp3") == 0) {
printf("Audio saved to /tmp/speech.mp3\n");
} else {
printf("TTS failed\n");
}
}
printf("\n=== Examples Complete ===\n");
uncloseai_free(client);
curl_global_cleanup();
return 0;
}