diff --git a/src/modules/aix/server/dispatch/chatGenerate/parsers/openai.parser.ts b/src/modules/aix/server/dispatch/chatGenerate/parsers/openai.parser.ts index b13b19ae7..ecfc311fa 100644 --- a/src/modules/aix/server/dispatch/chatGenerate/parsers/openai.parser.ts +++ b/src/modules/aix/server/dispatch/chatGenerate/parsers/openai.parser.ts @@ -576,9 +576,17 @@ function _fromOpenAIUsage(usage: OpenAIWire_API_Chat_Completions.Response['usage // Upstream Cost Reporting - // [Perplexity, 2025-10-20] - if (!!usage.cost && typeof usage.cost === 'object' && 'total_cost' in usage.cost && typeof usage.cost.total_cost === 'number') - metricsUpdate.$cReported = Math.round(usage.cost.total_cost * 100 * 10000) / 10000; + // [Perplexity, 2025-10-20] - cost as object with total_cost + // [OpenRouter, 2025-10-22] - cost as direct number + if (usage.cost !== null && usage.cost !== undefined) { + if (typeof usage.cost === 'number') { + // OpenRouter sends cost directly as a number + metricsUpdate.$cReported = Math.round(usage.cost * 100 * 10000) / 10000; + } else if (typeof usage.cost === 'object' && 'total_cost' in usage.cost && typeof usage.cost.total_cost === 'number') { + // Perplexity sends cost as an object with total_cost + metricsUpdate.$cReported = Math.round(usage.cost.total_cost * 100 * 10000) / 10000; + } + } // Time Metrics diff --git a/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts b/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts index 6705fc692..164c7eef4 100644 --- a/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts +++ b/src/modules/aix/server/dispatch/wiretypes/openai.wiretypes.ts @@ -418,6 +418,7 @@ export namespace OpenAIWire_API_Chat_Completions { reasoning_tokens: z.number().optional(), // [Discord, 2024-04-10] reported missing // text_tokens: z.number().optional(), // [Discord, 2024-04-10] revealed as present on custom OpenAI endpoint - not using it here yet audio_tokens: z.number().optional(), // [OpenAI, 2024-10-01] audio tokens used in the completion (charged at a different rate) + // image_tokens: z.number().optional(), // [OpenRouter, 2025-10-22] first seen.. sounds likely? accepted_prediction_tokens: z.number().optional(), // [OpenAI, 2024-11-05] Predicted Outputs rejected_prediction_tokens: z.number().optional(), // [OpenAI, 2024-11-05] Predicted Outputs }).optional() // not present in other APIs yet @@ -427,13 +428,25 @@ export namespace OpenAIWire_API_Chat_Completions { prompt_cache_hit_tokens: z.number().optional(), prompt_cache_miss_tokens: z.number().optional(), - // [Perplexity, 2025-10-20] cost breakdown - cost: z.looseObject({ - input_tokens_cost: z.number().optional(), - output_tokens_cost: z.number().optional(), - request_cost: z.number().optional(), - total_cost: z.number().optional(), - }).nullish(), + // [Perplexity, 2025-10-20] cost breakdown (object format) + // [OpenRouter, 2025-01-22] cost as direct number + cost: z.union([ + z.number(), // OpenRouter sends cost as a number directly + z.looseObject({ + input_tokens_cost: z.number().optional(), + output_tokens_cost: z.number().optional(), + request_cost: z.number().optional(), + total_cost: z.number().optional(), + }), + ]).nullish(), + + // [OpenRouter, 2025-10-22] additional usage fields when used with Chutes + // is_byok: z.boolean().optional(), // Bring Your Own Key indicator + // cost_details: z.object({ + // upstream_inference_cost: z.number().optional(), + // upstream_inference_prompt_cost: z.number().optional(), + // upstream_inference_completions_cost: z.number().optional(), + // }).optional(), }).nullable(); /**