fix(devin): add missing support for ATIF v1.7 (#570)

* fix: add mssing support for ATIF v1.7

Co-Authored-By: bmcdonough <18721778+bmcdonough@users.noreply.github.com>

* fix(devin): drop unused MCP-coupled types, dead guard, harden metrics fallback

- Remove the @modelcontextprotocol/sdk JsonSchemaType import and the unused
  ToolDefinition/FunctionDefinition types it served; type Agent.tool_definitions
  as unknown since the parser never reads it (no dependency warranted).
- Remove isImageContentPart: unused, and its `"image" in part` check could never
  be true (image parts carry `source`/`type`, not `image`).
- Use the `type` discriminant in isTextContentPart instead of property presence.
- getMetricsFromStep: fall back to legacy metadata.metrics when step.metrics is
  present but empty, so a partial metrics object cannot silently zero usage.
- Tests: cover the empty step.metrics fallback and image-only message normalization.

---------

Co-authored-by: bmcdonough <18721778+bmcdonough@users.noreply.github.com>
Co-authored-by: AgentSeal <hello@agentseal.org>
This commit is contained in:
Tiago Santos 2026-06-28 18:00:51 +01:00 committed by GitHub
parent 5efb7e9b76
commit b424bf1d3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 566 additions and 47 deletions

View file

@ -4,7 +4,7 @@ Cognition Devin CLI local usage tracking.
- **Source:** `src/providers/devin.ts`
- **Loading:** eager (`src/providers/index.ts`)
- **Test:** `tests/providers/devin.test.ts` (336 lines)
- **Test:** `tests/providers/devin.test.ts`
## Where it reads from
@ -59,39 +59,53 @@ appear in CLI/UI results until configured.
## Storage format
Transcript root is a JSON object following the [ATIF-v1.4 trajectory schema][atif],
with Devin-specific additions such as per-step `metadata`. The parser does not
validate `schema_version`; it only requires a parseable object with `steps[]`.
Transcript root is a JSON object following the [ATIF-v1.7 trajectory schema][atif],
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`, and `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). Similarly, ACU cost is read from
`step.metadata.committed_acu_cost` first, falling back to
`step.extra.committed_acu_cost`.
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_id`
- `metadata.committed_acu_cost`
- `metadata.metrics.input_tokens`
- `metadata.metrics.output_tokens`
- `metadata.metrics.cache_creation_tokens`
- `metadata.metrics.cache_read_tokens`
- `metadata.committed_acu_cost` (or `extra.committed_acu_cost`)
- `metrics.prompt_tokens` (or `metadata.metrics.input_tokens`)
- `metrics.completion_tokens` (or `metadata.metrics.output_tokens`)
- `metrics.extra.cache_creation_input_tokens` (or `metadata.metrics.cache_creation_tokens`)
- `metrics.cached_tokens` (or `metadata.metrics.cache_read_tokens`)
- `metadata.created_at`
- `metadata.generation_model`
- `metadata.generation_model` (or `extra.generation_model`)
- `metadata.request_id`
- `tool_calls[].function_name`
- `observation.results[]` (tool output; not parsed for usage)
User-input steps (`metadata.is_user_input === true`) are skipped. Non-user
steps are included only if they have positive ACU usage or positive token usage.
## Pricing
`metadata.committed_acu_cost` is per step, not cumulative. The provider converts
each step with:
ACU cost is per step, not cumulative. The provider reads
`metadata.committed_acu_cost` first, falling back to
`extra.committed_acu_cost`, then converts with:
```text
costUSD = committed_acu_cost * devin.acuUsdRate
```
Token-only steps are still included when they have positive token metrics, but
their `costUSD` is `0` if `committed_acu_cost` is absent.
their `costUSD` is `0` if `committed_acu_cost` is absent from both locations.
`src/parser.ts` preserves Devin's provider-supplied `costUSD` instead of
re-pricing it through LiteLLM.
@ -150,7 +164,9 @@ The provider name is part of the key via the `devin:` prefix.
## Quirks
- The transcript directory has usage; `sessions.db` is enrichment only.
- `committed_acu_cost` is per-generation/per-step ACU usage. Never treat it as cumulative.
- `committed_acu_cost` is per-generation/per-step ACU usage. Never treat it as cumulative. It can appear in `metadata` (legacy) or `extra` (ATIF v1.7); the provider checks both.
- Token metrics can live in `step.metrics` (standard ATIF) or `step.metadata.metrics` (legacy Devin). The provider checks `step.metrics` first, falling back to `metadata`.
- Step messages can be a plain string or an array of `ContentPart` objects (text/image). The parser normalises both when extracting user messages.
- There is no default ACU-to-USD rate. Missing config intentionally hides Devin.
- Hidden sessions from `sessions.db` are skipped in discovery and parsing.
- Tool names come directly from `tool_calls[].function_name`; the provider assumes valid ATIF tool-call records.
@ -160,16 +176,16 @@ The provider name is part of the key via the `devin:` prefix.
1. First check whether `~/.config/codeburn/config.json` contains a valid
`devin.acuUsdRate`. Without it, no Devin sessions should appear.
2. For usage total bugs, compare against:
2. For usage total bugs, compare against (ACU cost can live in `metadata` or `extra`):
```bash
jq '[.steps[] | select(.metadata.committed_acu_cost != null) | .metadata.committed_acu_cost] | add' ~/.local/share/devin/cli/transcripts/<session>.json
jq '[.steps[] | select(.metadata.is_user_input != true) | (.metadata.committed_acu_cost // .extra.committed_acu_cost // 0)] | add' ~/.local/share/devin/cli/transcripts/<session>.json
```
3. If project/model/timestamp metadata is wrong, inspect `sessions.db`, not the transcript.
4. If a hidden session appears, check the `hidden` column. Discovery can only
hide sessions whose transcript filename matches `sessions.id`; parsing uses
the transcript `session_id` when present.
5. Run `tests/providers/devin.test.ts` after parser changes. It covers ACU conversion, disabled-until-configured behavior, timestamp parsing, deduplication, hidden sessions, and `sessions.db` enrichment.
5. Run `tests/providers/devin.test.ts` after parser changes. It covers ACU conversion, disabled-until-configured behavior, timestamp parsing, deduplication, hidden sessions, `sessions.db` enrichment, ATIF v1.7 multimodal messages, `step.metrics` vs `metadata.metrics` priority, and `extra.committed_acu_cost` fallback.
[atif]: https://github.com/harbor-framework/harbor/blob/main/rfcs/0001-trajectory-format.md