diff --git a/languages/awk/Dockerfile b/languages/awk/Dockerfile index 1b8fcdd..4a24aca 100644 --- a/languages/awk/Dockerfile +++ b/languages/awk/Dockerfile @@ -1,5 +1,5 @@ # GNU AWK 5.3.2 (checked 2025-10-13: gawk 5.3.2-r2 is latest in Alpine edge) -FROM alpine:3.22 +FROM alpine:latest RUN apk add --no-cache gawk curl ca-certificates diff --git a/languages/c/curl/Dockerfile b/languages/c/curl/Dockerfile index e22c4dd..556d98d 100644 --- a/languages/c/curl/Dockerfile +++ b/languages/c/curl/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-12: alpine:3.21 is latest stable) -FROM alpine:3.21 +FROM alpine:latest # Install C compiler and libcurl development libraries RUN apk --no-cache add \ diff --git a/languages/c/libsoup/Dockerfile b/languages/c/libsoup/Dockerfile index d51cade..7c1308a 100644 --- a/languages/c/libsoup/Dockerfile +++ b/languages/c/libsoup/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-14: alpine:3.20 is latest stable) -FROM alpine:3.20 +FROM alpine:latest # Install C compiler and libsoup development libraries RUN apk --no-cache add \ diff --git a/languages/c/nghttp2/Dockerfile b/languages/c/nghttp2/Dockerfile index 24ec79a..6c54bd4 100644 --- a/languages/c/nghttp2/Dockerfile +++ b/languages/c/nghttp2/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-14: alpine:3.20 is latest stable) -FROM alpine:3.20 +FROM alpine:latest # Install C compiler and nghttp2 development libraries RUN apk --no-cache add \ diff --git a/languages/c/nghttp2/uncloseai.c b/languages/c/nghttp2/uncloseai.c index 94e7033..66f3954 100644 --- a/languages/c/nghttp2/uncloseai.c +++ b/languages/c/nghttp2/uncloseai.c @@ -1,6 +1,6 @@ /* * UncloseAI C Client using nghttp2 (HTTP/2) - * OpenAI-compatible API client with HTTP/2 support + * OpenAI-compatible API client with HTTP/2 and streaming support * Compatible with vLLM, Ollama, and OpenAI-compatible endpoints */ @@ -43,6 +43,9 @@ struct Connection { size_t response_size; size_t response_capacity; int done; + int streaming; + char stream_buffer[8192]; + size_t stream_buffer_pos; }; // Request data provider @@ -151,23 +154,86 @@ ssize_t recv_callback(nghttp2_session *session, uint8_t *buf, size_t length, return rv; } +// Extract content from SSE data chunk +void extract_sse_content(const char *data, char *content, size_t max_len) { + 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'; +} + // Data chunk received callback int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, int32_t stream_id, const uint8_t *data, size_t len, void *user_data) { struct Connection *conn = (struct Connection *)user_data; - // Resize buffer if needed - if (conn->response_size + len > conn->response_capacity) { - size_t new_capacity = (conn->response_capacity + len) * 2; - char *new_data = realloc(conn->response_data, new_capacity); - if (!new_data) return 0; - conn->response_data = new_data; - conn->response_capacity = new_capacity; - } + if (conn->streaming) { + // Process streaming SSE data + for (size_t i = 0; i < len; i++) { + if (conn->stream_buffer_pos >= sizeof(conn->stream_buffer) - 1) { + conn->stream_buffer_pos = 0; // Reset if overflow + } - memcpy(conn->response_data + conn->response_size, data, len); - conn->response_size += len; + conn->stream_buffer[conn->stream_buffer_pos++] = data[i]; + + // Check for line ending + if (data[i] == '\n' && conn->stream_buffer_pos >= 2 && + conn->stream_buffer[conn->stream_buffer_pos - 2] == '\n') { + conn->stream_buffer[conn->stream_buffer_pos] = '\0'; + + // Process SSE line + if (strncmp(conn->stream_buffer, "data: ", 6) == 0) { + const char *json_data = conn->stream_buffer + 6; + + // Check for [DONE] + if (strncmp(json_data, "[DONE]", 6) == 0) { + conn->stream_buffer_pos = 0; + break; + } + + // Extract and print content + char content[1024]; + extract_sse_content(json_data, content, sizeof(content)); + + if (strlen(content) > 0) { + printf("%s", content); + fflush(stdout); + } + } + + conn->stream_buffer_pos = 0; + } + } + } else { + // Non-streaming: accumulate response + if (conn->response_size + len > conn->response_capacity) { + size_t new_capacity = (conn->response_capacity + len) * 2; + char *new_data = realloc(conn->response_data, new_capacity); + if (!new_data) return 0; + conn->response_data = new_data; + conn->response_capacity = new_capacity; + } + + memcpy(conn->response_data + conn->response_size, data, len); + conn->response_size += len; + } return 0; } @@ -201,38 +267,8 @@ ssize_t data_provider_read_callback(nghttp2_session *session, int32_t stream_id, return to_copy; } -// Make HTTP/2 request -int http2_request(const char *host, int port, const char *path, - const char *method, const char *body, - char **response, size_t *response_size) { - int sock = connect_to_server(host, port); - if (sock < 0) return -1; - - // Initialize connection - struct Connection conn = {0}; - conn.sock = sock; - conn.response_data = malloc(BUFFER_SIZE); - conn.response_capacity = BUFFER_SIZE; - conn.response_size = 0; - conn.done = 0; - - // Setup nghttp2 callbacks - nghttp2_session_callbacks *callbacks; - nghttp2_session_callbacks_new(&callbacks); - nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); - nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); - nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); - - // Create client session - nghttp2_session_client_new(&conn.session, callbacks, &conn); - nghttp2_session_callbacks_del(callbacks); - - // Send HTTP/2 connection preface - nghttp2_submit_settings(conn.session, NGHTTP2_FLAG_NONE, NULL, 0); - - // Prepare headers - nghttp2_nv hdrs[5]; +// Setup HTTP/2 headers +void setup_http2_headers(nghttp2_nv *hdrs, const char *method, const char *path, const char *host) { hdrs[0].name = (uint8_t *)":method"; hdrs[0].value = (uint8_t *)method; hdrs[0].namelen = strlen(":method"); @@ -262,6 +298,46 @@ int http2_request(const char *host, int port, const char *path, hdrs[4].namelen = strlen("content-type"); hdrs[4].valuelen = strlen("application/json"); hdrs[4].flags = NGHTTP2_NV_FLAG_NONE; +} + +// Setup nghttp2 session with callbacks +void setup_nghttp2_session(struct Connection *conn, nghttp2_session_callbacks *callbacks) { + nghttp2_session_callbacks_new(&callbacks); + nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); + nghttp2_session_callbacks_set_recv_callback(callbacks, recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); + + nghttp2_session_client_new(&conn->session, callbacks, conn); + nghttp2_session_callbacks_del(callbacks); + + nghttp2_submit_settings(conn->session, NGHTTP2_FLAG_NONE, NULL, 0); +} + +// Make HTTP/2 request +int http2_request(const char *host, int port, const char *path, + const char *method, const char *body, + char **response, size_t *response_size) { + int sock = connect_to_server(host, port); + if (sock < 0) return -1; + + // Initialize connection + struct Connection conn = {0}; + conn.sock = sock; + conn.response_data = malloc(BUFFER_SIZE); + conn.response_capacity = BUFFER_SIZE; + conn.response_size = 0; + conn.done = 0; + conn.streaming = 0; + conn.stream_buffer_pos = 0; + + // Setup nghttp2 session and callbacks + nghttp2_session_callbacks *callbacks; + setup_nghttp2_session(&conn, callbacks); + + // Prepare headers + nghttp2_nv hdrs[5]; + setup_http2_headers(hdrs, method, path, host); // Submit request if (body && strlen(body) > 0) { @@ -290,6 +366,55 @@ int http2_request(const char *host, int port, const char *path, return 0; } +// Make streaming HTTP/2 request +int http2_stream_request(const char *host, int port, const char *path, + const char *method, const char *body) { + int sock = connect_to_server(host, port); + if (sock < 0) return -1; + + // Initialize connection for streaming + struct Connection conn = {0}; + conn.sock = sock; + conn.response_data = malloc(BUFFER_SIZE); + conn.response_capacity = BUFFER_SIZE; + conn.response_size = 0; + conn.done = 0; + conn.streaming = 1; // Enable streaming mode + conn.stream_buffer_pos = 0; + + // Setup nghttp2 session and callbacks + nghttp2_session_callbacks *callbacks; + setup_nghttp2_session(&conn, callbacks); + + // Prepare headers + nghttp2_nv hdrs[5]; + setup_http2_headers(hdrs, method, path, host); + + // Submit request + if (body && strlen(body) > 0) { + struct RequestData req_data = {body, strlen(body), 0}; + nghttp2_data_provider data_prd; + data_prd.source.ptr = &req_data; + data_prd.read_callback = data_provider_read_callback; + nghttp2_submit_request(conn.session, NULL, hdrs, 5, &data_prd, NULL); + } else { + nghttp2_submit_request(conn.session, NULL, hdrs, 4, NULL, NULL); + } + + // Send and receive (streaming prints directly in callback) + while (!conn.done) { + if (nghttp2_session_send(conn.session) != 0) break; + if (nghttp2_session_recv(conn.session) != 0) break; + } + + // Cleanup + free(conn.response_data); + nghttp2_session_del(conn.session); + close(sock); + + return 0; +} + // Simple JSON string extractor void extract_model_ids(const char *json, const char *endpoint) { const char *search = json; @@ -393,17 +518,17 @@ int main(void) { } printf("\n"); - // Non-streaming chat - printf("=== Non-Streaming Chat ===\n"); + // Non-streaming chat (Hermes) + printf("=== Non-Streaming Chat (Hermes) ===\n"); printf("Model: %s\n", models[0].id); char json[2048]; snprintf(json, sizeof(json), "{\"model\":\"%s\"," - "\"messages\":[{\"role\":\"user\",\"content\":\"Explain quantum computing in one sentence\"}]," + "\"messages\":[{\"role\":\"user\",\"content\":\"Give a Python Fizzbuzz solution in one line of code?\"}]," "\"stream\":false," - "\"max_tokens\":100," - "\"temperature\":0.7}", + "\"max_tokens\":150," + "\"temperature\":0.5}", models[0].id); char *response = NULL; @@ -421,6 +546,46 @@ int main(void) { printf("Request failed\n\n"); } + // Streaming chat (Hermes) + printf("=== Streaming Chat (Hermes) ===\n"); + printf("Model: %s\n", models[0].id); + printf("Response: "); + + char stream_json_hermes[2048]; + snprintf(stream_json_hermes, sizeof(stream_json_hermes), + "{\"model\":\"%s\"," + "\"messages\":[{\"role\":\"user\",\"content\":\"Give a Python Fizzbuzz solution in one line of code?\"}]," + "\"stream\":true," + "\"max_tokens\":500," + "\"temperature\":0.7}", + models[0].id); + + if (http2_stream_request(models[0].host, models[0].port, path, "POST", stream_json_hermes) != 0) { + printf("\nStreaming request failed"); + } + printf("\n\n"); + + // Streaming chat (Qwen Coder 3) - if second model available + if (model_count >= 2) { + printf("=== Streaming Chat (Qwen Coder 3) ===\n"); + printf("Model: %s\n", models[1].id); + printf("Response: "); + + char stream_json_qwen[2048]; + snprintf(stream_json_qwen, sizeof(stream_json_qwen), + "{\"model\":\"%s\"," + "\"messages\":[{\"role\":\"user\",\"content\":\"Write a hello world program in C\"}]," + "\"stream\":true," + "\"max_tokens\":500," + "\"temperature\":0.7}", + models[1].id); + + if (http2_stream_request(models[1].host, models[1].port, path, "POST", stream_json_qwen) != 0) { + printf("\nStreaming request failed"); + } + printf("\n\n"); + } + // TTS if (tts_count > 0) { printf("=== TTS Speech Generation ===\n"); diff --git a/languages/cpp/boost-beast/Dockerfile b/languages/cpp/boost-beast/Dockerfile index 534537f..5b18594 100644 --- a/languages/cpp/boost-beast/Dockerfile +++ b/languages/cpp/boost-beast/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-14: alpine:3.20 is latest stable) -FROM alpine:3.20 +FROM alpine:latest # Install C++ compiler and Boost libraries RUN apk --no-cache add \ diff --git a/languages/cpp/cpp-httplib/Dockerfile b/languages/cpp/cpp-httplib/Dockerfile index 633eabc..0c3ec3b 100644 --- a/languages/cpp/cpp-httplib/Dockerfile +++ b/languages/cpp/cpp-httplib/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-14: alpine:3.20 is latest stable) -FROM alpine:3.20 +FROM alpine:latest # Install C++ compiler and build tools RUN apk --no-cache add \ diff --git a/languages/cpp/libcurl/Dockerfile b/languages/cpp/libcurl/Dockerfile index f6b7f89..eb51aab 100644 --- a/languages/cpp/libcurl/Dockerfile +++ b/languages/cpp/libcurl/Dockerfile @@ -1,5 +1,5 @@ # Pin to specific Alpine version (checked 2025-10-12: alpine:3.21 is latest stable) -FROM alpine:3.21 +FROM alpine:latest # Install C++ compiler and libcurl development libraries RUN apk --no-cache add \ diff --git a/languages/csharp/openai/Dockerfile b/languages/csharp/openai/Dockerfile index bd57bca..b1e2927 100644 --- a/languages/csharp/openai/Dockerfile +++ b/languages/csharp/openai/Dockerfile @@ -1,5 +1,5 @@ # Use official .NET SDK to build -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS builder WORKDIR /app @@ -12,7 +12,7 @@ COPY uncloseai.cs ./ RUN dotnet build -c Release -o /app/build # Run stage -FROM mcr.microsoft.com/dotnet/runtime:8.0 +FROM mcr.microsoft.com/dotnet/runtime:9.0-alpine WORKDIR /app diff --git a/languages/go/http/Dockerfile b/languages/go/http/Dockerfile index 481c23e..778e6ed 100644 --- a/languages/go/http/Dockerfile +++ b/languages/go/http/Dockerfile @@ -14,7 +14,7 @@ COPY uncloseai.go . RUN go build -o uncloseai uncloseai.go # Use minimal alpine image for runtime -FROM alpine:3.21 +FROM alpine:latest # Install ca-certificates for HTTPS RUN apk --no-cache add ca-certificates diff --git a/languages/go/openai/Dockerfile b/languages/go/openai/Dockerfile index bd09b33..c7b20af 100644 --- a/languages/go/openai/Dockerfile +++ b/languages/go/openai/Dockerfile @@ -1,5 +1,5 @@ # Multi-stage build for Go with official OpenAI SDK -FROM golang:1.22-alpine AS builder +FROM golang:1.23-alpine AS builder WORKDIR /app diff --git a/languages/lua/Dockerfile b/languages/lua/Dockerfile index 17db7e9..2988337 100644 --- a/languages/lua/Dockerfile +++ b/languages/lua/Dockerfile @@ -1,5 +1,5 @@ # Alpine 3.22 with Lua 5.4 (checked 2025-10-13: alpine:3.22 with lua5.4 is latest stable) -FROM alpine:3.22 +FROM alpine:latest RUN apk add --no-cache lua5.4 lua5.4-dev luarocks5.4 ca-certificates openssl-dev gcc musl-dev diff --git a/languages/ruby/openai/Dockerfile b/languages/ruby/openai/Dockerfile index 2cf809a..6c12cde 100644 --- a/languages/ruby/openai/Dockerfile +++ b/languages/ruby/openai/Dockerfile @@ -1,5 +1,5 @@ # Use official Ruby image -FROM ruby:3.3-slim +FROM ruby:3.3-alpine WORKDIR /app diff --git a/languages/vbnet/Dockerfile b/languages/vbnet/Dockerfile index 3f80560..a9a7cbf 100644 --- a/languages/vbnet/Dockerfile +++ b/languages/vbnet/Dockerfile @@ -1,7 +1,7 @@ -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build WORKDIR /src COPY ["UncloseAI.vbproj", "."] RUN dotnet restore "UncloseAI.vbproj"