Cache Breakpoint Planner — Ruby 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 Ruby 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: Ruby (3.x, zero dependencies)
# Port of src/lib/cacheBreakpointPlanner.ts (the canonical TypeScript
# implementation). Method names are snake_case per Ruby convention.
# Tool page: https://dev.cosmolabs.org/tools/cache-breakpoint-planner
module CacheBreakpointPlanner
# Cached reads bill at ~0.1x — the saving on the cached share is ~90%.
CACHE_READ_DISCOUNT = 0.1
# One prompt session: an id plus its ordered blocks.
Session = Struct.new(:id, :blocks, keyword_init: true)
# Place the cache breakpoint AFTER this block index (0-based); -1 = terminal.
Breakpoint = Struct.new(:after_block, :label, :reason, :cached_tokens, keyword_init: true)
PerSessionRow = Struct.new(
:id, :total_tokens, :unique_tokens, :cached_ratio, keyword_init: true
)
# The full plan: shared blocks, breakpoints, rows, savings.
Plan = Struct.new(
:prefix_blocks, :prefix_tokens, :suffix_blocks, :suffix_tokens,
:breakpoints, :per_session, :estimated_savings, :warnings,
keyword_init: true
)
module_function
# The `type: 'prose'` path of the tokenEstimator, inlined: every non-empty
# line costs max(1, round(length / 4)) tokens; empty text is 0.
def tok(text)
return 0 if text.nil? || text.empty?
text.split("\n", -1).sum do |line|
line.empty? ? 0 : [1, (line.length / 4.0).round].max
end
end
# 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.
def plan_breakpoints(sessions)
warnings = []
valid = sessions.select { |s| s.blocks.is_a?(Array) }
if valid.empty?
return Plan.new(
warnings: ['No sessions given — paste at least two prompts to compare.']
)
end
if valid.length == 1
warnings << 'Only one session — a prefix needs at least two prompts to detect.'
end
# Common leading blocks by position.
shortest = valid.map { |s| s.blocks.length }.min
prefix_end = 0
while prefix_end < shortest &&
valid.all? { |s| s.blocks[prefix_end] == valid.first.blocks[prefix_end] }
prefix_end += 1
end
# Common trailing blocks, matched from each session's own tail, never
# overlapping the prefix.
suffix_len = 0
while suffix_len < shortest - prefix_end &&
valid.all? do |s|
s.blocks[s.blocks.length - 1 - suffix_len] ==
valid.first.blocks[valid.first.blocks.length - 1 - suffix_len]
end
suffix_len += 1
end
prefix_blocks = valid.first.blocks.first(prefix_end)
suffix_blocks = suffix_len.positive? ? valid.first.blocks.last(suffix_len) : []
prefix_tokens = tok(prefix_blocks.join("\n"))
suffix_tokens = tok(suffix_blocks.join("\n"))
breakpoints = []
if prefix_blocks.any?
breakpoints << Breakpoint.new(
after_block: prefix_end - 1,
label: 'after the shared prefix',
reason: "#{prefix_blocks.length} block(s) identical across every session — " \
'cache once, hit on every request.',
cached_tokens: prefix_tokens
)
end
if suffix_len.positive?
breakpoints << Breakpoint.new(
after_block: -1, # terminal: the shared tail sits at the end
label: 'shared tail',
reason: "#{suffix_len} trailing block(s) also identical — extend the cache " \
'segment or accept the re-read.',
cached_tokens: suffix_tokens
)
end
if breakpoints.empty?
warnings << 'No shared leading or trailing blocks — nothing to cache across ' \
'these sessions.'
end
per_session = valid.map do |s|
total_tokens = tok(s.blocks.join("\n"))
unique = [total_tokens - prefix_tokens - suffix_tokens, 0].max
cached_ratio = if total_tokens.positive?
[(prefix_tokens + suffix_tokens).to_f / total_tokens, 1.0].min
else
0.0
end
PerSessionRow.new(
id: s.id, total_tokens: total_tokens,
unique_tokens: unique, cached_ratio: cached_ratio
)
end
avg_total = per_session.sum { |p| p.total_tokens }.to_f / per_session.length
cached_share = if avg_total.positive?
[(prefix_tokens + suffix_tokens) / avg_total, 1.0].min
else
0.0
end
estimated_savings = cached_share * (1 - CACHE_READ_DISCOUNT)
Plan.new(
prefix_blocks: prefix_blocks,
prefix_tokens: prefix_tokens,
suffix_blocks: suffix_blocks,
suffix_tokens: suffix_tokens,
breakpoints: breakpoints,
per_session: per_session,
estimated_savings: estimated_savings,
warnings: warnings
)
end
end
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 →