Devin transcripts carry no committed_acu_cost, so the ACU path priced every session at zero and devin.acuUsdRate hid them all until it was set by hand. Cost now comes from each step's tokens and its generation_model through the shared pricing tables, with the cached share carved out of OpenAI-style prompt_tokens; swe-2-high stays unpriced at $0. Task text is the user turn, then the sessions.db title, then prompt_history. The menubar ACU field, its config store and the config key are gone. Separate commit: gpt-5-3-codex ids resolve to the gpt-5.3-codex pricing row instead of the base gpt-5 row.
7.4 KiB
Devin
Cognition Devin CLI local usage tracking.
- Source:
src/providers/devin.ts - Loading: eager (
src/providers/index.ts) - Test:
tests/providers/devin.test.ts
Where it reads from
Devin CLI data lives under:
~/.local/share/devin/cli/
The MVP usage source is transcript JSON:
~/.local/share/devin/cli/transcripts/*.json
The provider also reads:
~/.local/share/devin/cli/sessions.db
sessions.db is enrichment only. It supplies project path/name, model fallback,
timestamp fallback, and hidden-session filtering. It is not the source of usage
or billing.
Configuration
None. Devin is priced from per-step tokens like every other provider, so it needs no config to appear in CLI/UI results.
Storage format
Transcript root is a JSON object following the ATIF-v1.7 trajectory schema,
with Devin-specific additions such as per-step metadata and extra. The
parser does not validate schema_version; it only requires a parseable object
with steps[].
Core fields include session_id, agent.model_name, agent.extra (Devin
backend/permission info), final_metrics, and steps[].
Steps now support two metric sources. The parser checks step.metrics first
(the standard ATIF location) and falls back to step.metadata.metrics (the
legacy Devin location). committed_acu_cost is ignored wherever it appears.
Messages can be a plain string or an array of ContentPart objects (text or
image), following the ATIF v1.6+ multimodal content model. The parser
normalises both forms when extracting user messages.
Each counted step can provide:
step_idmetrics.prompt_tokens(ormetadata.metrics.input_tokens)metrics.completion_tokens(ormetadata.metrics.output_tokens)metrics.extra.cache_creation_input_tokens(ormetadata.metrics.cache_creation_tokens)metrics.cached_tokens(ormetadata.metrics.cache_read_tokens)metadata.created_atmetadata.generation_model(orextra.generation_model)metadata.request_idtool_calls[].function_nameobservation.results[](tool output; not parsed for usage)
User steps (source === "user" or metadata.is_user_input === true) are skipped. Non-user
steps are included only if they have positive token usage.
Pricing
Per step, from tokens and the model id, through calculateCost() — the same
pricing tables every other provider uses. The pricing id is
metadata.generation_model (or extra.generation_model), falling back to
step.model_name, agent.model_name, then sessions.model; a MODEL_*
placeholder is never used for pricing. gpt-5-3-codex style ids are rewritten
to gpt-5.3-codex before lookup, so they hit their own row instead of
collapsing to the base gpt-5 price.
Devin reports OpenAI-style prompt_tokens with the cached tokens counted
inside it, so the cached share is subtracted from input rather than billed at
the full input rate twice.
A model with no pricing row (today: swe-2-high) costs $0 and warns once,
like any other unpriced model. It is never silently priced off a neighbouring
row.
src/parser.ts preserves Devin's provider-supplied costUSD instead of
re-pricing it, because the call carries Devin's display model name.
sessions.db enrichment
The provider currently reads these columns from sessions:
| Column | Use |
|---|---|
id |
join key with transcript session_id during parsing; discovery uses the transcript filename before .json |
working_directory |
projectPath and derived project name |
model |
model fallback |
title |
task text; project name fallback (falls back to the session's first prompt_history row when empty) |
created_at |
timestamp fallback |
last_activity_at |
preferred session timestamp fallback |
hidden |
skip hidden sessions |
prompt_history supplies the task text for a session Devin has not titled yet.
message_nodes and tool_call_state are not parsed.
Timestamps
Step timestamps come from metadata.created_at, falling back to
sessions.last_activity_at, then sessions.created_at.
Transcript step timestamps are passed through as ATIF string timestamps.
Numeric normalization is only applied to sessions.db timestamps:
- less than
10_000_000_000: seconds - otherwise: milliseconds
Model Resolution
Model names (display and pricing) resolve in this order:
step.metadata.generation_modelstep.model_nametranscript.agent.model_namesessions.modeldevin
Caching
No provider-level cache.
The normal session cache stores parsed provider calls, but Devin is always
reparsed by src/parser.ts because sessions.db can change without the
transcript JSON fingerprint changing.
Deduplication
devin:<sessionId>:<step.step_id>
The provider name is part of the key via the devin: prefix.
Quirks
- The transcript directory has usage;
sessions.dbis enrichment only. committed_acu_costis null in current Devin builds and is ignored; cost comes from tokens.- Token metrics can live in
step.metrics(standard ATIF) orstep.metadata.metrics(legacy Devin). The provider checksstep.metricsfirst, falling back tometadata. - Step messages can be a plain string or an array of
ContentPartobjects (text/image). The parser normalises both when extracting user messages. - Real transcripts mark the user turn with
source: "user"; older ones setmetadata.is_user_input. Either marks a step as the user's, so it is skipped and used as task text. - Hidden sessions from
sessions.dbare skipped in discovery and parsing. - Tool names come directly from
tool_calls[].function_name; the provider assumes valid ATIF tool-call records. - If SQLite is unavailable or
sessions.dbcannot be opened, the provider still parses transcripts without enrichment.
When fixing a bug here
-
For usage total bugs, compare against the transcript's own metrics:
jq '.final_metrics, [.steps[] | select(.metrics.prompt_tokens) | .metrics.prompt_tokens] | add' ~/.local/share/devin/cli/transcripts/<session>.json -
For cost bugs, check which pricing row the step's
generation_modelhits; an unpriced model costs $0 by design. -
If project/model/timestamp metadata is wrong, inspect
sessions.db, not the transcript. -
If a hidden session appears, check the
hiddencolumn. Discovery can only hide sessions whose transcript filename matchessessions.id; parsing uses the transcriptsession_idwhen present. -
Run
tests/providers/devin.test.tsafter parser changes. It covers token pricing against the pricing tables, timestamp parsing, deduplication, hidden sessions,sessions.dbenrichment, ATIF v1.7 multimodal messages, andstep.metricsvsmetadata.metricspriority.