Skip to content

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 →