Skip to content

Mock LLM Responder — Zig source

Generate deterministic mock LLM API responses - chat completion JSON, SSE event streams with chunk timing, and a replay curl - for testing clients without an API key.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// mock-llm-responder — Zig port: deterministic mock LLM responses (seeded PRNG + token math).
const std = @import("std");

/// Vocabulary for the poem-ish lorem scenarios.
const poem_words = [_][]const u8{
    "cosmos", "nebula", "quantum", "signal", "photon", "drift", "orbit", "vector",
    "cipher", "lumen", "aurora", "echo", "helix", "nova", "pulse", "tide",
    "vertex", "zenith", "quasar", "ion", "halo", "flux", "prism", "comet",
};
/// FNV-1a 32-bit hash — turns the spec into a deterministic seed / id.
fn hashString(s: []const u8) u32 {
    var h: u32 = 0x811c9dc5;
    for (s) |c| { h ^= c; h = h *% 0x01000193; }
    return h;
}
/// mulberry32 — tiny seeded PRNG; same seed, same sequence, forever.
const Rng = struct {
    a: u32,
    fn next(self: *Rng) f64 {
        self.a +%= 0x6d2b79f5;
        var t: u32 = (self.a ^ (self.a >> 15)) *% (self.a | 1);
        t = (t +% ((t ^ (t >> 7)) *% (t | 61))) ^ t;
        return @as(f64, @floatFromInt(t ^ (t >> 14))) / 4294967296.0;
    }
};
/// ~4 chars per token, floor of 1 — deterministic, no tokenizer needed.
fn tokenCount(text: []const u8) u32 {
    if (text.len == 0) return 0;
    return @max(1, @as(u32, @intCast((text.len + 3) / 4)));
}
/// One poem line of 5-7 vocabulary words, assembled into `buf`.
fn makeLine(rng: *Rng, buf: []u8) []const u8 {
    const n = 5 + @as(usize, @intFromFloat(rng.next() * 3));
    var len: usize = 0;
    var i: usize = 0;
    while (i < n) : (i += 1) {
        const w = poem_words[@as(usize, @intFromFloat(rng.next() * poem_words.len))];
        if (i > 0) { buf[len] = ' '; len += 1; }
        @memcpy(buf[len..][0..w.len], w);
        len += w.len;
    }
    return buf[0..len];
}
/// Poem-ish lorem, grown line by line until the token budget is full.
fn buildPoem(seed: u32, max_tokens: u32, out: []u8) []const u8 {
    var rng = Rng{ .a = seed };
    var line_buf: [256]u8 = undefined;
    var text: [2048]u8 = undefined;
    var text_len: usize = 0;
    while (true) {
        const line = makeLine(&rng, &line_buf);
        var cand: [2048]u8 = undefined;
        var n: usize = 0;
        if (text_len > 0) {
            @memcpy(cand[0..text_len], text[0..text_len]);
            cand[text_len] = '\n';
            n = text_len + 1;
        }
        @memcpy(cand[n..][0..line.len], line);
        n += line.len;
        if (text_len > 0 and tokenCount(cand[0..n]) > max_tokens) break;
        @memcpy(text[0..n], cand[0..n]);
        text_len = n;
    }
    // Fit the budget: cut at max_tokens * 4 bytes, then trimEnd.
    var end: usize = @min(text_len, max_tokens * 4);
    while (end > 0 and (text[end - 1] == ' ' or text[end - 1] == '\n')) end -= 1;
    @memcpy(out[0..end], text[0..end]);
    return out[0..end];
}
pub fn main() !void {
    const spec = "streamed-lorem|mock-gpt-4o-mini|24";
    var poem_buf: [2048]u8 = undefined;
    const poem = buildPoem(hashString(spec ++ "|poem"), 24, &poem_buf);
    const stdout = std.io.getStdOut().writer();
    try stdout.print("id=chatcmpl-mock-{x:0>8}\n", .{hashString(spec ++ "|id")});
    try stdout.print("{s}\n", .{poem});
    try stdout.print("tokens={d}\n", .{tokenCount(poem)});
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →