| 336 | } |
| 337 | |
| 338 | export const getUsage = (input: { model: Provider.Model; usage: Usage; metadata?: ProviderMetadata }) => { |
| 339 | const safe = (value: number) => { |
| 340 | if (!Number.isFinite(value)) return 0 |
| 341 | return Math.max(0, value) |
| 342 | } |
| 343 | const inputTokens = safe(input.usage.inputTokens ?? 0) |
| 344 | const outputTokens = safe(input.usage.outputTokens ?? 0) |
| 345 | const reasoningTokens = safe(input.usage.reasoningTokens ?? 0) |
| 346 | |
| 347 | const cacheReadInputTokens = safe(input.usage.cacheReadInputTokens ?? 0) |
| 348 | const cacheWriteInputTokens = safe( |
| 349 | Number( |
| 350 | input.usage.cacheWriteInputTokens ?? |
| 351 | input.metadata?.["anthropic"]?.["cacheCreationInputTokens"] ?? |
| 352 | // google-vertex-anthropic returns metadata under "vertex" key |
| 353 | // (AnthropicMessagesLanguageModel custom provider key from 'vertex.anthropic.messages') |
| 354 | input.metadata?.["vertex"]?.["cacheCreationInputTokens"] ?? |
| 355 | // @ts-expect-error |
| 356 | input.metadata?.["bedrock"]?.["usage"]?.["cacheWriteInputTokens"] ?? |
| 357 | // @ts-expect-error |
| 358 | input.metadata?.["venice"]?.["usage"]?.["cacheCreationInputTokens"] ?? |
| 359 | 0, |
| 360 | ), |
| 361 | ) |
| 362 | |
| 363 | // AI SDK v6 normalized inputTokens to include cached tokens across all providers |
| 364 | // (including Anthropic/Bedrock which previously excluded them). Always subtract cache |
| 365 | // tokens to get the non-cached input count for separate cost calculation. |
| 366 | const adjustedInputTokens = safe(inputTokens - cacheReadInputTokens - cacheWriteInputTokens) |
| 367 | |
| 368 | const total = input.usage.totalTokens |
| 369 | |
| 370 | const tokens = { |
| 371 | total, |
| 372 | input: adjustedInputTokens, |
| 373 | output: safe(outputTokens - reasoningTokens), |
| 374 | reasoning: reasoningTokens, |
| 375 | cache: { |
| 376 | write: cacheWriteInputTokens, |
| 377 | read: cacheReadInputTokens, |
| 378 | }, |
| 379 | } |
| 380 | |
| 381 | const contextTokens = inputTokens |
| 382 | const costInfo = |
| 383 | input.model.cost?.tiers |
| 384 | ?.filter((item) => item.tier.type === "context" && contextTokens > item.tier.size) |
| 385 | .sort((a, b) => b.tier.size - a.tier.size)[0] ?? |
| 386 | (input.model.cost?.experimentalOver200K && contextTokens > 200_000 |
| 387 | ? input.model.cost.experimentalOver200K |
| 388 | : input.model.cost) |
| 389 | const totalNanoAiu = input.metadata?.["copilot"]?.["totalNanoAiu"] |
| 390 | return { |
| 391 | cost: |
| 392 | typeof totalNanoAiu === "number" && Number.isFinite(totalNanoAiu) && totalNanoAiu >= 0 |
| 393 | ? new Decimal(totalNanoAiu).div(100_000_000_000).toNumber() |
| 394 | : safe( |
| 395 | new Decimal(0) |