Skip to content

Commit cb7eeee

Browse files
authored
Merge branch 'main' into snyk-upgrade-aws-sdk-packages
2 parents 050c2bc + 08e2688 commit cb7eeee

31 files changed

+1203
-402
lines changed

.github/workflows/jetbrains-release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ jobs:
491491

492492
# Run Qodana inspections
493493
- name: Qodana - Code Inspection
494-
uses: JetBrains/qodana-action@v2025.2.2
494+
uses: JetBrains/qodana-action@v2025.2.3
495495
with:
496496
cache-default-branch-only: true
497497

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
{
22
"name": "cn",
3+
"dockerfile": "FROM runloop:runloop/universal-ubuntu-24.04-x86_64-dnd",
34
"system_setup_commands": [
45
"npm i -g @continuedev/cli@latest",
56
"sudo apt update",
6-
"sudo apt install -y ripgrep"
7-
]
7+
"printf '#!/bin/sh\\nexit 101\\n' | sudo tee /usr/sbin/policy-rc.d > /dev/null && sudo chmod +x /usr/sbin/policy-rc.d",
8+
"sudo apt install -y --no-install-recommends ripgrep chromium chromium-driver xvfb",
9+
"sudo rm /usr/sbin/policy-rc.d",
10+
"sudo mkdir -p /opt/google/chrome",
11+
"sudo ln -s /usr/bin/chromium /opt/google/chrome/chrome",
12+
"curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | sudo -E bash",
13+
"sudo apt-get install -y infisical"
14+
],
15+
"launch_parameters": {
16+
"launch_commands": [
17+
"export DISPLAY=:99",
18+
"nohup Xvfb :99 -screen 0 1920x1080x24 > /tmp/xvfb.log 2>&1 &",
19+
"sleep 2",
20+
"sudo mkdir -p /home/user",
21+
"for dir in /root/*; do [ -d \"$dir\" ] && [ -d \"$dir/.git\" ] && sudo ln -sf \"$dir\" \"/home/user/$(basename \"$dir\")\"; done"
22+
]
23+
}
824
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Upload Runloop Blueprint
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches:
7+
- main
8+
paths:
9+
- '.github/workflows/runloop-blueprint-template.json'
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
upload-blueprint:
16+
name: Upload Blueprint to Runloop
17+
runs-on: ubuntu-latest
18+
# Only run this workflow on the main repository (continuedev/continue)
19+
if: github.repository == 'continuedev/continue'
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v6
23+
24+
- name: Publish Blueprint to Runloop
25+
env:
26+
RUNLOOP_API_KEY: ${{ secrets.RUNLOOP_API_KEY }}
27+
run: |
28+
echo "Publishing blueprint 'cn' to Runloop API"
29+
30+
# Use blueprint configuration from template file
31+
cp .github/workflows/runloop-blueprint-template.json blueprint.json
32+
33+
# Publish to Runloop API
34+
response=$(curl -X POST "https://api.runloop.ai/v1/blueprints" \
35+
-H "Authorization: Bearer $RUNLOOP_API_KEY" \
36+
-H "Content-Type: application/json" \
37+
-d @blueprint.json \
38+
-w "%{http_code}" \
39+
-s)
40+
41+
http_code=$(echo "$response" | tail -c 4)
42+
response_body=$(echo "$response" | head -c -4)
43+
44+
if [[ "$http_code" == "200" ]] || [[ "$http_code" == "201" ]]; then
45+
echo "✅ Successfully published blueprint to Runloop API"
46+
echo "Response: $response_body"
47+
48+
# Extract blueprint ID if available
49+
blueprint_id=$(echo "$response_body" | jq -r '.id // empty')
50+
if [[ -n "$blueprint_id" ]]; then
51+
echo "Blueprint ID: $blueprint_id"
52+
echo "blueprint_id=$blueprint_id" >> $GITHUB_OUTPUT
53+
fi
54+
else
55+
echo "❌ Failed to publish blueprint to Runloop API"
56+
echo "HTTP Code: $http_code"
57+
echo "Response: $response_body"
58+
exit 1
59+
fi

core/index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,11 @@ export interface IContextProvider {
267267
get deprecationMessage(): string | null;
268268
}
269269

270+
export interface SessionUsage extends Usage {
271+
/** Total cumulative cost in USD for all LLM API calls in this session */
272+
totalCost: number;
273+
}
274+
270275
export interface Session {
271276
sessionId: string;
272277
title: string;
@@ -276,6 +281,8 @@ export interface Session {
276281
mode?: MessageModes;
277282
/** Optional: title of the selected chat model for this session */
278283
chatModelTitle?: string | null;
284+
/** Optional: cumulative usage and cost for all LLM API calls in this session */
285+
usage?: SessionUsage;
279286
}
280287

281288
export interface BaseSessionMetadata {

core/llm/utils/calculateRequestCost.ts

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@ function calculateAnthropicCost(
1212
// Normalize model name to handle various formats
1313
const normalizedModel = model.toLowerCase();
1414

15-
// Define pricing per million tokens (MTok)
15+
// Define pricing per million tokens (MTok) by model family prefix
1616
const pricing: Record<
1717
string,
1818
{ input: number; output: number; cacheWrite: number; cacheRead: number }
1919
> = {
20-
// Claude Opus 4 (most intelligent model)
21-
"claude-3-opus": {
22-
input: 15,
23-
output: 75,
24-
cacheWrite: 18.75,
25-
cacheRead: 1.5,
20+
// Claude Opus 4.5 (most intelligent model)
21+
"claude-opus-4-5": {
22+
input: 5,
23+
output: 25,
24+
cacheWrite: 6.25,
25+
cacheRead: 0.5,
2626
},
27-
"claude-3-opus-20240229": {
27+
28+
// Claude Opus 4 (legacy)
29+
"claude-3-opus": {
2830
input: 15,
2931
output: 75,
3032
cacheWrite: 18.75,
@@ -38,18 +40,6 @@ function calculateAnthropicCost(
3840
cacheWrite: 3.75,
3941
cacheRead: 0.3,
4042
},
41-
"claude-3-5-sonnet-20241022": {
42-
input: 3,
43-
output: 15,
44-
cacheWrite: 3.75,
45-
cacheRead: 0.3,
46-
},
47-
"claude-3-5-sonnet-20240620": {
48-
input: 3,
49-
output: 15,
50-
cacheWrite: 3.75,
51-
cacheRead: 0.3,
52-
},
5343

5444
// Claude Haiku 3.5 (fastest, most cost-effective)
5545
"claude-3-5-haiku": {
@@ -58,12 +48,6 @@ function calculateAnthropicCost(
5848
cacheWrite: 1,
5949
cacheRead: 0.08,
6050
},
61-
"claude-3-5-haiku-20241022": {
62-
input: 0.8,
63-
output: 4,
64-
cacheWrite: 1,
65-
cacheRead: 0.08,
66-
},
6751

6852
// Legacy Claude 3 Haiku
6953
"claude-3-haiku": {
@@ -72,35 +56,16 @@ function calculateAnthropicCost(
7256
cacheWrite: 0.3,
7357
cacheRead: 0.03,
7458
},
75-
"claude-3-haiku-20240307": {
76-
input: 0.25,
77-
output: 1.25,
78-
cacheWrite: 0.3,
79-
cacheRead: 0.03,
80-
},
8159
};
8260

83-
// Find matching pricing model
84-
let modelPricing = pricing[normalizedModel];
61+
// Sort keys by length (longest first) to match most specific patterns first
62+
const sortedKeys = Object.keys(pricing).sort((a, b) => b.length - a.length);
8563

86-
// If exact match not found, try to find by partial match
87-
if (!modelPricing) {
88-
for (const [key, value] of Object.entries(pricing)) {
89-
if (normalizedModel.includes(key) || key.includes(normalizedModel)) {
90-
modelPricing = value;
91-
break;
92-
}
93-
}
94-
}
95-
96-
// If still no match, try common patterns
97-
if (!modelPricing) {
98-
if (normalizedModel.includes("opus")) {
99-
modelPricing = pricing["claude-3-opus"];
100-
} else if (normalizedModel.includes("sonnet")) {
101-
modelPricing = pricing["claude-3-5-sonnet"];
102-
} else if (normalizedModel.includes("haiku")) {
103-
modelPricing = pricing["claude-3-5-haiku"];
64+
let modelPricing = null;
65+
for (const prefix of sortedKeys) {
66+
if (normalizedModel.startsWith(prefix)) {
67+
modelPricing = pricing[prefix];
68+
break;
10469
}
10570
}
10671

@@ -167,14 +132,90 @@ function calculateAnthropicCost(
167132
};
168133
}
169134

135+
function calculateOpenAICost(
136+
model: string,
137+
usage: Usage,
138+
): CostBreakdown | null {
139+
// Normalize model name
140+
const normalizedModel = model.toLowerCase();
141+
142+
// Define pricing per million tokens (MTok) by model family prefix
143+
const pricing: Record<string, { input: number; output: number }> = {
144+
// GPT-4o models (most specific first)
145+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
146+
"gpt-4o": { input: 2.5, output: 10 },
147+
148+
// GPT-4 Turbo models
149+
"gpt-4-turbo": { input: 10, output: 30 },
150+
151+
// GPT-3.5 Turbo models (most specific first)
152+
"gpt-3.5-turbo-0125": { input: 0.5, output: 1.5 },
153+
"gpt-3.5-turbo-1106": { input: 1, output: 2 },
154+
"gpt-3.5-turbo": { input: 1.5, output: 2 },
155+
156+
// Base GPT-4 (fallback for other gpt-4 variants)
157+
"gpt-4": { input: 30, output: 60 },
158+
};
159+
160+
// Sort keys by length (longest first) to match most specific patterns first
161+
const sortedKeys = Object.keys(pricing).sort((a, b) => b.length - a.length);
162+
163+
let modelPricing = null;
164+
for (const prefix of sortedKeys) {
165+
if (normalizedModel.startsWith(prefix)) {
166+
modelPricing = pricing[prefix];
167+
break;
168+
}
169+
}
170+
171+
if (!modelPricing) {
172+
return null; // Unknown model
173+
}
174+
175+
// Calculate costs
176+
const inputCost = (usage.promptTokens / 1_000_000) * modelPricing.input;
177+
const outputCost = (usage.completionTokens / 1_000_000) * modelPricing.output;
178+
179+
// Build breakdown components
180+
const breakdownParts: string[] = [];
181+
182+
if (usage.promptTokens > 0) {
183+
breakdownParts.push(
184+
`Input: ${usage.promptTokens.toLocaleString()} tokens × $${modelPricing.input}/MTok = $${inputCost.toFixed(6)}`,
185+
);
186+
}
187+
188+
if (usage.completionTokens > 0) {
189+
breakdownParts.push(
190+
`Output: ${usage.completionTokens.toLocaleString()} tokens × $${modelPricing.output}/MTok = $${outputCost.toFixed(6)}`,
191+
);
192+
}
193+
194+
const totalCost = inputCost + outputCost;
195+
196+
// Build final breakdown string
197+
let breakdown = `Model: ${model}\n`;
198+
breakdown += breakdownParts.join("\n");
199+
if (breakdownParts.length > 1) {
200+
breakdown += `\nTotal: $${totalCost.toFixed(6)}`;
201+
}
202+
203+
return {
204+
cost: totalCost,
205+
breakdown,
206+
};
207+
}
208+
170209
export function calculateRequestCost(
171210
provider: string,
172211
model: string,
173212
usage: Usage,
174213
): CostBreakdown | null {
175-
switch (provider) {
214+
switch (provider.toLowerCase()) {
176215
case "anthropic":
177216
return calculateAnthropicCost(model, usage);
217+
case "openai":
218+
return calculateOpenAICost(model, usage);
178219
default:
179220
return null;
180221
}

0 commit comments

Comments
 (0)