llm-proxy / client_curl.c
trung.le
feat: add curl.exe C clients
2af870d
Raw
History Blame Contribute Delete
8.25 kB
/*
* LLM Proxy Client - C Version (Windows using curl.exe)
* Compile: gcc -std=c11 -O2 -Wall -Wextra -o llm_client_curl.exe client_curl.c
* Usage: llm_client_curl.exe
* Requires curl.exe, included with current Windows 10 and Windows 11.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define MAX_PROMPT_SIZE 4096
#define MAX_RESPONSE_SIZE 65536
typedef struct {
const char *name;
const char *id;
} LLMModel;
static const LLMModel MODELS[] = {
{"Laguna S 2.1 (Free)", "poolside/laguna-s-2.1:free"},
{"Gemini 3.6 Flash (Paid)", "google/gemini-3.6-flash"},
{"Claude Opus 5 (Paid)", "anthropic/claude-opus-5"},
{"Claude Fable 5 (Paid)", "anthropic/claude-fable-5"},
{"GPT-5.6 Sol (Paid)", "openai/gpt-5.6-sol"},
{"GPT-5.6 Luna (Paid)", "openai/gpt-5.6-luna"},
{"Nemotron 3 Ultra (Free)", "nvidia/nemotron-3-ultra-550b-a55b:free"}
};
static const int MODEL_COUNT = sizeof(MODELS) / sizeof(MODELS[0]);
static char *copy_string(const char *text) {
size_t size = strlen(text) + 1;
char *copy = (char *)malloc(size);
if (copy != NULL) {
memcpy(copy, text, size);
}
return copy;
}
static char *error_message(const char *message, DWORD code) {
char buffer[256];
snprintf(buffer, sizeof(buffer), "Error: %s (code: %lu)", message, (unsigned long)code);
return copy_string(buffer);
}
static int create_temp_file(char *path) {
char temp_dir[MAX_PATH];
DWORD length = GetTempPathA(sizeof(temp_dir), temp_dir);
if (length == 0 || length >= sizeof(temp_dir)) {
return 0;
}
return GetTempFileNameA(temp_dir, "llm", 0, path) != 0;
}
static int write_binary_file(const char *path, const char *data, size_t size) {
FILE *file = fopen(path, "wb");
if (file == NULL) {
return 0;
}
int success = fwrite(data, 1, size, file) == size;
if (fclose(file) != 0) {
success = 0;
}
return success;
}
static char *read_response_file(const char *path) {
FILE *file = fopen(path, "rb");
if (file == NULL) {
return copy_string("Error: Failed to read curl response");
}
char *response = (char *)malloc(MAX_RESPONSE_SIZE);
if (response == NULL) {
fclose(file);
return copy_string("Error: Memory allocation failed");
}
size_t total_size = fread(response, 1, MAX_RESPONSE_SIZE - 1, file);
if (ferror(file)) {
free(response);
fclose(file);
return copy_string("Error: Failed while reading curl response");
}
response[total_size] = '\0';
fclose(file);
return response;
}
char *ask_llm(const char *prompt, const LLMModel *model) {
char request_path[MAX_PATH] = {0};
char response_path[MAX_PATH] = {0};
char command[2048];
STARTUPINFOA startup_info;
PROCESS_INFORMATION process_info;
DWORD exit_code = 0;
char *response = NULL;
if (!create_temp_file(request_path) || !create_temp_file(response_path)) {
response = error_message("Failed to create temporary files", GetLastError());
goto cleanup;
}
if (!write_binary_file(request_path, prompt, strlen(prompt))) {
response = copy_string("Error: Failed to write temporary request file");
goto cleanup;
}
int command_length = snprintf(
command,
sizeof(command),
"curl.exe --silent --show-error --fail-with-body --connect-timeout 30 "
"--max-time 120 --request POST --header \"Content-Type: text/plain; charset=utf-8\" "
"--header \"X-Model: %s\" --data-binary \"@%s\" --output \"%s\" "
"https://ttl0110-llm-proxy.hf.space/ask",
model->id,
request_path,
response_path);
if (command_length < 0 || command_length >= (int)sizeof(command)) {
response = copy_string("Error: curl command is too long");
goto cleanup;
}
ZeroMemory(&startup_info, sizeof(startup_info));
ZeroMemory(&process_info, sizeof(process_info));
startup_info.cb = sizeof(startup_info);
if (!CreateProcessA(
NULL,
command,
NULL,
NULL,
FALSE,
CREATE_NO_WINDOW,
NULL,
NULL,
&startup_info,
&process_info)) {
response = error_message("Failed to start curl.exe", GetLastError());
goto cleanup;
}
WaitForSingleObject(process_info.hProcess, INFINITE);
if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) {
response = error_message("Failed to read curl exit code", GetLastError());
}
CloseHandle(process_info.hThread);
CloseHandle(process_info.hProcess);
if (response == NULL) {
response = read_response_file(response_path);
}
if (exit_code != 0 && response != NULL && response[0] == '\0') {
free(response);
response = error_message("curl.exe request failed", exit_code);
}
cleanup:
if (request_path[0] != '\0') {
DeleteFileA(request_path);
}
if (response_path[0] != '\0') {
DeleteFileA(response_path);
}
return response != NULL ? response : copy_string("Error: Unknown request failure");
}
void clear_input_buffer(void) {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
int main(void) {
char prompt[MAX_PROMPT_SIZE];
char prompt_prefix[MAX_PROMPT_SIZE] = {0};
int choice = 0;
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
printf("=== C LLM Assistant (curl.exe) ===\n\n");
printf("Chon Model LLM:\n");
for (int i = 0; i < MODEL_COUNT; i++) {
printf("%d. %s\n", i + 1, MODELS[i].name);
}
while (choice < 1 || choice > MODEL_COUNT) {
printf("Nhap lua chon (1-%d): ", MODEL_COUNT);
if (scanf("%d", &choice) != 1) {
clear_input_buffer();
choice = 0;
}
}
clear_input_buffer();
const LLMModel *selected_model = &MODELS[choice - 1];
printf("Ban da chon: %s\n\n", selected_model->name);
printf("Nhap prompt prefix (bo trong neu khong dung): ");
if (fgets(prompt_prefix, sizeof(prompt_prefix), stdin) != NULL) {
prompt_prefix[strcspn(prompt_prefix, "\r\n")] = '\0';
}
printf("Huong dan: Nhap prompt cua ban. Nhap dong trong de gui.\n");
printf("Nhap 'EXIT' de thoat.\n");
while (1) {
char full_prompt[MAX_PROMPT_SIZE] = {0};
size_t total_len = 0;
printf("\nBan [%s]:\n", selected_model->name);
while (1) {
if (fgets(prompt, sizeof(prompt), stdin) == NULL) {
printf("Tam biet!\n");
return 0;
}
if (_strnicmp(prompt, "EXIT", 4) == 0 &&
(prompt[4] == '\n' || prompt[4] == '\r' || prompt[4] == '\0')) {
printf("Tam biet!\n");
return 0;
}
if (prompt[0] == '\n' || (prompt[0] == '\r' && prompt[1] == '\n')) {
break;
}
size_t line_len = strlen(prompt);
if (total_len + line_len < MAX_PROMPT_SIZE - 1) {
strcat(full_prompt, prompt);
total_len += line_len;
}
}
if (total_len == 0) {
printf("Prompt trong, vui long nhap lai.\n");
continue;
}
while (total_len > 0 &&
(full_prompt[total_len - 1] == '\n' || full_prompt[total_len - 1] == '\r')) {
full_prompt[--total_len] = '\0';
}
char request_prompt[MAX_PROMPT_SIZE] = {0};
if (prompt_prefix[0] != '\0') {
int written = snprintf(request_prompt, sizeof(request_prompt), "%s\n\n%s",
prompt_prefix, full_prompt);
if (written < 0 || written >= (int)sizeof(request_prompt)) {
printf("Prompt va prefix qua dai, vui long nhap lai.\n");
continue;
}
} else {
strcpy(request_prompt, full_prompt);
}
printf("Thinking...\n");
char *response = ask_llm(request_prompt, selected_model);
printf("\n[LLM]: %s\n", response);
printf("-------------------------------------------\n");
free(response);
}
}