Session API

June 30, 2026 · View on GitHub

Flemma tracks token usage and costs for every API request in a global session object. The session lives in memory for the lifetime of the Neovim instance and is accessible through the flemma.session module. Session cost tracking and subscription rate-limit windows are the harness's economics and budget observability.

Module functions

session.get()

Returns the global Session instance that tracks all requests across all buffers.

local session = require("flemma.session").get()

session.now()

Returns the current wall-clock time as a Unix timestamp with microsecond precision (e.g. 1700000042.123456). Used internally for request timestamps but available for general use.

local ts = require("flemma.session").now()

Reading the current session

Two ways to observe session state: poll the singleton on demand, or subscribe to the request:finished hook (see docs/extending.md). The hook fires after a request is recorded and carries the just-added flemma.session.Request as data.request, so you can update UI without polling.

Note

A request is only added to the session when the resolved model has registry pricing. Requests against custom or unknown models — where require("flemma.provider.registry").get_model_info(...).pricing returns nil — silently skip session recording. If you're iterating on a new model and see no request:finished payload and an unchanged session, that's why.

local session = require("flemma.session").get()

-- Aggregate stats
print("Requests:", session:get_request_count())
print("Input tokens:", session:get_total_input_tokens())
print("Output tokens:", session:get_total_output_tokens())
print("Thinking tokens:", session:get_total_thoughts_tokens())
print("Total cost: $" .. string.format("%.4f", session:get_total_cost()))

-- Iterate individual requests
for _, request in ipairs(session.requests) do
  print(string.format(
    "%s/%s  in=%d out=%d  $%.4f  %s",
    request.provider,
    request.model,
    request:get_total_input_tokens(),
    request:get_total_output_tokens(),
    request:get_total_cost(),
    request.filepath or "(unnamed)"
  ))
end

-- Inspect the most recent request
local latest = session:get_latest_request()
if latest then
  print("Last model:", latest.provider .. "/" .. latest.model)
end

-- Filter by file
local req = session:get_latest_request_for_filepath(vim.fn.expand("%:p"))

Request fields

Each request stores raw data -- tokens, per-million prices, cache pricing, and timestamps -- so costs are always derived from the underlying components.

FieldDescription
provider, modelProvider and model that handled the request
input_tokens, output_tokens, thoughts_tokensRaw token counts (see output_has_thoughts for how thinking tokens relate to output)
input_price, output_priceUSD per million tokens (snapshot at request time)
cache_read_input_tokens, cache_creation_input_tokensCache token counts
cache_read_price, cache_write_priceUSD per million cache tokens (nil when the provider does not support caching)
output_has_thoughtsWhether output_tokens already includes thinking tokens (true for Anthropic, OpenAI, and Codex; false for Vertex and Moonshot/Kimi, and the default for providers that do not set it)
started_at, completed_atTimestamps as seconds since epoch with microsecond precision (e.g. 1700000042.123456)
filepath, bufnrSource buffer identifier (filepath is the resolved absolute path; either may be nil)
rate_limitsSubscription rate-limit snapshot (flemma.session.RateLimitSnapshot) when the provider reports one (e.g. the experimental Codex / ChatGPT-subscription provider); nil for usage-billed providers

Subscription rate limits

Providers that bill against a subscription quota rather than per-token attach a rate-limit snapshot to each request through the rate_limits field. Currently only the experimental Codex / ChatGPT-subscription provider populates it (parsed from the response headers); every other provider leaves it nil.

TypeShape
flemma.session.RateLimitSnapshotplan_name? (human-readable plan, e.g. "Plus"/"Pro"), windows (a list of RateLimitWindow, ordered shortest-window-first / most-urgent-first)
flemma.session.RateLimitWindowused_percent (0–100 consumed), window_seconds (rolling window length), resets_at? (Unix seconds when the window resets)

These feed the usage bar and the lualine resolvers (see docs/integrations.md); the 5-hour and weekly Codex windows are the typical entries.

Request methods

MethodReturnsDescription
get_input_cost()numberInput cost in USD (cache-aware: uses cache-specific prices when available, falls back to input price)
get_output_cost()numberOutput cost in USD (adds thoughts_tokens when they are separate from output_tokens)
get_total_cost()numberSum of input and output cost
get_total_input_tokens()numberinput_tokens + cache_read_input_tokens + cache_creation_input_tokens (the API reports input_tokens as only the non-cached portion)
get_total_output_tokens()numberTotal output tokens including thinking (provider-aware: includes thoughts_tokens only when separate)

Session methods

MethodReturnsDescription
get_request_count()numberNumber of requests in the session
get_total_input_tokens()numberSum of get_total_input_tokens() across all requests
get_total_output_tokens()numberSum of get_total_output_tokens() across all requests
get_total_thoughts_tokens()numberSum of raw thoughts_tokens across all requests
get_total_input_cost()numberTotal input cost in USD
get_total_output_cost()numberTotal output cost in USD
get_total_cost()numberTotal cost in USD
get_latest_request()Request|nilMost recent request, or nil if none
get_latest_request_for_filepath(filepath)Request|nilMost recent request matching the given absolute filepath
reset()Clear all requests
load(requests_data)Replace session with a list of RequestOpts tables

Recipes

Resetting the session

Session:reset() clears all accumulated requests, zeroing token and cost counters without restarting Neovim:

require("flemma.session").get():reset()

Saving and restoring a session

Session:load() accepts a list of option tables in the same format as add_request() and replaces the current session contents. Combined with reading session.requests, this enables crude persistence:

local json = require("flemma.utilities.json")

-- Save to a JSON file
local session = require("flemma.session").get()
local encoded = json.encode(session.requests)
vim.fn.writefile({ encoded }, vim.fn.stdpath("data") .. "/flemma_session.json")

-- Restore from a saved file
local path = vim.fn.stdpath("data") .. "/flemma_session.json"
local lines = vim.fn.readfile(path)
if #lines > 0 then
  require("flemma.session").get():load(json.decode(table.concat(lines, "\n")))
end