Skip to content

Cache Breakpoint Planner — C source

Find what your prompts share — common prefix and suffix blocks — and place prompt-cache breakpoints where they pay, with an estimated cost saving. 100% client-side.

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

/*
 * Cache Breakpoint Planner — find the blocks a set of prompts share and
 * place cache breakpoints where they pay.
 *
 * Language: C (C11, standard library only)
 * Port of src/lib/cacheBreakpointPlanner.ts (the canonical TypeScript
 * implementation). javascript.js in this set carries the same port.
 * Tool page: https://dev.cosmolabs.org/tools/cache-breakpoint-planner
 *
 * C shape: a fixed-capacity result struct (caller allocates; blocks are
 * pointers INTO the sessions — no copies, valid while the inputs are).
 */

#include <stdio.h>
#include <string.h>

/* Cached reads bill at ~0.1x — the saving on the cached share is ~90%. */
#define CACHE_READ_DISCOUNT 0.1

/* Raise these caps for larger display examples; the planner saturates. */
#define CBP_MAX_BLOCKS 64
#define CBP_MAX_SESSIONS 32
#define CBP_MAX_BREAKPOINTS 2
#define CBP_MAX_WARNINGS 4

typedef struct {
    const char *id;
    const char *const *blocks; /* ordered prompt blocks */
    size_t block_count;
} cbp_session;

typedef struct {
    long after_block; /* place the breakpoint AFTER this 0-based index; -1 = terminal */
    const char *label;
    char reason[256];
    long cached_tokens;
} cbp_breakpoint;

typedef struct {
    const char *id;
    long total_tokens;
    long unique_tokens;
    double cached_ratio;
} cbp_row;

typedef struct {
    const char *prefix_blocks[CBP_MAX_BLOCKS];
    size_t prefix_count;
    long prefix_tokens;
    const char *suffix_blocks[CBP_MAX_BLOCKS];
    size_t suffix_count;
    long suffix_tokens;
    cbp_breakpoint breakpoints[CBP_MAX_BREAKPOINTS];
    size_t breakpoint_count;
    cbp_row per_session[CBP_MAX_SESSIONS];
    size_t row_count;
    double estimated_savings; /* 0..1 vs no caching */
    char warnings[CBP_MAX_WARNINGS][192];
    size_t warning_count;
} cbp_plan;

/*
 * The `type: 'prose'` path of the tokenEstimator, inlined: every non-empty
 * line costs max(1, round(length / 4)) tokens; empty text is 0.
 */
static long cbp_tok(const char *text) {
    if (text == NULL || text[0] == '\0') return 0;
    long tokens = 0;
    const char *line = text;
    for (const char *p = text;; p++) {
        if (*p == '\n' || *p == '\0') {
            size_t len = (size_t)(p - line);
            if (len > 0) {
                long per = (long)(((double)len / 4.0) + 0.5);
                tokens += per < 1 ? 1 : per;
            }
            if (*p == '\0') break;
            line = p + 1;
        }
    }
    return tokens;
}

/* Joined-token count over a session's blocks (the TS joins with '\n';
 * the joiner newline never adds a line, so a per-block sum is exact). */
static long cbp_tok_blocks(const char *const *blocks, size_t count) {
    long total = 0;
    for (size_t i = 0; i < count; i++) {
        total += cbp_tok(blocks[i]);
    }
    return total;
}

/*
 * Plan cache breakpoints for a set of prompt sessions: find the common
 * leading/trailing blocks across every session and place breakpoints where
 * the cache pays. Returns the filled plan (pointers reference the inputs).
 */
cbp_plan cbp_plan_breakpoints(const cbp_session *sessions, size_t session_count) {
    cbp_plan plan;
    memset(&plan, 0, sizeof(plan));

    if (session_count == 0) {
        strncpy(plan.warnings[plan.warning_count++],
                "No sessions given — paste at least two prompts to compare.",
                sizeof(plan.warnings[0]) - 1);
        return plan;
    }
    if (session_count == 1) {
        strncpy(plan.warnings[plan.warning_count++],
                "Only one session — a prefix needs at least two prompts to detect.",
                sizeof(plan.warnings[0]) - 1);
    }

    /* Common leading blocks by position. */
    size_t shortest = sessions[0].block_count;
    for (size_t s = 1; s < session_count; s++) {
        if (sessions[s].block_count < shortest) shortest = sessions[s].block_count;
    }
    size_t prefix_end = 0;
    while (prefix_end < shortest) {
        int shared = 1;
        for (size_t s = 1; s < session_count; s++) {
            if (strcmp(sessions[s].blocks[prefix_end],
                       sessions[0].blocks[prefix_end]) != 0) {
                shared = 0;
                break;
            }
        }
        if (!shared) break;
        prefix_end++;
    }

    /* Common trailing blocks, matched from each session's own tail, never
     * overlapping the prefix. */
    size_t suffix_len = 0;
    while (suffix_len < shortest - prefix_end) {
        int shared = 1;
        for (size_t s = 1; s < session_count; s++) {
            const char *a = sessions[s].blocks[sessions[s].block_count - 1 - suffix_len];
            const char *b =
                sessions[0].blocks[sessions[0].block_count - 1 - suffix_len];
            if (strcmp(a, b) != 0) {
                shared = 0;
                break;
            }
        }
        if (!shared) break;
        suffix_len++;
    }

    for (size_t i = 0; i < prefix_end && i < CBP_MAX_BLOCKS; i++) {
        plan.prefix_blocks[plan.prefix_count++] = sessions[0].blocks[i];
    }
    if (suffix_len > 0) {
        for (size_t i = sessions[0].block_count - suffix_len;
             i < sessions[0].block_count && plan.suffix_count < CBP_MAX_BLOCKS; i++) {
            plan.suffix_blocks[plan.suffix_count++] = sessions[0].blocks[i];
        }
    }
    plan.prefix_tokens = cbp_tok_blocks(sessions[0].blocks, prefix_end);
    plan.suffix_tokens = suffix_len > 0
        ? cbp_tok_blocks(sessions[0].blocks + (sessions[0].block_count - suffix_len), suffix_len)
        : 0;

    if (plan.prefix_count > 0) {
        cbp_breakpoint *bp = &plan.breakpoints[plan.breakpoint_count++];
        bp->after_block = (long)prefix_end - 1;
        bp->label = "after the shared prefix";
        snprintf(bp->reason, sizeof(bp->reason),
                 "%zu block(s) identical across every session — cache once, hit on every request.",
                 plan.prefix_count);
        bp->cached_tokens = plan.prefix_tokens;
    }
    if (suffix_len > 0) {
        cbp_breakpoint *bp = &plan.breakpoints[plan.breakpoint_count++];
        bp->after_block = -1; /* terminal: the shared tail sits at the end */
        bp->label = "shared tail";
        snprintf(bp->reason, sizeof(bp->reason),
                 "%zu trailing block(s) also identical — extend the cache segment or accept the re-read.",
                 suffix_len);
        bp->cached_tokens = plan.suffix_tokens;
    }
    if (plan.breakpoint_count == 0) {
        strncpy(plan.warnings[plan.warning_count++],
                "No shared leading or trailing blocks — nothing to cache across these sessions.",
                sizeof(plan.warnings[0]) - 1);
    }

    for (size_t s = 0; s < session_count && plan.row_count < CBP_MAX_SESSIONS; s++) {
        cbp_row *row = &plan.per_session[plan.row_count++];
        row->id = sessions[s].id;
        row->total_tokens = cbp_tok_blocks(sessions[s].blocks, sessions[s].block_count);
        long unique = row->total_tokens - plan.prefix_tokens - plan.suffix_tokens;
        row->unique_tokens = unique > 0 ? unique : 0;
        row->cached_ratio = row->total_tokens > 0
            ? ((double)(plan.prefix_tokens + plan.suffix_tokens) / (double)row->total_tokens)
            : 0.0;
        if (row->cached_ratio > 1.0) row->cached_ratio = 1.0;
    }

    long sum_total = 0;
    for (size_t i = 0; i < plan.row_count; i++) {
        sum_total += plan.per_session[i].total_tokens;
    }
    double avg_total = plan.row_count > 0 ? (double)sum_total / (double)plan.row_count : 0.0;
    double cached_share = avg_total > 0.0
        ? ((double)(plan.prefix_tokens + plan.suffix_tokens) / avg_total)
        : 0.0;
    if (cached_share > 1.0) cached_share = 1.0;
    plan.estimated_savings = cached_share * (1.0 - CACHE_READ_DISCOUNT);

    return plan;
}

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 →