Cache Breakpoint Planner — Zig 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 Zig 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: Zig (0.13+, stdlib only)
// Port of src/lib/cacheBreakpointPlanner.ts (the canonical TypeScript
// implementation). Plan strings/slices reference the INPUT sessions (no
// copies) — keep the inputs alive while using the plan. Reason strings are
// formatted into fixed buffers.
//
// Tool page: https://dev.cosmolabs.org/tools/cache-breakpoint-planner
const std = @import("std");
/// Cached reads bill at ~0.1x — the saving on the cached share is ~90%.
pub const cache_read_discount: f64 = 0.1;
/// One prompt session: an id plus its ordered blocks.
pub const Session = struct {
id: []const u8,
blocks: []const []const u8,
};
/// Place the cache breakpoint AFTER this block index (0-based).
pub const Breakpoint = struct {
after_block: i64, // -1 = terminal (the shared tail sits at the end)
label: []const u8,
reason: [256]u8,
reason_len: usize,
cached_tokens: i64,
pub fn reasonText(self: *const Breakpoint) []const u8 {
return self.reason[0..self.reason_len];
}
};
pub const Row = struct {
id: []const u8,
total_tokens: i64,
unique_tokens: i64,
cached_ratio: f64,
};
/// Raise these caps for larger display examples; the planner saturates.
pub const MAX_BLOCKS = 64;
pub const MAX_SESSIONS = 32;
pub const MAX_BREAKPOINTS = 2;
pub const MAX_WARNINGS = 4;
pub const Plan = struct {
prefix_blocks: []const []const u8 = &.{},
prefix_tokens: i64 = 0,
suffix_blocks: []const []const u8 = &.{},
suffix_tokens: i64 = 0,
breakpoints: [MAX_BREAKPOINTS]Breakpoint = undefined,
breakpoint_count: usize = 0,
rows: [MAX_SESSIONS]Row = undefined,
row_count: usize = 0,
/// Estimated cost saving across the sessions vs no caching (0-1).
estimated_savings: f64 = 0,
warnings: [MAX_WARNINGS][192]u8 = undefined,
warning_lens: [MAX_WARNINGS]usize = undefined,
warning_count: usize = 0,
pub fn warningText(self: *const Plan, i: usize) []const u8 {
return self.warnings[i][0..self.warning_lens[i]];
}
};
/// The `type: 'prose'` path of the tokenEstimator, inlined: every non-empty
/// line costs max(1, round(length / 4)) tokens; empty text is 0.
fn tok(text: []const u8) i64 {
if (text.len == 0) return 0;
var tokens: i64 = 0;
var it = std.mem.splitScalar(u8, text, '\n');
while (it.next()) |line| {
if (line.len > 0) {
const per: i64 = @intFromFloat(@as(f64, @floatFromInt(line.len)) / 4.0 + 0.5);
tokens += @max(1, per);
}
}
return tokens;
}
fn tokBlocks(blocks: []const []const u8) i64 {
var total: i64 = 0;
for (blocks) |b| total += tok(b);
return total;
}
fn warn(plan: *Plan, comptime fmt: []const u8, args: anytype) void {
if (plan.warning_count >= MAX_WARNINGS) return;
const i = plan.warning_count;
const out = std.fmt.bufPrint(&plan.warnings[i], fmt, args) catch
std.fmt.bufPrint(&plan.warnings[i], "{s}", .{"(warning truncated)"}) catch return;
plan.warning_lens[i] = out.len;
plan.warning_count += 1;
}
/// 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.
pub fn planBreakpoints(sessions: []const Session) Plan {
var plan: Plan = .{};
if (sessions.len == 0) {
warn(&plan, "No sessions given — paste at least two prompts to compare.", .{});
return plan;
}
if (sessions.len == 1) {
warn(&plan, "Only one session — a prefix needs at least two prompts to detect.", .{});
}
// Common leading blocks by position.
var shortest: usize = sessions[0].blocks.len;
for (sessions[1..]) |s| {
shortest = @min(shortest, s.blocks.len);
}
var prefix_end: usize = 0;
while (prefix_end < shortest) {
var shared = true;
for (sessions) |s| {
if (!std.mem.eql(u8, s.blocks[prefix_end], sessions[0].blocks[prefix_end])) {
shared = false;
break;
}
}
if (!shared) break;
prefix_end += 1;
}
// Common trailing blocks, matched from each session's own tail, never
// overlapping the prefix.
var suffix_len: usize = 0;
while (suffix_len < shortest - prefix_end) {
var shared = true;
for (sessions) |s| {
const a = s.blocks[s.blocks.len - 1 - suffix_len];
const b = sessions[0].blocks[sessions[0].blocks.len - 1 - suffix_len];
if (!std.mem.eql(u8, a, b)) {
shared = false;
break;
}
}
if (!shared) break;
suffix_len += 1;
}
plan.prefix_blocks = sessions[0].blocks[0..@min(prefix_end, MAX_BLOCKS)];
if (suffix_len > 0) {
const n = sessions[0].blocks.len;
plan.suffix_blocks = sessions[0].blocks[n - suffix_len .. n];
}
plan.prefix_tokens = tokBlocks(plan.prefix_blocks);
plan.suffix_tokens = tokBlocks(plan.suffix_blocks);
if (plan.prefix_blocks.len > 0) {
const bp = &plan.breakpoints[plan.breakpoint_count];
bp.* = .{
.after_block = @as(i64, @intCast(prefix_end)) - 1,
.label = "after the shared prefix",
.reason = undefined,
.reason_len = 0,
.cached_tokens = plan.prefix_tokens,
};
const out = std.fmt.bufPrint(&bp.reason, "{d} block(s) identical across every session — cache once, hit on every request.", .{plan.prefix_blocks.len}) catch "";
bp.reason_len = out.len;
plan.breakpoint_count += 1;
}
if (suffix_len > 0) {
const bp = &plan.breakpoints[plan.breakpoint_count];
bp.* = .{
.after_block = -1, // terminal: the shared tail sits at the end
.label = "shared tail",
.reason = undefined,
.reason_len = 0,
.cached_tokens = plan.suffix_tokens,
};
const out = std.fmt.bufPrint(&bp.reason, "{d} trailing block(s) also identical — extend the cache segment or accept the re-read.", .{suffix_len}) catch "";
bp.reason_len = out.len;
plan.breakpoint_count += 1;
}
if (plan.breakpoint_count == 0) {
warn(&plan, "No shared leading or trailing blocks — nothing to cache across these sessions.", .{});
}
for (sessions, 0..) |s, i| {
if (i >= MAX_SESSIONS) break;
const row = &plan.rows[plan.row_count];
const total = tokBlocks(s.blocks);
const unique = @max(total - plan.prefix_tokens - plan.suffix_tokens, 0);
row.* = .{
.id = s.id,
.total_tokens = total,
.unique_tokens = unique,
.cached_ratio = if (total > 0)
@min(@as(f64, @floatFromInt(plan.prefix_tokens + plan.suffix_tokens)) / @as(f64, @floatFromInt(total)), 1.0)
else
0.0,
};
plan.row_count += 1;
}
var sum: i64 = 0;
for (plan.rows[0..plan.row_count]) |r| sum += r.total_tokens;
const avg_total: f64 = if (plan.row_count > 0)
@as(f64, @floatFromInt(sum)) / @as(f64, @floatFromInt(plan.row_count))
else
0.0;
const cached_share: f64 = if (avg_total > 0)
@min(@as(f64, @floatFromInt(plan.prefix_tokens + plan.suffix_tokens)) / avg_total, 1.0)
else
0.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 →