Skip to content

feat: add self-describing billed usage to TokenUsage - #896

Merged
AlemTuzlak merged 10 commits into
TanStack:mainfrom
season179:feat/billed-usage-unit
Aug 19, 2026
Merged

feat: add self-describing billed usage to TokenUsage#896
AlemTuzlak merged 10 commits into
TanStack:mainfrom
season179:feat/billed-usage-unit

Conversation

@season179

@season179season179 commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes#816.

Non-token billed quantities (seconds of audio/video, fal's endpoint units) are currently reported as bare counts on unitsBilled / durationSeconds, so consumers have to guess the unit from provider identity. This PR adds an optional structured billed field to TokenUsage that pairs the quantity with the unit it is denominated in:

usage.billed// { quantity: 8, unit: 'seconds' }

Design notes (where this deviates from the issue proposal)

The issue proposed a required flat billingUnit field. This PR uses an optional billed: { quantity, unit } object instead, for three reasons:

  • A flat unit field doesn't say which quantity it describes — durationSeconds and unitsBilled can coexist, so billingUnit: 'seconds' would still be ambiguous. The object shape forces quantity and unit to travel together.
  • Making it required would be a runtime lie: usage objects cross the SSE wire and server-function JSON boundaries, where version skew between client and server would surface undefined in a field typed as required. Optional matches reality — token-only activities simply don't set it (the token fields are already self-describing).
  • Required would also break every existing TokenUsage literal (~50 test files and any userland constructor) for no information gain.

BillingUnit is an open string union ('tokens' | 'seconds' | 'characters' | 'images' | 'videos' | 'megapixels' | 'requests' | 'units' | (string & {})) so common units autocomplete while provider-specific ones stay representable. 'units' marks an opaque provider-defined unit (fal's endpoint units, priced via fal's pricing API).

One premise of the issue didn't hold up: the Gemini video adapter reports no usage at all — its durationSeconds is request config sent to Veo, and the operation response carries no billing signal — so there was nothing to align there.

Changes

  • @tanstack/ai-event-client: BillingUnit + BilledUsage types; TokenUsage.billed; unitsBilled / durationSeconds deprecated (still populated). Re-exported from @tanstack/ai.
  • Producers: fal adapters report { quantity, unit: 'units' } from the x-fal-billable-units header; Grok Imagine video { quantity, unit: 'seconds' }; OpenAI (gpt-4o duration usage + whisper verbose_json) and Grok STT transcription { quantity, unit: 'seconds' }.
  • otelMiddleware: emits the pair as tanstack.ai.usage.billed_quantity / tanstack.ai.usage.billed_unit span attributes, guarded so a non-finite quantity never emits a dangling unit. Deprecated attributes still emitted for backward compatibility.
  • Example: ts-react-media labels billed usage from billed.unit instead of inferring from cost/provider.
  • Docs: media generation pages, OTel attribute tables (the units_billed attribute row was previously missing entirely), Grok adapter page, media-generation skill.

Tests

  • Unit tests across ai-fal / ai-grok / ai-openai / ai for the new field, including a NaN-guard case for the OTel attributes.
  • New E2E spec: drives generateTranscription (whisper-1, duration-billed) through otelMiddleware against the aimock fixture and asserts the billed span attributes end-to-end. The two ~100-line duplicated in-memory tracers in the otel E2E routes were deduplicated into a shared helper along the way.
  • Note: testing/e2e/fixtures/transcription/basic.json turned out to be a dead fixture — its userMessage match clause can never match because aimock builds transcription requests with empty messages. The live mock is the programmatic onTranscription in global-setup.ts, which now carries the duration the new spec asserts on. Left the dead fixture untouched; can remove it here or in a follow-up if preferred.

pnpm test:pr and the full E2E suite pass locally.

Summary by CodeRabbit

  • New Features
    • Added first-class usage.billed: { quantity, unit } for media and transcription, with legacy billed fields still populated for compatibility.
    • OpenTelemetry middleware now emits tanstack.ai.usage.billed_quantity / tanstack.ai.usage.billed_unit.
  • Bug Fixes
    • Aligned provider adapters to report billed quantities/units consistently (including duration-billed transcription and billed video seconds).
  • Documentation
    • Updated docs, examples, and media/skill guidance to use usage.billed instead of usage.unitsBilled.
  • Tests
    • Expanded unit and end-to-end coverage, including a new /api/otel-transcription test asserting billed span attributes.

@coderabbitai

coderabbitaiBot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

Walkthrough

Adds usage.billed { quantity, unit } to TokenUsage, updates Fal/Grok/OpenAI usage mapping to populate it, emits new OTel billed span attributes, and refreshes docs, examples, and e2e transcription tracing coverage.

Changes

Billed usage rollout

Layer / File(s)Summary
Billing types and usage contract
packages/ai-event-client/src/index.ts, packages/ai/src/types.ts
Adds BillingUnit and BilledUsage, extends TokenUsage with billed, deprecates durationSeconds, and re-exports the new types.
Adapter usage mapping
packages/ai-fal/..., packages/ai-grok/..., packages/ai-openai/...
Fal, Grok, and OpenAI usage mapping now populate usage.billed, with tests updated to assert the new shape.
OTel usage attributes
packages/ai/src/middlewares/usage-attributes.ts, packages/ai/tests/middlewares/otel.test.ts
Emits tanstack.ai.usage.billed_quantity and tanstack.ai.usage.billed_unit when billed usage is present.
Documentation and media examples
docs/..., examples/ts-react-media/..., packages/ai/skills/..., .changeset/...
Updates documentation, examples, skill guidance, changeset metadata, and documentation timestamps to describe usage.billed.
Shared e2e tracer and request parsing
testing/e2e/src/lib/..., testing/e2e/src/routes/api.otel-{media,usage}.ts
Extracts shared tracer and request-body helpers and updates existing OTel routes to import them.
OTel transcription route and test
testing/e2e/src/routes/api.otel-transcription.ts, testing/e2e/src/routeTree.gen.ts, testing/e2e/global-setup.ts, testing/e2e/tests/middleware.spec.ts
Adds the transcription OTel route, registers it in the route tree, seeds a transcription duration fixture, and verifies billed span attributes in e2e.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
participant PlaywrightTest
participant OtelTranscriptionRoute
participant TranscriptionAdapter
participant OtelMiddleware
participant LocalCaptureTracer
PlaywrightTest->>OtelTranscriptionRoute: POST audio, provider, testId
OtelTranscriptionRoute->>TranscriptionAdapter: generateTranscription()
TranscriptionAdapter->>OtelMiddleware: provide billed usage
OtelMiddleware->>LocalCaptureTracer: record billed quantity and unit
OtelTranscriptionRoute-->>PlaywrightTest: return captured spans
Loading

Suggested reviewers:alemtuzlak

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Linked Issues check⚠️ WarningThe PR does not meet issue #816's required billingUnit contract because it implements an optional billed object instead.Update TokenUsage and related adapters/docs to satisfy the linked issue's required billingUnit field, or revise the issue scope if this alternative is intended.
Out of Scope Changes check⚠️ WarningThe PR includes unrelated persistence-durability route and docs-navigation changes that are not tied to the billed-usage objective.Move the persistence-durability and docs-config/navigation updates into a separate PR unless they are required for this change.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Title check✅ PassedThe title clearly summarizes the main change: adding self-describing billed usage to TokenUsage.
Description check✅ PassedThe description covers the summary, design notes, changes, and tests; only optional checklist and release-impact sections are omitted.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitaicoderabbitaiBot 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.

🧹 Nitpick comments (2)
testing/e2e/src/routes/api.otel-transcription.ts (1)

8-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate isRecord/recordFromBody with api.otel-media.ts.

These two helpers are byte-for-byte duplicates of the ones in api.otel-media.ts. Given this PR just extracted createLocalCaptureTracer into a shared otel-local-tracer.ts specifically to eliminate this kind of duplication, it'd be consistent to hoist these two small parsing helpers into a shared module (e.g. @/lib/request-body.ts) as well.

♻️ Suggested extraction
// testing/e2e/src/lib/request-body.tsexportfunctionisRecord(value: unknown): value is Record<string,unknown>{returntypeofvalue==='object'&&value!==null}exportfunctionrecordFromBody(body: unknown): Record<string,unknown>{if(!isRecord(body)){thrownewError('Invalid request body')}constdata=body.forwardedProps??body.data??bodyif(!isRecord(data)){thrownewError('Invalid request body')}returndata}
-function isRecord(value: unknown): value is Record<string, unknown> {- return typeof value === 'object' && value !== null-}--function recordFromBody(body: unknown): Record<string, unknown> {- if (!isRecord(body)) {- throw new Error('Invalid request body')- }-- const data = body.forwardedProps ?? body.data ?? body- if (!isRecord(data)) {- throw new Error('Invalid request body')- }-- return data-}+import { recordFromBody } from '`@/lib/request-body`'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@testing/e2e/src/routes/api.otel-transcription.ts` around lines 8 - 23, The
`isRecord` and `recordFromBody` helpers in `api.otel-transcription.ts` are
duplicated in `api.otel-media.ts`; extract them into a shared request-body
utility module and import them from both routes. Keep the shared implementation
consistent with the current `recordFromBody` behavior, including the
`forwardedProps`/`data` fallback and validation logic, so both route handlers
reuse the same parsing helpers.
packages/ai-openai/src/adapters/transcription.ts (1)

74-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a shared helper for the duration-billed TokenUsage shape.

Both branches build the identical zeroed-tokens + billed/durationSeconds object, differing only in the source value (usage.seconds vs duration). Consolidating avoids drift between billed and the deprecated durationSeconds field as this compatibility shim evolves.

♻️ Proposed refactor
+function buildDurationBilledUsage(seconds: number): TokenUsage {+ return {+ promptTokens: 0,+ completionTokens: 0,+ totalTokens: 0,+ billed: { quantity: seconds, unit: 'seconds' },+ durationSeconds: seconds,+ }+}+
function buildTranscriptionUsage(
model: string,
duration?: number,
response?: OpenAI_SDK.Audio.TranscriptionCreateResponse,
): TokenUsage | undefined {
...
if (usage.type === 'duration') {
- return {- promptTokens: 0,- completionTokens: 0,- totalTokens: 0,- billed: { quantity: usage.seconds, unit: 'seconds' },- durationSeconds: usage.seconds,- }+ return buildDurationBilledUsage(usage.seconds)
}
...
if (duration !== undefined && duration > 0) {
- return {- promptTokens: 0,- completionTokens: 0,- totalTokens: 0,- billed: { quantity: duration, unit: 'seconds' },- durationSeconds: duration,- }+ return buildDurationBilledUsage(duration)
}

Also applies to: 113-122

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/ai-openai/src/adapters/transcription.ts` around lines 74 - 82, The
duration-billed TokenUsage object is duplicated in the transcription adapter,
with only the source value changing between usage.seconds and duration. Extract
a shared helper in transcription.ts around the TokenUsage construction used by
the duration branch (and the other matching branch mentioned in the review) so
both paths reuse the same zeroed-tokens plus billed/durationSeconds shape. Keep
the helper focused on the shared compatibility-shim fields to prevent drift
between billed and durationSeconds as the code evolves.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/ai-openai/src/adapters/transcription.ts`:
- Around line 74-82: The duration-billed TokenUsage object is duplicated in the
transcription adapter, with only the source value changing between usage.seconds
and duration. Extract a shared helper in transcription.ts around the TokenUsage
construction used by the duration branch (and the other matching branch
mentioned in the review) so both paths reuse the same zeroed-tokens plus
billed/durationSeconds shape. Keep the helper focused on the shared
compatibility-shim fields to prevent drift between billed and durationSeconds as
the code evolves.
In `@testing/e2e/src/routes/api.otel-transcription.ts`:
- Around line 8-23: The `isRecord` and `recordFromBody` helpers in
`api.otel-transcription.ts` are duplicated in `api.otel-media.ts`; extract them
into a shared request-body utility module and import them from both routes. Keep
the shared implementation consistent with the current `recordFromBody` behavior,
including the `forwardedProps`/`data` fallback and validation logic, so both
route handlers reuse the same parsing helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23e53528-e844-4931-bd7e-4969133f4998

📥 Commits

Reviewing files that changed from the base of the PR and between 5deda27 and 3a53a16.

📒 Files selected for processing (36)
  • .changeset/billed-usage-unit.md
  • docs/adapters/grok.md
  • docs/advanced/otel.md
  • docs/config.json
  • docs/media/audio-generation.md
  • docs/media/image-generation.md
  • docs/media/video-generation.md
  • examples/ts-react-media/src/components/ImageGenerator.tsx
  • examples/ts-react-media/src/components/VideoGenerator.tsx
  • examples/ts-react-media/src/lib/server-functions.ts
  • packages/ai-event-client/src/index.ts
  • packages/ai-fal/src/utils/billing.ts
  • packages/ai-fal/tests/audio-adapter.test.ts
  • packages/ai-fal/tests/billing.test.ts
  • packages/ai-fal/tests/image-adapter.test.ts
  • packages/ai-fal/tests/speech-adapter.test.ts
  • packages/ai-fal/tests/transcription-adapter.test.ts
  • packages/ai-fal/tests/video-adapter.test.ts
  • packages/ai-grok/src/adapters/transcription.ts
  • packages/ai-grok/src/adapters/video.ts
  • packages/ai-grok/tests/audio-adapters.test.ts
  • packages/ai-grok/tests/video-adapter.test.ts
  • packages/ai-openai/src/adapters/transcription.ts
  • packages/ai-openai/tests/transcription-adapter.test.ts
  • packages/ai-openai/tests/transcription-usage.test.ts
  • packages/ai/skills/ai-core/media-generation/SKILL.md
  • packages/ai/src/middlewares/usage-attributes.ts
  • packages/ai/src/types.ts
  • packages/ai/tests/middlewares/otel.test.ts
  • testing/e2e/global-setup.ts
  • testing/e2e/src/lib/otel-local-tracer.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.otel-media.ts
  • testing/e2e/src/routes/api.otel-transcription.ts
  • testing/e2e/src/routes/api.otel-usage.ts
  • testing/e2e/tests/middleware.spec.ts

@AlemTuzlak

Copy link
Copy Markdown
Contributor

@tombeckenham leaving this one to you!

@coderabbitaicoderabbitaiBot 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.

Caution

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

⚠️ Outside diff range comments (1)
docs/config.json (1)

392-392: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh these updatedAt values to July 30, 2026.

These documentation entries are changed in this PR but remain stamped 2026-07-04. Update all four values to 2026-07-30.

As per coding guidelines, docs/config.json must refresh updatedAt to today’s date for substantive content changes.

Proposed fix
- "updatedAt": "2026-07-04"+ "updatedAt": "2026-07-30"

Apply this at Lines 392, 404, 438, and 710.

Also applies to: 404-404, 438-438, 710-710

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/config.json` at line 392, Update all four changed documentation entries’
updatedAt fields in docs/config.json from 2026-07-04 to 2026-07-30, including
the entries currently at lines 392, 404, 438, and 710.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/config.json`:
- Line 392: Update all four changed documentation entries’ updatedAt fields in
docs/config.json from 2026-07-04 to 2026-07-30, including the entries currently
at lines 392, 404, 438, and 710.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 64510017-0fc6-4041-9b7f-04a85ec449a3

📥 Commits

Reviewing files that changed from the base of the PR and between 091319a and 37dc210.

📒 Files selected for processing (3)
  • docs/config.json
  • packages/ai/src/types.ts
  • testing/e2e/src/routeTree.gen.ts

Non-token billed quantities (seconds of audio/video, fal's endpoint
units) were previously reported as bare counts on unitsBilled /
durationSeconds, leaving consumers to guess the unit from provider
identity. TokenUsage now carries an optional billed field pairing the
quantity with the unit it is denominated in:
usage.billed = { quantity: 8, unit: 'seconds' }
- BillingUnit is an open string union ('tokens' | 'seconds' | 'units' |
...) so provider-specific units stay representable while the common
ones autocomplete.
- Producers updated: fal adapters ({ unit: 'units' } from the
x-fal-billable-units header), Grok Imagine video ({ unit: 'seconds' }),
and the OpenAI/Grok duration-billed transcription paths
({ unit: 'seconds' }).
- otelMiddleware emits the pair as tanstack.ai.usage.billed_quantity /
billed_unit span attributes, guarded so a non-finite quantity never
leaves a dangling unit.
- unitsBilled / durationSeconds are deprecated but still populated, so
existing consumers keep working.
ClosesTanStack#816
- Extract durationUsage() in the OpenAI transcription adapter so the
gpt-4o duration branch and the whisper-1 path share one zeroed-tokens +
billed/durationSeconds shape and can't drift apart.
- Move the identical recordFromBody() body-parsing helper from the
otel-media and otel-transcription e2e routes into a shared
lib/request-body module.
@season179
season179force-pushed the feat/billed-usage-unit branch from 37dc210 to ebeab86CompareAugust 2, 2026 02:06
# Conflicts:
#	docs/config.json
#	docs/media/audio-generation.md
#	docs/media/image-generation.md
#	examples/ts-react-media/src/components/ImageGenerator.tsx
#	examples/ts-react-media/src/components/VideoGenerator.tsx
#	examples/ts-react-media/src/lib/server-functions.ts
#	packages/ai/skills/ai-core/media-generation/SKILL.md
@nx-cloud

nx-cloudBot commented Aug 10, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit ee3b705

CommandStatusDurationResult
nx run-many --targets=build --exclude=examples/...✅ Succeeded1m 23sView ↗

☁️ Nx Cloud last updated this comment at 2026-08-19 09:30:42 UTC

@pkg-pr-new

pkg-pr-newBot commented Aug 10, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@896

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@896

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@896

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@896

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@896

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@896

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@896

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@896

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@896

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@896

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@896

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@896

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@896

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@896

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@896

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@896

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@896

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@896

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@896

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@896

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@896

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@896

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@896

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@896

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@896

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@896

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@896

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@896

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@896

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@896

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@896

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@896

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@896

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@896

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@896

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@896

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@896

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@896

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@896

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@896

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@896

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@896

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@896

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@896

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@896

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@896

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@896

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@896

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@896

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@896

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@896

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@896

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@896

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@896

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@896

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@896

commit: ee3b705

@AlemTuzlak

Copy link
Copy Markdown
Contributor

I like this idea! cc @tombeckenham you'll probably find this useful as well

@tombeckenham

Copy link
Copy Markdown
Contributor

Me too. It's a better design

@github-actionsgithub-actionsBot added the waiting-on: author Waiting for the author to respond or update label Aug 13, 2026
# Conflicts:
#	docs/config.json
#	docs/media/image-generation.md
#	docs/media/video-generation.md
#	testing/e2e/src/routes/api.otel-usage.ts
@github-actionsgithub-actionsBot added waiting-on: maintainer The ball is in the maintainers’ court and removed waiting-on: author Waiting for the author to respond or update labels Aug 13, 2026
@github-actionsgithub-actionsBot added merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update and removed waiting-on: maintainer The ball is in the maintainers’ court labels Aug 18, 2026
# Conflicts:
#	docs/config.json
#	packages/ai-grok/src/adapters/video.ts
#	packages/ai/skills/ai-core/media-generation/SKILL.md
@AlemTuzlak

Copy link
Copy Markdown
Contributor

Merged latest main into this branch (4a5cbe770).

Conflicts were in:

  • docs/config.json — kept today's Grok updatedAt
  • packages/ai-grok/src/adapters/video.ts — kept main's reference/edit/extend docs, and usage.billed
  • packages/ai/skills/ai-core/media-generation/SKILL.md — same: main's Grok 1.5 text + usage.billed

The PR is mergeable again. CI is running.

@AlemTuzlakAlemTuzlak removed the merge-conflicts Conflicts with the base branch — needs a rebase label Aug 19, 2026
Cohere and OpenRouter rerank now set usage.billed with unit units. Seedance video sets unit tokens. Persistence sums billed when both reports use the same unit.
@AlemTuzlak
AlemTuzlak merged commit 41a5d18 into TanStack:mainAug 19, 2026
9 checks passed
@AlemTuzlak

Copy link
Copy Markdown
Contributor

@season179 thank you and sorry for the wait

@github-actionsgithub-actionsBot mentioned this pull request Aug 19, 2026
@season179
season179 deleted the feat/billed-usage-unit branch August 20, 2026 01:47
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: authorWaiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a required billingUnit field to TokenUsage so billed quantities are self-describing

3 participants

@season179@AlemTuzlak@tombeckenham