Infrastructure

Two recipes for LLM-backed TCP services: a transparent AI proxy that any net-plugin client can connect to, and an event-driven webhook router.

Back to Kookbook


Local AI Gateway

Modules: net plugin · curl plugin · JSON · String · Konsol

Hosts a TCP session named ai-gateway on port 2310. Any connected player sends a plain-text prompt; the gateway forwards it to gpt-4o-mini and broadcasts the response back to all players. Runs forever until killed.

Key patterns:

Connect with lan_chat.ks (choose j, session name ai-gateway) or any other net-plugin client.

Usage

# terminal 1 - start the gateway
minks ai_gateway.ks <openai_api_key>

# terminal 2 - connect as a player (uses lan_chat.ks from the web-and-network recipes)
minks lan_chat.ks
# > Host or join? j
# > Session: ai-gateway
# > Host IP: 127.0.0.1

Sample session

=== AI Gateway ===
Starting gateway session 'ai-gateway' on port 2310...
Ready.  Waiting for players...
[alice] What is gradient descent?
[AI] Gradient descent is an optimization algorithm that iteratively adjusts...
[bob] How does it differ from stochastic gradient descent?
[AI] Stochastic gradient descent updates parameters using a single random sample...

Script

// ai_gateway.ks - TCP server that proxies player messages to an LLM
// Modules: net plugin, curl plugin, JSON, String, Konsol
// Usage:  minks ai_gateway.ks <openai_api_key>
//
// Hosts a session named "ai-gateway" on port 2310.  Any connected player can
// send a text prompt; the gateway forwards it to the LLM and broadcasts the
// response back to all players.
//
// Connect with lan_chat.ks (choose 'j', session = "ai-gateway") or any
// net-plugin client.

#include "net"
#include "curl"

Konsol:Print("=== AI Gateway ===");

// ── Step 1: Read the API key ──────────────────────────────────────────────────
Var:List args;
OS:Args(args);

Var:Number argc;
List:Size(args, argc);

if (argc < 1) {
    Konsol:Print("Usage: minks ai_gateway.ks <openai_api_key>");
    Konsol:Exit(1);
}

Var:String apiKey;
List:Get(0, args, apiKey);

// ── Step 2: Start the TCP session ────────────────────────────────────────────
// Net:Host registers this process as the host on port 2310.
// Players connect with Net:Join("ai-gateway", <host_ip>).
Konsol:Print("Starting gateway session 'ai-gateway' on port 2310...");

Var:Number handle;
Net:Host("gateway", 8, "ai-gateway", handle);
Konsol:Print("Ready.  Waiting for players...");

// Set up curl headers once - they persist for all subsequent Curl:Post calls.
Curl:SetHeader("Authorization", "Bearer ${apiKey}");
Curl:SetHeader("Content-Type", "application/json");
Curl:SetTimeout(30);

// ── Step 3: Proxy loop ────────────────────────────────────────────────────────
// Net:Check pumps the network stack each iteration.
// Net:GetMessage drains one message at a time - loop until got = false.
Var:String sender;
Var:String msg;
Var:Boolean got;
Var:Boolean sendOk;

while (true) {
    Net:Check(handle);

    Net:GetMessage(handle, sender, msg, got);
    while (got) {
        Konsol:Print("[${sender}] ${msg}");

        // Forward the player's message to the LLM.
        Var:String safeMsg;
        String:Replace(msg, "\"", "\\\"", safeMsg);
        Var:String payload = """{
          "model": "gpt-4o-mini",
          "messages": [{"role": "user", "content": "${safeMsg}"}],
          "max_tokens": 256
        }""";

        Var:String body;
        Var:String reply;
        try {
            Curl:Post("https://api.openai.com/v1/chat/completions", payload, body);
            Var:Number status;
            Curl:Status(status);
            if (status == 200) {
                JSON:Parse(body, resp);
                JSON:Get("choices.0.message.content", resp, reply);
                String:Trim(reply, reply);
            } else {
                reply = "[gateway error: HTTP ${status}]";
            }
        } catch (CurlException e) {
            reply = "[gateway error: ${e.message}]";
        }

        // Broadcast the LLM reply to all connected players.
        Net:Send(handle, reply, sendOk);
        Konsol:Print("[AI] ${reply}");

        Net:GetMessage(handle, sender, msg, got);
    }
}


Webhook Receiver

Modules: net plugin · curl plugin · JSON · String · Dictionary · Konsol

Hosts a TCP session named webhooks on port 2310. Clients send JSON payloads; the receiver parses the event field, looks up an event-specific system prompt from a Dictionary, calls the LLM, and replies directly to the sender with Net:SendTo.

Event LLM task
alert Root-cause analysis and remediation bullets
question Direct Q&A answer
summarize 2–3 sentence summary of content field

Key patterns:

Payload format (client sends)

{"event": "alert",     "content": "CPU usage at 98% for 5 minutes on web-01"}
{"event": "question",  "content": "What does P99 latency mean?"}
{"event": "summarize", "content": "The quarterly report shows a 12% increase in..."}

Usage

# terminal 1 - start the receiver
minks webhook_receiver.ks <openai_api_key>

# terminal 2 - send an event (any net-plugin client)
# payload is sent as a plain-text JSON string via Net:Send

Sample session

=== Webhook Receiver ===
Starting webhook session 'webhooks' on port 2310...
Ready.  Waiting for events...
[monitor] {"event":"alert","content":"disk usage at 95% on /var/data"}
  Routing 'alert' event to LLM...
  → [monitor] • Disk at 95% - immediate risk of write failures.
               • Run `du -sh /*` to identify large directories.
               • Archive or delete logs in /var/log and /var/data/tmp.

Script

// webhook_receiver.ks - receive JSON events over TCP and route to an LLM
// Modules: net plugin, curl plugin, JSON, String, Dictionary, Konsol
// Usage:  minks webhook_receiver.ks <openai_api_key>
//
// Hosts a session named "webhooks" on port 2310.  Senders deliver JSON
// payloads as plain text messages.  The receiver parses the "event" field
// and routes the payload to the LLM with an event-specific system prompt.
//
// Supported event types and their LLM tasks:
//   alert     → root-cause analysis of the alert message
//   question  → direct Q&A answer
//   summarize → concise summary of the "content" field
//
// Clients send with Net:Send; the gateway replies via Net:SendTo back to sender.

#include "net"
#include "curl"

Konsol:Print("=== Webhook Receiver ===");

// ── Step 1: Read the API key ──────────────────────────────────────────────────
Var:List args;
OS:Args(args);

Var:Number argc;
List:Size(args, argc);

if (argc < 1) {
    Konsol:Print("Usage: minks webhook_receiver.ks <openai_api_key>");
    Konsol:Exit(1);
}

Var:String apiKey;
List:Get(0, args, apiKey);

// ── Step 2: Build the event routing table ─────────────────────────────────────
// Each event type maps to a system prompt that shapes the LLM's response.
Dictionary:New prompts;
Dictionary:Set("alert",     "You are a DevOps on-call assistant. Given an alert message, identify the likely cause and suggest immediate remediation steps in 2-3 bullet points.", prompts);
Dictionary:Set("question",  "You are a helpful assistant. Answer the question clearly and concisely.", prompts);
Dictionary:Set("summarize", "You are a summarization assistant. Produce a concise 2-3 sentence summary of the provided content.", prompts);

// ── Step 3: Start the TCP session ────────────────────────────────────────────
Konsol:Print("Starting webhook session 'webhooks' on port 2310...");

Var:Number handle;
Net:Host("receiver", 16, "webhooks", handle);
Konsol:Print("Ready.  Waiting for events...");

Curl:SetHeader("Authorization", "Bearer ${apiKey}");
Curl:SetHeader("Content-Type", "application/json");
Curl:SetTimeout(30);

// ── Step 4: Event loop ────────────────────────────────────────────────────────
Var:String sender;
Var:String rawMsg;
Var:Boolean got;
Var:Boolean sendOk;

while (true) {
    Net:Check(handle);

    Net:GetMessage(handle, sender, rawMsg, got);
    while (got) {
        Konsol:Print("[${sender}] ${rawMsg}");

        // Parse the incoming JSON payload.
        JSON:Parse(rawMsg, evt);
        Var:String eventType;
        Var:String payload;
        JSON:Get("event", evt, eventType);
        JSON:Get("content", evt, payload);

        String:Trim(eventType, eventType);

        // Look up the system prompt for this event type.
        Var:Boolean hasPrompt;
        Dictionary:Has(eventType, prompts, hasPrompt);

        Var:String reply;
        if (!hasPrompt) {
            reply = "[unknown event type: ${eventType}]";
            Konsol:Print("  Unknown event type '${eventType}' - ignored.");
        } else {
            Var:String sysMsg;
            Dictionary:Get(eventType, prompts, sysMsg);

            Konsol:Print("  Routing '${eventType}' event to LLM...");

            // Build and send the request.
            Var:String safePayload;
            Var:String safeSys;
            String:Replace(payload, "\"", "\\\"", safePayload);
            String:Replace(sysMsg, "\"", "\\\"", safeSys);

            Var:String apiPayload = """{
              "model": "gpt-4o-mini",
              "messages": [
                {"role": "system", "content": "${safeSys}"},
                {"role": "user", "content": "${safePayload}"}
              ],
              "max_tokens": 300
            }""";

            Var:String body;
            try {
                Curl:Post("https://api.openai.com/v1/chat/completions", apiPayload, body);
                Var:Number status;
                Curl:Status(status);
                if (status == 200) {
                    JSON:Parse(body, resp);
                    JSON:Get("choices.0.message.content", resp, reply);
                    String:Trim(reply, reply);
                } else {
                    reply = "[LLM error: HTTP ${status}]";
                }
            } catch (CurlException e) {
                reply = "[LLM error: ${e.message}]";
            }
        }

        // Send the LLM reply back only to the original sender.
        Net:SendTo(handle, sender, reply, sendOk);
        Konsol:Print("  → [${sender}] ${reply}");

        Net:GetMessage(handle, sender, rawMsg, got);
    }
}


Back to Kookbook