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++ (C++20, 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

#include <algorithm>
#include <cmath>
#include <string>
#include <vector>

namespace cache_breakpoint_planner {

/// Cached reads bill at ~0.1x — the saving on the cached share is ~90%.
inline constexpr double kCacheReadDiscount = 0.1;

/// One prompt session: an id plus its ordered blocks.
struct PromptSession {
    std::string id;
    std::vector<std::string> blocks;
};

/// Place the cache breakpoint AFTER this block index (0-based).
/// -1 = terminal (the shared tail sits at the end of each request).
struct Breakpoint {
    long after_block;
    std::string label;
    std::string reason;
    long cached_tokens;
};

struct PerSessionRow {
    std::string id;
    long total_tokens;
    long unique_tokens;
    double cached_ratio;
};

/// The full plan: shared blocks, breakpoints, per-session rows, savings.
struct BreakpointPlan {
    std::vector<std::string> prefix_blocks;
    long prefix_tokens = 0;
    std::vector<std::string> suffix_blocks;
    long suffix_tokens = 0;
    std::vector<Breakpoint> breakpoints;
    std::vector<PerSessionRow> per_session;
    /// Estimated cost saving across the sessions vs no caching (0-1).
    double estimated_savings = 0.0;
    std::vector<std::string> warnings;
};

/// The `type: 'prose'` path of the tokenEstimator, inlined: every non-empty
/// line costs max(1, round(length / 4)) tokens; empty text is 0.
inline long tok(const std::string &text) {
    if (text.empty()) return 0;
    long tokens = 0;
    size_t start = 0;
    while (start <= text.size()) {
        size_t end = text.find('\n', start);
        if (end == std::string::npos) end = text.size();
        size_t len = end - start;
        if (len > 0) {
            long per = std::lround(static_cast<double>(len) / 4.0);
            tokens += std::max<long>(1, per);
        }
        if (end == text.size()) break;
        start = end + 1;
    }
    return tokens;
}

/// 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 — everything stable before the breakpoint, everything
/// per-request after it.
inline BreakpointPlan plan_breakpoints(const std::vector<PromptSession> &sessions) {
    BreakpointPlan plan;
    std::vector<const PromptSession *> valid;
    for (const auto &s : sessions) valid.push_back(&s);

    if (valid.empty()) {
        plan.warnings.push_back(
            "No sessions given — paste at least two prompts to compare.");
        return plan;
    }
    if (valid.size() == 1) {
        plan.warnings.push_back(
            "Only one session — a prefix needs at least two prompts to detect.");
    }

    // Common leading blocks by position.
    size_t shortest = valid[0]->blocks.size();
    for (const auto *s : valid) shortest = std::min(shortest, s->blocks.size());
    size_t prefix_end = 0;
    while (prefix_end < shortest) {
        bool shared = std::all_of(valid.begin(), valid.end(), [&](const PromptSession *s) {
            return s->blocks[prefix_end] == valid[0]->blocks[prefix_end];
        });
        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) {
        bool shared = std::all_of(valid.begin(), valid.end(), [&](const PromptSession *s) {
            return s->blocks[s->blocks.size() - 1 - suffix_len]
                == valid[0]->blocks[valid[0]->blocks.size() - 1 - suffix_len];
        });
        if (!shared) break;
        suffix_len++;
    }

    auto join = [](const std::vector<std::string> &parts) {
        std::string out;
        for (size_t i = 0; i < parts.size(); i++) {
            if (i > 0) out += '\n';
            out += parts[i];
        }
        return out;
    };

    plan.prefix_blocks.assign(valid[0]->blocks.begin(),
                              valid[0]->blocks.begin() + prefix_end);
    if (suffix_len > 0) {
        plan.suffix_blocks.assign(valid[0]->blocks.end() - suffix_len,
                                  valid[0]->blocks.end());
    }
    plan.prefix_tokens = tok(join(plan.prefix_blocks));
    plan.suffix_tokens = tok(join(plan.suffix_blocks));

    if (!plan.prefix_blocks.empty()) {
        plan.breakpoints.push_back(Breakpoint{
            static_cast<long>(prefix_end) - 1,
            "after the shared prefix",
            std::to_string(plan.prefix_blocks.size())
                + " block(s) identical across every session — cache once, hit on every request.",
            plan.prefix_tokens,
        });
    }
    if (suffix_len > 0) {
        plan.breakpoints.push_back(Breakpoint{
            -1, // terminal: the shared tail sits at the end of each request
            "shared tail",
            std::to_string(suffix_len)
                + " trailing block(s) also identical — extend the cache segment or accept the re-read.",
            plan.suffix_tokens,
        });
    }
    if (plan.breakpoints.empty()) {
        plan.warnings.push_back(
            "No shared leading or trailing blocks — nothing to cache across these sessions.");
    }

    for (const auto *s : valid) {
        long total_tokens = tok(join(s->blocks));
        long unique = std::max<long>(total_tokens - plan.prefix_tokens - plan.suffix_tokens, 0);
        double cached_ratio = total_tokens > 0
            ? std::min(1.0,
                       static_cast<double>(plan.prefix_tokens + plan.suffix_tokens)
                           / static_cast<double>(total_tokens))
            : 0.0;
        plan.per_session.push_back(
            PerSessionRow{s->id, total_tokens, unique, cached_ratio});
    }

    double sum = 0.0;
    for (const auto &row : plan.per_session) sum += static_cast<double>(row.total_tokens);
    double avg_total = plan.per_session.empty() ? 0.0 : sum / plan.per_session.size();
    double cached_share = avg_total > 0.0
        ? std::min(1.0,
                   static_cast<double>(plan.prefix_tokens + plan.suffix_tokens) / avg_total)
        : 0.0;
    plan.estimated_savings = cached_share * (1.0 - kCacheReadDiscount);

    return plan;
}

} // namespace cache_breakpoint_planner

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 →