Skip to content

fix(copilot): baseline cost tracking and cache token display - #12762

Merged
majdyz merged 16 commits into
devfrom
fix/copilot-cost-tracking
Apr 14, 2026
Merged

fix(copilot): baseline cost tracking and cache token display#12762
majdyz merged 16 commits into
devfrom
fix/copilot-cost-tracking

Conversation

@majdyz

@majdyz majdyz commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Why

The baseline copilot path (OpenAI-compatible / OpenRouter) did not record any cost when the x-total-cost response header was absent, even though token counts were always available. The admin cost dashboard also lacked cache token columns.

What

  • x-total-cost header extraction: Reads the OpenRouter cost header per LLM call in the finally block (so cost is captured even when the stream errors mid-way). Accumulated across multi-round tool-calling turns.
  • Cache token extraction: Extracts prompt_tokens_details.cached_tokens and cache_creation_input_tokens from streaming usage chunks and passes cache_read_tokens/cache_creation_tokens through to persist_and_record_usage for storage in PlatformCostLog.
  • Dashboard cache token display: Adds cache read/write columns to the Raw Logs and By User tables on the admin platform costs dashboard. Adds total_cache_read_tokens and total_cache_creation_tokens to UserCostSummary.
  • No cost estimation: When x-total-cost is absent, cost_usd is left as None and persist_and_record_usage records the entry under tracking_type="tokens". Token-based cost estimation was removed — the platform dashboard already handles per-token cost display, and estimates would introduce inaccuracy in the reported figures.

How

  • In _baseline_llm_caller: extract the x-total-cost header in the finally block; accumulate to state.cost_usd.
  • In _BaselineStreamState: add turn_cache_read_tokens / turn_cache_creation_tokens counters, populated from streaming usage chunks.
  • In persist_and_record_usage / record_cost_log: pass through cache_read_tokens and cache_creation_tokens to PlatformCostEntry.
  • Frontend: add total_cache_read_tokens / total_cache_creation_tokens fields to UserCostSummary and render them as columns in the cost dashboard.

Test plan

  • Verify baseline copilot sessions log cost when x-total-cost header is present
  • Verify cost_usd stays None and token count is logged when header is absent
  • Verify cache tokens appear in the dashboard logs table for sessions using prompt caching
  • Verify the By User tab shows Cache Read and Cache Write columns
  • Unit tests: test_cost_usd_extracted_from_response_header, test_cost_usd_remains_none_when_header_missing, test_cache_tokens_extracted_from_usage_details

…en display

When OpenRouter's x-total-cost header is missing, estimate cost from
token counts using a known model pricing table so cost is always logged.
Also extract cache token details from streaming usage chunks
(prompt_tokens_details.cached_tokens) and pass them through to
PlatformCostLog.

On the dashboard side, add cache read/write columns to the logs table
and user table, and include cache tokens in the UserCostSummary backend
model so they surface in the API response.
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 13, 2026
@majdyz
majdyz requested a review from a team as a code owner April 13, 2026 09:40
@majdyz
majdyz requested review from 0ubbe and kcze and removed request for a team April 13, 2026 09:40
@github-actions github-actions Bot added platform/frontend AutoGPT Platform - Front end platform/backend AutoGPT Platform - Back end labels Apr 13, 2026
@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds per-model OpenRouter token pricing and token-based fallback cost estimation, tracks cache read/creation tokens in baseline LLM streaming, persists those cache metrics to platform cost dashboard and OpenAPI, updates frontend tables to show cache metrics, and adds unit tests for estimation and token extraction.

Changes

Cohort / File(s) Summary
Backend — Baseline LLM service
autogpt_platform/backend/backend/copilot/baseline/service.py
Added _OPENROUTER_MODEL_PRICING and _estimate_cost_from_tokens(). Extended _BaselineStreamState with turn_cache_read_tokens and turn_cache_creation_tokens. _baseline_llm_caller now snapshots per-call token counters, accumulates cached-token details from streamed chunks, and falls back to token-based cost estimation when x-total-cost header is missing. Minor formatting tweaks.
Backend — Baseline service tests
autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
Added tests for _estimate_cost_from_tokens(), fallback cost estimation using per-call token deltas, extraction/accumulation of cached_tokens and cache_creation_input_tokens, and multi-turn delta behavior.
Backend — Platform cost model & aggregation
autogpt_platform/backend/backend/data/platform_cost.py
Added total_cache_read_tokens and total_cache_creation_tokens to UserCostSummary. get_platform_cost_dashboard now fills these fields from Prisma aggregation (cacheReadTokens, cacheCreationTokens). Minor asyncio.gather formatting change.
Backend — Platform cost tests (minor)
autogpt_platform/backend/backend/data/platform_cost_test.py
Minor formatting cleanup (removed blank line); no behavioral change.
Frontend — Logs table (UI)
autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/LogsTable.tsx
Replaced "Tokens" column with "In / Out" and added "Cache (R/W)" column; row cells now show input/output tokens and cache read/creation tokens; adjusted empty-state colSpan.
Frontend — User table (UI)
autogpt_platform/frontend/src/app/(platform)/admin/platform-costs/components/UserTable.tsx
Added "Cache Read" and "Cache Write" columns displaying per-user aggregated cache metrics (formatted or "-"); adjusted empty-state colSpan.
Frontend — OpenAPI schema
autogpt_platform/frontend/src/app/api/openapi.json
Added total_cache_read_tokens and total_cache_creation_tokens to UserCostSummary schema with default 0.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant BaselineService as Baseline Service
  participant OpenRouter as OpenRouter Stream
  participant State as BaselineState
  participant DB as Platform DB / Prisma
  participant Frontend

  Client->>BaselineService: send chat completion request
  BaselineService->>OpenRouter: open streaming request (model, input)
  OpenRouter-->>BaselineService: stream chunks (usage + optional x-total-cost header)
  BaselineService->>State: snapshot turn_prompt/completion tokens at call start
  BaselineService->>State: accumulate chunk usage, cached_tokens, cache_creation_input_tokens
  alt x-total-cost header present
    BaselineService->>State: set cost_usd from header
  else header missing
    BaselineService->>BaselineService: call _estimate_cost_from_tokens(model, prompt_delta, completion_delta)
    BaselineService->>State: set estimated cost_usd
  end
  BaselineService->>DB: persist tokens, cache metrics, and cost
  Frontend->>DB: query dashboard (includes cache fields)
  DB-->>Frontend: return dashboard data
  Frontend->>Client: render tables with cache metrics
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • 0ubbe
  • kcze
  • ntindle
  • Pwuts

Poem

🐰 I nibble tokens, count each crunchy bite,
cached reads and writes beneath the moonlight.
When headers hide cost, I do the math instead,
carrots of data flow to dashboard beds.
Hop—frontend and backend tally what I fed.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly aligns with the main changes: fixing cost tracking for baseline copilot when x-total-cost header is missing and adding cache token display to the dashboard.
Description check ✅ Passed The description clearly explains the why, what, and how of the changes, covering cost tracking fallback, cache token extraction, and dashboard display improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/copilot-cost-tracking

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 3 conflict(s), 0 medium risk, 2 low risk (out of 5 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py
@codecov

codecov Bot commented Apr 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.05882% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.59%. Comparing base (9de22eb) to head (d7653ac).
⚠️ Report is 5 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #12762      +/-   ##
==========================================
+ Coverage   63.46%   63.59%   +0.13%     
==========================================
  Files        1815     1815              
  Lines      131479   132034     +555     
  Branches    14304    14327      +23     
==========================================
+ Hits        83439    83970     +531     
- Misses      45436    45463      +27     
+ Partials     2604     2601       -3     
Flag Coverage Δ
platform-backend 75.04% <97.05%> (+0.13%) ⬆️
platform-frontend 15.80% <ø> (-0.01%) ⬇️
platform-frontend-e2e 27.99% <ø> (-0.21%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 75.04% <97.05%> (+0.13%) ⬆️
Platform Frontend 23.78% <ø> (-0.09%) ⬇️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…ion_tokens extraction

Bug 1: Fallback cost estimation was using accumulated turn_prompt_tokens /
turn_completion_tokens across all tool-call rounds, causing compounding
over-estimation on the 2nd+ turn. Snapshot token counts before each call and
pass only the per-call delta to _estimate_cost_from_tokens.

Bug 2: turn_cache_creation_tokens was defined but never populated. Extract
cache_creation_input_tokens from prompt_tokens_details (available from some
providers such as Anthropic via OpenRouter).

Add regression tests for both fixes.
Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py Outdated
Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py
Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/baseline/service.py (1)

1373-1392: ⚠️ Potential issue | 🟠 Major

Backfilled tokens still never produce a fallback cost_usd.

When a provider omits both x-total-cost and streaming usage, Lines 1355-1371 backfill token counts with tiktoken, but the pricing fallback only runs inside _baseline_llm_caller. persist_and_record_usage(...) still receives cost_usd=None for the exact provider behavior this PR is trying to cover.

💡 Proposed fix
         if (
             state.turn_prompt_tokens == 0
             and state.turn_completion_tokens == 0
             and not (_stream_error and not state.assistant_text)
         ):
@@
             logger.info(
                 "[Baseline] No streaming usage reported; estimated tokens: "
                 "prompt=%d, completion=%d",
                 state.turn_prompt_tokens,
                 state.turn_completion_tokens,
             )
 
+        if state.cost_usd is None and (
+            state.turn_prompt_tokens > 0 or state.turn_completion_tokens > 0
+        ):
+            estimated = _estimate_cost_from_tokens(
+                active_model,
+                state.turn_prompt_tokens,
+                state.turn_completion_tokens,
+            )
+            if estimated is not None:
+                state.cost_usd = estimated
+
         # Persist token usage to session and record for rate limiting.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/baseline/service.py` around lines
1373 - 1392, persist_and_record_usage is still called with cost_usd=None when
tokens were backfilled; detect when state.cost_usd is None after you compute
uncached_prompt (i.e., the provider omitted both x-total-cost and streaming
usage) and compute a fallback cost using the same pricing fallback logic used in
_baseline_llm_caller (apply the model pricing estimation for active_model using
uncached_prompt and state.turn_completion_tokens), assign that value to cost_usd
and pass it into persist_and_record_usage so the persisted record includes an
estimated cost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/baseline/service.py`:
- Around line 418-423: The locals prompt_tokens_before and
completion_tokens_before must be initialized before calling
client.chat.completions.create(...) to avoid UnboundLocalError in the finally
block if the API call raises; set prompt_tokens_before =
state.turn_prompt_tokens and completion_tokens_before =
state.turn_completion_tokens (or sensible defaults) immediately before invoking
client.chat.completions.create so they are always defined, then proceed with the
API await and use those snapshots in the finally/fallback cost estimation logic.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/baseline/service.py`:
- Around line 1373-1392: persist_and_record_usage is still called with
cost_usd=None when tokens were backfilled; detect when state.cost_usd is None
after you compute uncached_prompt (i.e., the provider omitted both x-total-cost
and streaming usage) and compute a fallback cost using the same pricing fallback
logic used in _baseline_llm_caller (apply the model pricing estimation for
active_model using uncached_prompt and state.turn_completion_tokens), assign
that value to cost_usd and pass it into persist_and_record_usage so the
persisted record includes an estimated cost.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c24b982e-afc3-4362-ade1-a6aa3e30f9c6

📥 Commits

Reviewing files that changed from the base of the PR and between f6c7d1e and c6af520.

📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/copilot/baseline/service.py
  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: integration_test
  • GitHub Check: check API types
  • GitHub Check: Seer Code Review
  • GitHub Check: type-check (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: end-to-end tests
  • GitHub Check: Check PR Status
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

autogpt_platform/backend/**/*.py: Use poetry run ... command for executing Python package dependencies
Use top-level imports only — avoid local/inner imports except for lazy imports of heavy optional dependencies like openpyxl
Use absolute imports with from backend.module import ... for cross-package imports; single-dot relative imports are acceptable for sibling modules within the same package; avoid double-dot relative imports
Do not use duck typing — avoid hasattr/getattr/isinstance for type dispatch; use typed interfaces/unions/protocols instead
Use Pydantic models over dataclass/namedtuple/dict for structured data
Do not use linter suppressors — no # type: ignore, # noqa, # pyright: ignore; fix the type/code instead
Prefer list comprehensions over manual loop-and-append patterns
Use early return with guard clauses first to avoid deep nesting
Use %s for deferred interpolation in debug log statements for efficiency; use f-strings elsewhere for readability (e.g., logger.debug("Processing %s items", count) vs logger.info(f"Processing {count} items"))
Sanitize error paths by using os.path.basename() in error messages to avoid leaking directory structure
Be aware of TOCTOU (Time-Of-Check-Time-Of-Use) issues — avoid check-then-act patterns for file access and credit charging
Use transaction=True for Redis pipelines to ensure atomicity on multi-step operations
Use max(0, value) guards for computed values that should never be negative
Keep files under ~300 lines; if a file grows beyond this, split by responsibility (extract helpers, models, or a sub-module into a new file)
Keep functions under ~40 lines; extract named helpers when a function grows longer
...

Files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
autogpt_platform/{backend,autogpt_libs}/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/AGENTS.md)

autogpt_platform/backend/**/*_test.py: Use pytest with snapshot testing for API responses
Colocate test files with source files using *_test.py naming convention
Mock at boundaries — mock where the symbol is used, not where it's defined; after refactoring, update mock targets to match new module paths
Use AsyncMock from unittest.mock for async functions in tests
When writing tests, use Test-Driven Development (TDD): write failing tests marked with @pytest.mark.xfail before implementation, then remove the marker once the implementation is complete
When creating snapshots in tests, use poetry run pytest path/to/test.py --snapshot-update; always review snapshot changes with git diff before committing

Files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
🧠 Learnings (17)
📓 Common learnings
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12651
File: autogpt_platform/frontend/src/app/api/openapi.json:8653-8696
Timestamp: 2026-04-02T14:27:41.807Z
Learning: Repo: Significant-Gravitas/AutoGPT — Platform costs
The PlatformCostLog.duration is stored in DB but intentionally omitted from the CostLogRow API response to keep the raw logs compact. Do not flag this omission; suggest documenting the intent in the route description if needed.
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.
📚 Learning: 2026-03-30T11:49:37.770Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12604
File: autogpt_platform/backend/backend/copilot/sdk/security_hooks.py:165-171
Timestamp: 2026-03-30T11:49:37.770Z
Learning: In `autogpt_platform/backend/backend/copilot/sdk/security_hooks.py`, the `web_search_count` and `total_tool_call_count` circuit-breaker counters in `create_security_hooks` are intentionally per-turn (closure-local), not per-session. Hooks are recreated per stream invocation in `service.py`, so counters reset each turn. This is an accepted v1 design: it caps a single runaway turn (incident d2f7cba3: 179 WebSearch calls, $20.66). True per-session persistence via Redis is deferred to a later iteration. Do not flag these as a per-session vs. per-turn mismatch bug.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-04-01T04:17:41.600Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12632
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:0-0
Timestamp: 2026-04-01T04:17:41.600Z
Learning: When reviewing AutoGPT Copilot tool implementations, accept that `readOnlyHint=True` (provided via `ToolAnnotations`) may be applied unconditionally to *all* tools—even tools that have side effects (e.g., `bash_exec`, `write_workspace_file`, or other write/save operations). Do **not** flag these tools for having `readOnlyHint=True`; this is intentional to enable fully-parallel dispatch by the Anthropic SDK/CLI and has been E2E validated. Only flag `readOnlyHint` issues if they conflict with the established `ToolAnnotations` behavior (e.g., missing/incorrect propagation relative to the intended annotation mechanism).

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-16T16:35:40.236Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/api/features/workflow_import.py:54-63
Timestamp: 2026-03-16T16:35:40.236Z
Learning: Avoid using the word 'competitor' in public-facing identifiers and text. Use neutral naming for API paths, model names, function names, and UI text. Examples: rename 'CompetitorFormat' to 'SourcePlatform', 'convert_competitor_workflow' to 'convert_workflow', '/competitor-workflow' to '/workflow'. Apply this guideline to files under autogpt_platform/backend and autogpt_platform/frontend.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-31T15:37:38.626Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12623
File: autogpt_platform/backend/backend/copilot/tools/agent_generator/fixer.py:37-47
Timestamp: 2026-03-31T15:37:38.626Z
Learning: When validating/constructing Anthropic API model IDs in Significant-Gravitas/AutoGPT, allow the hyphen-separated Claude Opus 4.6 model ID `claude-opus-4-6` (it corresponds to `LlmModel.CLAUDE_4_6_OPUS` in `autogpt_platform/backend/backend/blocks/llm.py`). Do NOT require the dot-separated form in Anthropic contexts. Only OpenRouter routing variants should use the dot separator (e.g., `anthropic/claude-opus-4.6`); `claude-opus-4-6` should be treated as correct when passed to Anthropic, and flagged only if it’s used in the OpenRouter path where the dot form is expected.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py
  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-09T10:50:43.907Z
Learnt from: Bentlybro
Repo: Significant-Gravitas/AutoGPT PR: 0
File: :0-0
Timestamp: 2026-03-09T10:50:43.907Z
Learning: Repo: Significant-Gravitas/AutoGPT — File: autogpt_platform/backend/backend/blocks/llm.py
For xAI Grok models accessed via OpenRouter, the API returns `null` for `max_completion_tokens`. The convention in this codebase is to use the model's context window size as the `max_output_tokens` value in ModelMetadata. For example, Grok 3 uses 131072 (128k) and Grok 4 uses 262144 (256k). Do not flag these as incorrect max output token values.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-13T15:49:44.961Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12385
File: autogpt_platform/backend/backend/copilot/rate_limit.py:0-0
Timestamp: 2026-03-13T15:49:44.961Z
Learning: In `autogpt_platform/backend/backend/copilot/rate_limit.py`, the original per-session token window (with a TTL-based reset) was replaced with fixed daily and weekly windows. `resets_at` is now derived from `_daily_reset_time()` (midnight UTC) and `_weekly_reset_time()` (next Monday 00:00 UTC) — deterministic fixed-boundary calculations that require no Redis TTL introspection.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-17T10:57:12.953Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12440
File: autogpt_platform/backend/backend/copilot/workflow_import/converter.py:0-0
Timestamp: 2026-03-17T10:57:12.953Z
Learning: In Significant-Gravitas/AutoGPT PR `#12440`, `autogpt_platform/backend/backend/copilot/workflow_import/converter.py` was fully rewritten (commit 732960e2d) to no longer make direct LLM/OpenAI API calls. The converter now builds a structured text prompt for AutoPilot/CoPilot instead. There is no `response.choices` access or any direct LLM client usage in this file. Do not flag `response.choices` access or LLM client initialization patterns as issues in this file.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-12T14:42:40.552Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12385
File: autogpt_platform/backend/backend/copilot/rate_limit.py:141-170
Timestamp: 2026-03-12T14:42:40.552Z
Learning: In Significant-Gravitas/AutoGPT, `check_rate_limit` in `autogpt_platform/backend/backend/copilot/rate_limit.py` is intentionally a "pre-turn soft check" (not a hard atomic reservation). Because LLM token counts are unknown before generation completes, a strict check-and-reserve is impractical. The TOCTOU race (two concurrent turns both passing the pre-check and both committing via `record_token_usage`) is an accepted trade-off. If stricter enforcement is ever needed, the approach is a Lua script doing GET+INCRBY atomically in Redis.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-10T08:39:22.025Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12356
File: autogpt_platform/backend/backend/copilot/constants.py:9-12
Timestamp: 2026-03-10T08:39:22.025Z
Learning: In Significant-Gravitas/AutoGPT PR `#12356`, the `COPILOT_SYNTHETIC_ID_PREFIX = "copilot-"` check in `create_auto_approval_record` (human_review.py) is intentional and safe. The `graph_exec_id` passed to this function comes from server-side `PendingHumanReview` DB records (not from user input); the API only accepts `node_exec_id` from users. Synthetic `copilot-*` IDs are only ever created server-side in `run_block.py`. The prefix skip avoids a DB lookup for a `AgentGraphExecution` record that legitimately does not exist for CoPilot sessions, while `user_id` scoping is enforced at the auth layer and on the resulting auto-approval record.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-17T06:48:26.471Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12445
File: autogpt_platform/backend/backend/copilot/sdk/service.py:1071-1072
Timestamp: 2026-03-17T06:48:26.471Z
Learning: In Significant-Gravitas/AutoGPT (autogpt_platform), the AI SDK enforces `z.strictObject({type, errorText})` on SSE `StreamError` responses, so additional fields like `retryable: bool` cannot be added to `StreamError` or serialized via `to_sse()`. Instead, retry signaling for transient Anthropic API errors is done via the `COPILOT_RETRYABLE_ERROR_PREFIX` constant prepended to persisted session messages (in `ChatMessage.content`). The frontend detects retryable errors by checking `markerType === "retryable_error"` from `parseSpecialMarkers()` — no SSE schema changes and no string matching on error text. This pattern was established in PR `#12445`, commit 64d82797b.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-01T07:59:02.311Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12213
File: autogpt_platform/frontend/src/app/api/openapi.json:10030-10037
Timestamp: 2026-03-01T07:59:02.311Z
Learning: Repo: Significant-Gravitas/AutoGPT PR: 12213 — For MCP manual token storage, backend model autogpt_platform/backend/backend/api/features/mcp/routes.py defines MCPStoreTokenRequest.token as Pydantic SecretStr with a min length constraint, which generates OpenAPI schema metadata (format: "password", writeOnly: true, minLength: 1) in autogpt_platform/frontend/src/app/api/openapi.json. Prefer SecretStr (with length constraints) for sensitive request fields so generated TS clients and docs treat them as secrets.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-04T23:58:18.476Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:18.476Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
📚 Learning: 2026-03-16T07:34:53.523Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12385
File: autogpt_platform/backend/backend/copilot/rate_limit.py:0-0
Timestamp: 2026-03-16T07:34:53.523Z
Learning: In `autogpt_platform/backend/backend/copilot/rate_limit.py`, the `record_token_usage` Redis warning log omits `user_id` entirely. The final log message is `"Redis unavailable for recording token usage (tokens=%d)"` with only the token count — no user identifier (full or truncated) is included.

Applied to files:

  • autogpt_platform/backend/backend/copilot/baseline/service.py
🔇 Additional comments (1)
autogpt_platform/backend/backend/copilot/baseline/service_unit_test.py (1)

988-1052: Good regression coverage for the cumulative-turn pricing path.

This test exercises the exact stateful failure mode that used to compound fallback cost across tool rounds, so it should be very effective at preventing that regression from coming back.

Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py Outdated
The backend added total_cache_read_tokens and total_cache_creation_tokens
to UserCostSummary but the OpenAPI spec was not updated, causing frontend
build failures.
majdyz added a commit that referenced this pull request Apr 13, 2026
Resolve conflicts between cost dashboard PR (#12757) and cache token
columns PR (#12762). Keep all HEAD-side functionality (percentile
queries, histogram buckets, cost-bearing request counts, unfiltered
aggregate) while retaining cache token fields from the incoming side.
majdyz added a commit that referenced this pull request Apr 13, 2026
Keep HEAD's pre-drain count logic for transcript loading and drain error
handling, and merge incoming cache token extraction tests from PR #12762.
…ndLocalError

When client.chat.completions.create() raises (e.g. network timeout),
the finally block referenced prompt_tokens_before/completion_tokens_before
which were only assigned after the API call inside the try block, causing
an UnboundLocalError that masked the original exception.

Move the snapshots to before the try block so the finally block can always
reference them safely even when the API call fails.
@github-actions

Copy link
Copy Markdown
Contributor

Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly.

@github-actions github-actions Bot added conflicts Automatically applied to PRs with merge conflicts and removed conflicts Automatically applied to PRs with merge conflicts labels Apr 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly.

…f hardcoded discount table

Remove the hardcoded _CACHE_READ_DISCOUNT dict (Anthropic 10%, OpenAI 50%).
Instead, extract the pricing.cache_read field from OpenRouter's /api/v1/models
response and use it directly. Models where OpenRouter does not publish a
cache_read rate fall back to the full input rate (safe over-estimate).

Cache tuple type: (input_rate, output_rate) → (input_rate, output_rate, cache_read_rate | None)
@majdyz

majdyz commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 012ea167d: Replaced the hardcoded _CACHE_READ_DISCOUNT table with per-model cache_read rates sourced directly from OpenRouter's /api/v1/models response (pricing.cache_read). For models where OpenRouter doesn't publish a cache_read rate, cache tokens fall back to the full input rate (safe over-estimate). The discount constants (anthropic/: 0.10, openai/: 0.50) are removed.

Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py
majdyz added 2 commits April 14, 2026 20:06
…led StreamUsage

Two gaps closed:

1. When a provider omits both `x-total-cost` and streaming `usage`,
   `stream_chat_completion_baseline` backfills token counts via tiktoken
   but previously left `state.cost_usd = None`. Now we call
   `await _estimate_cost_from_tokens(active_model, ...)` immediately
   after the tiktoken backfill so `persist_and_record_usage` receives
   a non-None cost when the model is in the OpenRouter pricing response.

2. The `StreamUsage` event was reporting raw `state.turn_prompt_tokens`
   (includes cached reads), making the frontend token count inconsistent
   with `cost_usd` which already applied the cache discount. Now we
   compute `billed_prompt = max(0, turn_prompt_tokens - turn_cache_read_tokens)`
   and yield that instead.

Also removes unused `_ThinkingStripper` import from service_unit_test.py
(caught by ruff) to fix the lint CI failure.
@majdyz

majdyz commented Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Fixed in 8420ad0c1: After the tiktoken token backfill (when a provider omits both x-total-cost and streaming usage), we now call await _estimate_cost_from_tokens(active_model, state.turn_prompt_tokens, state.turn_completion_tokens) guarded by state.cost_usd is None. When the model is in the OpenRouter pricing response, this sets state.cost_usd so persist_and_record_usage never receives cost_usd=None for the tiktoken path.

Comment thread autogpt_platform/backend/backend/copilot/baseline/service.py Outdated
majdyz added 2 commits April 14, 2026 20:28
…ing cache fetch

_fetch_openrouter_pricing() called from the finally block can block for
up to 10s on a cold cache, and concurrent callers all seeing an expired
cache would each make separate HTTP requests simultaneously.

Two fixes:
1. Add a lazy asyncio.Lock (_OPENROUTER_PRICING_LOCK) with double-checked
   locking so only one coroutine makes the HTTP request; others wait and
   reuse the result.
2. Update _OPENROUTER_PRICING_CACHE_FETCHED_AT *before* the HTTP request
   so that a failed or timed-out fetch advances the "next allowed fetch"
   window, creating a natural backoff instead of every subsequent request
   immediately retrying on OpenRouter outage.
@github-actions github-actions Bot added size/l and removed size/xl labels Apr 14, 2026
@majdyz majdyz changed the title fix(copilot): baseline cost tracking fallback and cache token display fix(copilot): baseline cost tracking and cache token display Apr 14, 2026
@majdyz
majdyz merged commit b3a5838 into dev Apr 14, 2026
40 checks passed
@majdyz
majdyz deleted the fix/copilot-cost-tracking branch April 14, 2026 14:08
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to ✅ Done in AutoGPT development kanban Apr 14, 2026
@github-project-automation github-project-automation Bot moved this to Done in Frontend Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/l

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant