Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/sim/lib/tokenization/streaming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export function processStreamingBlockLog(log: BlockLog, streamedContent: string)
return false
}

// Skip recalculation if cost was explicitly set by the billing layer (e.g. BYOK zero cost)
if (log.output?.cost?.pricing) {
return false
}

// Check if we have content to tokenize
if (!streamedContent?.trim()) {
return false
Expand Down
42 changes: 39 additions & 3 deletions apps/sim/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,33 @@ function isReadableStream(response: any): response is ReadableStream {
return response instanceof ReadableStream
}

const ZERO_COST = Object.freeze({
input: 0,
output: 0,
total: 0,
pricing: Object.freeze({ input: 0, output: 0, updatedAt: new Date(0).toISOString() }),
})
Comment thread
TheodoreSpeaks marked this conversation as resolved.

/**
* Prevents streaming callbacks from writing non-zero cost for BYOK users.
* The property is frozen via defineProperty because providers set cost inside
* streaming callbacks that fire after this function returns.
*/
function zeroCostForBYOK(response: StreamingExecution): void {
const output = response.execution?.output
if (!output || typeof output !== 'object') {
logger.warn('zeroCostForBYOK: output not available at intercept time; cost may not be zeroed')
return
}

Object.defineProperty(output, 'cost', {
get: () => ZERO_COST,
set: () => {},
configurable: true,
enumerable: true,
})
Comment thread
TheodoreSpeaks marked this conversation as resolved.
}
Comment thread
TheodoreSpeaks marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.

export async function executeProviderRequest(
providerId: string,
request: ProviderRequest
Expand All @@ -80,6 +107,12 @@ export async function executeProviderRequest(
)
resolvedRequest = { ...resolvedRequest, apiKey: result.apiKey }
isBYOK = result.isBYOK
logger.info('API key resolved', {
provider: providerId,
model: request.model,
workspaceId: request.workspaceId,
isBYOK,
})
} catch (error) {
logger.error('Failed to resolve API key:', {
provider: providerId,
Expand Down Expand Up @@ -118,7 +151,10 @@ export async function executeProviderRequest(
const response = await provider.executeRequest(sanitizedRequest)

if (isStreamingExecution(response)) {
logger.info('Provider returned StreamingExecution')
logger.info('Provider returned StreamingExecution', { isBYOK })
if (isBYOK) {
zeroCostForBYOK(response)
}
return response
}

Expand Down Expand Up @@ -154,9 +190,9 @@ export async function executeProviderRequest(
},
}
if (isBYOK) {
logger.debug(`Not billing model usage for ${response.model} - workspace BYOK key used`)
logger.info(`Not billing model usage for ${response.model} - workspace BYOK key used`)
} else {
logger.debug(
logger.info(
`Not billing model usage for ${response.model} - user provided API key or not hosted model`
)
}
Expand Down
Loading