Skip to content

feat(cost): Anthropic and OpenAI as cost sources - #2577

Open
Makisuo wants to merge 6 commits into
mainfrom
feat/cost-anthropic-openai
Open

Makisuo wants to merge 6 commits into
mainfrom
feat/cost-anthropic-openai

fix(cost): OpenAI costs group_by in the bracketed form OpenAI's SDKs …

effdb8e
Select commit
Loading
Failed to load commit list.
Maple Review Bot / Maple / review succeeded Nov 13, 2026 in 2m 55s

Confidence 10/10 · No issues found

🟢 Confidence 10/10 · safe to merge. Contained, well-tested provider work; the only open question is the vendors' live contracts, which no test can cover.
no findings · tests covered · risk high · 3/3 new units observable

Adds Anthropic and OpenAI as pasted-credential cost sources: key check, sealed connection, daily sync into cost_daily, plus the Cost page and lab entries. Safe to merge; the open VcsWebhookEnqueueFailure and McpOAuthProtocolError issues raised in apps/api/src/runtime/http-graph.ts come from that file's existing wiring and this diff neither fixes nor worsens them.

  • AnthropicCostConnectionService and OpenAiCostConnectionService check a vendor key, then seal it through CostConnectionStore
  • planCostSync gives both providers a three-month daily history with a four-day head
  • Each vendor's normalize.ts folds a day's lines into cost_daily rows, cents to dollars for Anthropic
  • The three pasted-credential routes now share cost-connection-handlers.ts

Before merge

  • Manual · Call both vendors once with a real admin key: Anthropic accepting ending_at of tomorrow, the group_by[] spelling, and how far back each serves

Fixed since the last review

  • ✅ F1 · OpenAI costs query sends group_by, not the group_by[] the endpoint takes

Production impact

Open errors in the changed files (2)
Issue Service Occurrences File
@maple/api/routes/VcsWebhookEnqueueFailure (2 issues) maple-api 9 apps/api/src/runtime/http-graph.ts
@maple/backend/errors/McpOAuthProtocolError maple-api 4 apps/api/src/runtime/http-graph.ts

After this merges, Maple checks whether they stop.

Telemetry this change adds and removes (17)
  • ➕ span name HttpAnthropicCost.set · apps/api/src/routes/internal/anthropic-cost.http.ts:15
  • ➕ span name HttpAnthropicCost.remove · apps/api/src/routes/internal/anthropic-cost.http.ts:19
  • ➕ span name HttpOpenAiCost.set · apps/api/src/routes/internal/openai-cost.http.ts:15
  • ➕ span name HttpOpenAiCost.remove · apps/api/src/routes/internal/openai-cost.http.ts:19
  • ➕ span name AnthropicCostConnectionService.validateWithAnthropic · packages/backend/src/services/cost/AnthropicCostConnectionService.ts:73
  • ➕ span name AnthropicCostConnectionService.set · packages/backend/src/services/cost/AnthropicCostConnectionService.ts:88
  • ➕ span name AnthropicCostConnectionService.remove · packages/backend/src/services/cost/AnthropicCostConnectionService.ts:127
  • ➕ span name OpenAiCostConnectionService.validateWithOpenAi · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:71
  • ➕ attribute maple.cost.openai.pages · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:99
  • ➕ attribute maple.cost.openai.organization_header · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:100
  • ➕ attribute maple.cost.openai.organizations · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:101
  • ➕ attribute maple.cost.openai.has_results · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:102
  • ➕ span name OpenAiCostConnectionService.set · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:125
  • ➕ span name OpenAiCostConnectionService.remove · packages/backend/src/services/cost/OpenAiCostConnectionService.ts:164
  • ➕ span name AnthropicCostProvider.pull · packages/backend/src/services/cost/providers/anthropic/AnthropicCostProvider.ts:92
  • ➕ attribute maple.cost.anthropic.pages · packages/backend/src/services/cost/providers/anthropic/AnthropicCostProvider.ts:108
  • ➕ span name OpenAiCostProvider.pull · packages/backend/src/services/cost/providers/openai/OpenAiCostProvider.ts:62
What was checked
  • group_by[] is sent as repeated params, pinned in OpenAiCostProvider.test.ts and AnthropicCostConnectionService.test.ts
  • Rows carry the connection's stored accountId, so a replaced key cannot restamp a source (OpenAiCostProvider.ts:97)
  • couldBeAdminKey refuses anything unshaped before a request, pinned by the refusal test at AnthropicCostConnectionService.test.ts:108
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
PUT/DELETE /internal/cost/anthropic and /internal/cost/openai inbound HTTP yes Effect.withSpan("HttpAnthropicCost.set") / HttpOpenAiCost.set in the new *-cost.http.ts
Anthropic and OpenAI vendor cost calls outbound HTTP yes Effect.annotateSpans("peer.service", ...) plus maple.cost.* status attributes in each *-cost-api.ts
Scheduled cost sync pull per source background worker yes Effect.fn("OpenAiCostProvider.pull") and the Anthropic equivalent, with days, pages, rows and error reason

effdb8e · Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.