Merge pull request #1245 from simstudioai/fix/copilot-billing

improvement(copilot): billing multiplier adjustments
This commit is contained in:
Siddharth Ganesan
2025-09-04 12:05:17 -07:00
committed by GitHub
5 changed files with 48 additions and 15 deletions
+28 -1
View File
@@ -91,4 +91,31 @@ Copilot is your in-editor assistant that helps you build, understand, and improv
>
<div className="m-0 text-sm">Maximum reasoning for deep planning, debugging, and complex architectural changes.</div>
</Card>
</Cards>
</Cards>
## Billing and Cost Calculation
### How Costs Are Calculated
Copilot usage is billed per token from the underlying LLM:
- **Input tokens**: billed at the provider's base rate (**at-cost**)
- **Output tokens**: billed at **1.5×** the provider's base output rate
```javascript
copilotCost = (inputTokens × inputPrice + outputTokens × (outputPrice × 1.5)) / 1,000,000
```
| Component | Rate Applied |
|----------|----------------------|
| Input | inputPrice |
| Output | outputPrice × 1.5 |
<Callout type="warning">
Pricing shown reflects rates as of September 4, 2025. Check provider documentation for current pricing.
</Callout>
<Callout type="info">
Model prices are per million tokens. The calculation divides by 1,000,000 to get the actual cost. See <a href="/execution/advanced#cost-calculation">Logging and Cost Calculation</a> for background and examples.
</Callout>
@@ -16,7 +16,8 @@ const UpdateCostSchema = z.object({
input: z.number().min(0, 'Input tokens must be a non-negative number'),
output: z.number().min(0, 'Output tokens must be a non-negative number'),
model: z.string().min(1, 'Model is required'),
multiplier: z.number().min(0),
inputMultiplier: z.number().min(0),
outputMultiplier: z.number().min(0),
})
/**
@@ -75,14 +76,15 @@ export async function POST(req: NextRequest) {
)
}
const { userId, input, output, model, multiplier } = validation.data
const { userId, input, output, model, inputMultiplier, outputMultiplier } = validation.data
logger.info(`[${requestId}] Processing cost update`, {
userId,
input,
output,
model,
multiplier,
inputMultiplier,
outputMultiplier,
})
const finalPromptTokens = input
@@ -95,7 +97,8 @@ export async function POST(req: NextRequest) {
finalPromptTokens,
finalCompletionTokens,
false,
multiplier
inputMultiplier,
outputMultiplier
)
logger.info(`[${requestId}] Cost calculation result`, {
@@ -104,7 +107,8 @@ export async function POST(req: NextRequest) {
promptTokens: finalPromptTokens,
completionTokens: finalCompletionTokens,
totalTokens: totalTokens,
multiplier,
inputMultiplier,
outputMultiplier,
costResult,
})
@@ -226,6 +226,7 @@ describe('Copilot Chat API Route', () => {
mode: 'agent',
messageId: 'mock-uuid-1234-5678',
depth: 0,
chatId: 'chat-123',
}),
})
)
@@ -289,6 +290,7 @@ describe('Copilot Chat API Route', () => {
mode: 'agent',
messageId: 'mock-uuid-1234-5678',
depth: 0,
chatId: 'chat-123',
}),
})
)
@@ -341,6 +343,7 @@ describe('Copilot Chat API Route', () => {
mode: 'agent',
messageId: 'mock-uuid-1234-5678',
depth: 0,
chatId: 'chat-123',
}),
})
)
@@ -430,6 +433,7 @@ describe('Copilot Chat API Route', () => {
mode: 'ask',
messageId: 'mock-uuid-1234-5678',
depth: 0,
chatId: 'chat-123',
}),
})
)
+1
View File
@@ -378,6 +378,7 @@ export async function POST(req: NextRequest) {
...(typeof effectivePrefetch === 'boolean' ? { prefetch: effectivePrefetch } : {}),
...(session?.user?.name && { userName: session.user.name }),
...(agentContexts.length > 0 && { context: agentContexts }),
...(actualChatId ? { chatId: actualChatId } : {}),
}
try {
+6 -9
View File
@@ -1,4 +1,4 @@
import { getCostMultiplier, isHosted } from '@/lib/environment'
import { isHosted } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { anthropicProvider } from '@/providers/anthropic'
import { azureOpenAIProvider } from '@/providers/azure-openai'
@@ -444,7 +444,8 @@ export function calculateCost(
promptTokens = 0,
completionTokens = 0,
useCachedInput = false,
customMultiplier?: number
inputMultiplier?: number,
outputMultiplier?: number
) {
// First check if it's an embedding model
let pricing = getEmbeddingModelPricing(model)
@@ -479,13 +480,9 @@ export function calculateCost(
: pricing.input / 1_000_000)
const outputCost = completionTokens * (pricing.output / 1_000_000)
const totalCost = inputCost + outputCost
const costMultiplier = customMultiplier ?? getCostMultiplier()
const finalInputCost = inputCost * costMultiplier
const finalOutputCost = outputCost * costMultiplier
const finalTotalCost = totalCost * costMultiplier
const finalInputCost = inputCost * (inputMultiplier ?? 1)
const finalOutputCost = outputCost * (outputMultiplier ?? 1)
const finalTotalCost = finalInputCost + finalOutputCost
return {
input: Number.parseFloat(finalInputCost.toFixed(8)), // Use 8 decimal places for small costs