Skip to content

Read the canonical AI usage block and map legacy token keys at ingest - #1698

Merged
jaywedgeworth22 merged 5 commits into
mainfrom
claude/UM-1592-ai-usage-schema
Oct 18, 2026
Merged

jaywedgeworth22 merged 5 commits into
mainfrom
claude/UM-1592-ai-usage-schema

Conversation

@jaywedgeworth22

@jaywedgeworth22 jaywedgeworth22 commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Summary

Fixes UM-1592.  Every producer sent its own token metadata keys, so Usage Monitor could not aggregate or price AI usage the same way across producers.  congress-trading-shared v2.8.1 (Simple-With-Us/congress-trading-shared#378, #381) adds an optional strict ai block to the v2 event.  This PR teaches ingest to read it.

  • Accept and persist the block. It lands in event metadata under reserved keys a producer cannot set: _aiModelRequested, _aiModelServed, _aiHarness, _aiSeat, _aiTokensInput, _aiTokensOutput, _aiTokensCacheRead, _aiTokensCacheWrite5m, _aiTokensCacheWrite1h, _aiTokensReasoning, _aiDurationMs, _aiTtftMs, _aiBillingMode, _aiCostSource, _aiSource (block or legacy).  Since the UM-1590 per-request store has now landed, each event that carried the typed block also gets one AiRequest row (sources: ingest, dedupe key ingest:<idempotencyKey>), best effort so a store failure never fails the ACK.  Legacy-mapped rows get none, because collector rows are slices or window totals.
  • Map legacy keys at ingest (v1 and v2) onto the same keys: promptTokens, completionTokens, cachedTokens, cacheWriteTokens, cacheWriteOneHourTokens, inputTokens, outputTokens, cachedInputTokens, and tokenType / label: token:<type> slices (names resolved through UM-1591's canonicalTokenType, so 1-hour aliases and cacheTtl: "1h" land in cacheWrite1h), plus collector harness, seat, billingMode, durationMs, ttftMs and output-slice reasoningTokens.  Prompt figures include cache, so cache counts are subtracted to get uncached input.  A slice uses only its own quantity, because BotFleet repeats the whole turn's totals on every slice.
  • Cost derivation uses canonical tokens. Every bucket is priced at its own rate by served model, then requested model, then keyRef (first that resolves), with 1-hour cache writes going to UM-1591's TokenBreakdown.cacheCreation1h.  Reasoning is inside output and never priced twice.  Still metadata-only and still behind INGEST_COST_DERIVATION_ENABLED.
  • Replay safety. A producer replaying a row stored before this deploy would now carry the _ai* projection.  The idempotency comparison ignores the projection when only one side has it, so the replay dedupes instead of returning 409.  When both sides have it, it is compared as usual.  Server-computed _derivedCost* stamps are always ignored in that comparison.
  • Bumps congress-trading-shared to v2.8.1 (lockfile edited for that one entry only) and documents the block in docs/api-contract.md.

No prod env changes.  Existing rows are not backfilled; aggregations that read label: token:<type> are unchanged.

Verification

  • npx tsc --noEmit clean.
  • vitest after merging main: ai-usage-metadata, usage-telemetry, derive-ingest-cost, model-pricing*, external-usage-events*, ingest route, OTLP routes and mappers (18 files, 293 passed).
  • eslint on changed files: no errors.

🤖 Generated with Claude Code

Every producer sent its own token metadata keys, so AI usage could not be
aggregated or priced the same way across producers.  Shared v2.8.1 adds an
optional strict `ai` block to the v2 event; Usage Monitor now accepts it.

- Persist the block under reserved `_ai*` metadata keys (model requested and
  served, harness, seat, the six token buckets, duration, TTFT, billing mode,
  cost source, and `_aiSource`).  Producer metadata cannot set them.
- Map the legacy keys (promptTokens, completionTokens, cachedTokens,
  cacheWriteTokens, cacheWriteOneHourTokens, inputTokens, outputTokens,
  cachedInputTokens, and tokenType / token:<type> slices) onto the same keys
  for v1 and v2 events.  A tokenType slice uses only its own quantity, so
  BotFleet's repeated turn totals are not counted once per slice.
- Ingest cost derivation prices the canonical tokens by served model, then
  requested model, then keyRef, including 1-hour cache writes priced from
  the catalog's above_1hr rate.
- A replay of a row stored before the projection existed dedupes instead of
  raising an idempotency collision.
- Bump congress-trading-shared to v2.8.1 and document the block in
  docs/api-contract.md.

Fixes UM-1592

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 18, 2026

Copy link
Copy Markdown

UM-1592

@jaywedgeworth22
jaywedgeworth22 enabled auto-merge (squash) October 18, 2026 01:21
@kody-ai

This comment has been minimized.

jaywedgeworth22 and others added 2 commits October 17, 2026 20:22
…ge-schema

# Conflicts:
#	src/lib/external-usage-events.ts
The DSH and Clutch collectors (UM-1594) already send harness, billingMode,
durationMs and the output slice's reasoningTokens in metadata.  Carry them
onto the reserved _ai* keys so legacy collector rows match the typed block.

Refs UM-1592

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/lib/external-usage-events.ts
Comment thread src/lib/pricing/derive-ingest-cost.ts Outdated
@kody-ai

kody-ai Bot commented Oct 18, 2026

Copy link
Copy Markdown

kody code-review Business Logic medium

Business Rules Validation

Task: 4906370138 — OpenRouter provider: adapter + live prod row (MONET, owner-directed 2026-07-16)
Task Link: https://github.com/Simple-With-Us/Usage-Monitor/issues/378
Status: Issues Found
Confidence: high

Intent Comparison

  • Task intent: Add an OpenRouter provider adapter with management-key awareness (/key tier detection, credits + calendar-MTD from /activity with day-31 guard, per-key breakdown in rawData, inference-key graceful degradation), create a live prod row via session-authed API, surface keyPreview (sk-or-...37d3) per the display rule.
  • PR intent: Implement UM-1592 — canonical AI usage metadata persistence across producers. Bumps congress-trading-shared to v2.8.1, introduces ai-usage-metadata.ts with reserved _ai* keys, maps legacy producer metadata onto canonical tokens, updates ingest and cost derivation, and adds replay-safety logic to idempotency comparison.
  • Alignment: scope_mismatch. The PR diff touches the AI usage metadata / cost-derivation domain and the shared-contract layer; it does not touch the providers domain, any OpenRouter client, the /key or /activity endpoints, the key preview display rule, or any prod-row provisioning path.

Findings

MUST_FIX: PR scope does not match the task scope (OpenRouter provider adapter)

Requirement: "PR #375 f1e054b: management-key-aware adapter (/key tier detection; management: credits + calendar-MTD from /activity with day-31 guard + per-key breakdown in rawData; inference keys degrade gracefully)." (AC #1)
Missing in code: No evidence in this PR diff of an OpenRouter adapter, /key tier detection, /activity endpoint handling, day-31 calendar-MTD guard, rawData per-key breakdown, or graceful inference-key degradation. The changed files (docs/api-contract.md, package.json, package-lock.json, src/lib/ai-usage-metadata.ts, src/lib/external-usage-events.ts, src/lib/pricing/derive-ingest-cost.ts, src/lib/pricing/model-pricing.ts, src/lib/usage-telemetry.ts, and related tests) belong to the ingest / shared-contract / cost-derivation domain, not the providers / OpenRouter domain.
Suggested action: Replace this PR with the OpenRouter adapter implementation (or open the correct PR for UM-1592 and link it to a different issue). The task explicitly names f1e054b for the OpenRouter work; this diff is unrelated.

MUST_FIX: No evidence of prod-row provisioning for OpenRouter in this PR diff

Requirement: "Prod row created via session-authed API with the owner's management key (secret-handoff, never printed); first poll green: balance $25.00, MTD $0. Row shows keyPreview sk-or-...37d3 per the display rule." (AC #1)
Missing in code: No evidence in this PR diff of any prod-row creation flow, session-authed API call, management-key secret handoff, balance/MTD first-poll handling, or the sk-or-...37d3 keyPreview display rule. The diff contains no provider-registration, provisioning, or display-rule code.
Suggested action: Add the provisioning + display logic in the providers/prod-row domain, or scope this PR to UM-1592 and ship the OpenRouter work in its own PR.

MUST_FIX: No evidence of catalog follow-ups required by the task in this PR diff

Requirement: "Follow-ups in catalog: multi-workspace key enumeration, per-app cost attribution via api_key_hash-filtered activity." (AC #1)
Missing in code: No evidence in this PR diff of api_key_hash-filtered activity handling, multi-workspace key enumeration, or per-app cost attribution specific to OpenRouter. The catalog touchpoints in this diff are model-pricing.ts (cache_creation_input_token_cost_above_1hr) and derive-ingest-cost.ts, which are generic AI-usage changes, not OpenRouter follow-ups.
Suggested action: Track the catalog follow-ups in the OpenRouter follow-up list rather than in this UM-1592 PR.

INFO: Mirror/read-only contract for this issue

Requirement: "Edit the effort board (docs/EFFORT-LOG.md / the live board), not this issue. This issue is reconciled automatically from the board and manual edits here will be overwritten or ignored." (AC #2)
Observation: This PR diff does not edit docs/EFFORT-LOG.md. Because the PR's subject (UM-1592) is outside the task's stated scope (OpenRouter adapter), the mirror/board contract is the natural place to reflect the correct linkage. No code change is required for AC #2 itself; the gap is upstream (wrong PR for this issue).
Suggested action: Move the UM-1592 changelog entry to docs/EFFORT-LOG.md and link the UM-1592 PR there; keep this issue reserved for the OpenRouter adapter work referenced as PR #375 / f1e054b.

Requirements Verified


Analysis performed by Kodus AI Business Rules Validator


💡 This validation runs automatically only on the first review of a pull request. To run it again, comment @kody -v business-logic.

Comment thread src/lib/pricing/derive-ingest-cost.ts
…replay

Review findings on #1698:
- Cost derivation now tries the served model, then the requested model, then
  keyRef, so a served id the catalog lacks no longer hides a priceable one.
- The idempotency comparison ignores the server-computed _derivedCost* stamps.
  A replay derived from canonical tokens (or under a newer pricing snapshot)
  no longer reads as a producer change and returns 409.

Refs UM-1592

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kody-ai

kody-ai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Kody Review Complete

Great news! 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ✅

Access your configuration settings here.

​

@kody-ai

kody-ai Bot commented Oct 18, 2026

Copy link
Copy Markdown

kody code-review Kody Rules medium

Missing edge-case test coverage for readCanonicalAiModelCandidates (src/lib/ai-usage-metadata.ts) and withoutDerivedCostMetadata (src/lib/external-usage-events.ts), which ship without vitest cases asserting undefined/null metadata, empty or non-string _aiModelServed/_aiModelRequested values, de-duplication when both slots match, non-object inputs (array, null, primitive) returned unchanged, objects with no derived-cost keys returned unchanged, and derived-cost-only objects reduced to null—regressions will slip past npm test. Add co-located vitest cases asserting each scenario.

Kody rule violation: Every exported schema or utility ships with tests and must typecheck

… to AiRequest

Resolves the pricing conflicts in favour of UM-1591's TokenBreakdown and
1-hour rate handling, and routes legacy tokenType slices through its
canonicalTokenType (cacheWrite1h, cacheCreation + cacheTtl 1h, and aliases).

The UM-1590 per-request store landed, so a v2 event that carried the typed
`ai` block now also gets one AiRequest row (sources "ingest", dedupe key
ingest:<idempotencyKey>).  Best effort:  the usage event is the record of
truth, so a store failure is logged and the batch is still acknowledged.

Refs UM-1592

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jaywedgeworth22
jaywedgeworth22 merged commit 14c75c3 into main Oct 18, 2026
10 of 11 checks passed
@jaywedgeworth22
jaywedgeworth22 deleted the claude/UM-1592-ai-usage-schema branch October 18, 2026 01:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant