From 8c9148b5377abf667b850d519a916a40f2107a53 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Nov 2025 00:35:19 -0500 Subject: [PATCH] Improvements to base openai compatible --- .../base-openai-compatible-provider.ts | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 51db85410e..dc6d03b5cc 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -5,13 +5,14 @@ import type { ModelInfo } from "@roo-code/types" import { type ApiHandlerOptions, getModelMaxOutputTokens } from "../../shared/api" import { XmlMatcher } from "../../utils/xml-matcher" -import { ApiStream } from "../transform/stream" +import { ApiStream, ApiStreamUsageChunk } from "../transform/stream" import { convertToOpenAiMessages } from "../transform/openai-format" import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index" import { DEFAULT_HEADERS } from "./constants" import { BaseProvider } from "./base-provider" import { handleOpenAIError } from "./utils/openai-error-handler" +import { calculateApiCostOpenAI } from "../../shared/cost" type BaseOpenAiCompatibleProviderOptions = ApiHandlerOptions & { providerName: string @@ -119,6 +120,8 @@ export abstract class BaseOpenAiCompatibleProvider const toolCallAccumulator = new Map() + let lastUsage: OpenAI.CompletionUsage | undefined + for await (const chunk of stream) { // Check for provider-specific error responses (e.g., MiniMax base_resp) const chunkAny = chunk as any @@ -176,20 +179,40 @@ export abstract class BaseOpenAiCompatibleProvider } if (chunk.usage) { - yield { - type: "usage", - inputTokens: chunk.usage.prompt_tokens || 0, - outputTokens: chunk.usage.completion_tokens || 0, - } + lastUsage = chunk.usage } } + if (lastUsage) { + yield this.processUsageMetrics(lastUsage, this.getModel().info) + } + // Process any remaining content for (const processedChunk of matcher.final()) { yield processedChunk } } + protected processUsageMetrics(usage: any, modelInfo?: any): ApiStreamUsageChunk { + const inputTokens = usage?.prompt_tokens || 0 + const outputTokens = usage?.completion_tokens || 0 + const cacheWriteTokens = usage?.prompt_tokens_details?.cache_write_tokens || 0 + const cacheReadTokens = usage?.prompt_tokens_details?.cached_tokens || 0 + + const { totalCost } = modelInfo + ? calculateApiCostOpenAI(modelInfo, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) + : { totalCost: 0 } + + return { + type: "usage", + inputTokens, + outputTokens, + cacheWriteTokens: cacheWriteTokens || undefined, + cacheReadTokens: cacheReadTokens || undefined, + totalCost, + } + } + async completePrompt(prompt: string): Promise { const { id: modelId } = this.getModel()