Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion apps/supercode-cli/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "supercode-cli",
"version": "0.1.86",
"version": "0.1.87",
"description": "AI-powered coding agent CLI",
"main": "dist/main.js",
"bin": {
Expand Down
3 changes: 1 addition & 2 deletions apps/supercode-cli/server/src/cli/ai/concentrate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import chalk from "chalk"
import { recordUsage } from "../../lib/track-usage"
import { computeCost } from "../../lib/pricing"
import { isEmptyToolResult, isDeniedToolResult, summarizeToolResult, tcName } from "./tool-result"
import { checkDailyOpusLimit, incrementDailyOpusCount, checkDailyTokenBudget } from "../../lib/token-budget"
import { checkDailyOpusLimit, incrementDailyOpusCount } from "../../lib/token-budget"

const HIGH_VALUE_MODELS = ["anthropic/claude-fable-5", "anthropic/claude-opus-4-8", "anthropic/claude-opus-4-7", "openai/gpt-5.5"]
const OPUS_MODELS = ["anthropic/claude-opus-4-8", "anthropic/claude-opus-4-7"]
Expand Down Expand Up @@ -118,7 +118,6 @@ export class ConcentrateService {
const systemMessages = messages.filter(m => m.role === "system")
const nonSystemMessages = messages.filter(m => m.role !== "system")
const system = systemMessages.map(m => m.content).join("\n")
await checkDailyTokenBudget()
if (OPUS_MODELS.includes(this.modelName)) {
await checkDailyOpusLimit()
await incrementDailyOpusCount()
Expand Down
36 changes: 36 additions & 0 deletions apps/supercode-cli/server/src/cli/ai/server-proxy-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,27 @@ import { getStoredToken } from "src/lib/token"
import { zodToJsonSchema } from "zod-to-json-schema"
import type { ModelMessage, FinishReason, LanguageModelUsage } from "ai"
import { isEmptyToolResult, isDeniedToolResult, summarizeToolResult } from "./tool-result"
import { appendProxyUsage } from "src/lib/token-budget"
import prisma from "src/lib/prisma"

const BASE_URL = process.env.SUPERCODE_SERVER_URL || "https://supercode-8w7e.onrender.com"

const MAX_STEPS = 8

async function getUserIdFromToken(): Promise<string | null> {
const token = await getStoredToken()
if (!token?.access_token) return null
try {
const session = await prisma.session.findUnique({
where: { token: token.access_token as string },
select: { userId: true },
})
return session?.userId ?? null
} catch {
return null
}
}

export class ServerProxyService {
readonly modelName: string
readonly providerName: string
Expand Down Expand Up @@ -96,6 +112,9 @@ export class ServerProxyService {

if (!res.ok) {
const text = await res.text()
if (text.includes("Insufficient Funds") || text.includes("Credit usage at configured limit")) {
throw new Error("Cloud AI is warming up. Please try again in a moment.")
}
throw new Error(text || "AI proxy request failed")
}

Expand Down Expand Up @@ -181,6 +200,7 @@ export class ServerProxyService {
const seenStepResults: Array<{ toolName: string; result: string }> = []
const deniedCounts = new Map<string, number>()
const toolCallHistory: Array<{ toolName: string; argsKey: string }> = []
const userId = await getUserIdFromToken()

// Convert tools to JSON-schema definitions ONCE (while the Zod schemas
// are still intact) so the server can hand valid parameter schemas to
Expand Down Expand Up @@ -436,6 +456,19 @@ export class ServerProxyService {
}
}

if (userId && (usage.totalTokens ?? 0) > 0) {
appendProxyUsage({
provider: this.providerName,
model: this.modelName,
inputTokens: usage.inputTokens ?? 0,
outputTokens: usage.outputTokens ?? 0,
cachedInputTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0,
totalTokens: usage.totalTokens ?? 0,
userId,
timestamp: Date.now(),
}).catch((e) => console.error("[proxy-usage] Failed to record:", e?.message))
}

return {
content: accumulatedContent,
finishReason,
Expand Down Expand Up @@ -474,6 +507,9 @@ export class ServerProxyService {

if (!res.ok) {
const text = await res.text()
if (text.includes("Insufficient Funds") || text.includes("Credit usage at configured limit")) {
throw new Error("Cloud AI is warming up. Please try again in a moment.")
}
throw new Error(text || "AI proxy generate-object request failed")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const COMMANDS = [
{ cmd: "/interact", desc: "Browser interaction via Firecrawl" },
{ cmd: "/crawl", desc: "Crawl a website via Firecrawl" },
{ cmd: "/parse", desc: "Parse a file (PDF, DOC, etc.) via Firecrawl" },
{ cmd: "/usage", desc: "Show daily token usage and budget for Opus 4.8" },
{ cmd: "/usage", desc: "Show your daily token usage across all models" },
{ cmd: "/token-limit", desc: "Show token limits and usage per model per day" },
{ cmd: "/clear", desc: "Clear current session messages" },
{ cmd: "/new", desc: "Start a new conversation" },
Expand Down
85 changes: 56 additions & 29 deletions apps/supercode-cli/server/src/cli/commands/slashCommands/usage.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,72 @@
import chalk from "chalk"
import prisma from "src/lib/prisma"
import { theme, sectionHeader, heavyDivider, formatTokenCount } from "src/cli/utils/tui.ts"
import { getOrCreateDeviceId } from "src/lib/token-budget"

function todayStart(): Date {
const now = new Date()
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()))
}
import {
theme,
sectionHeader,
heavyDivider,
formatTokenCount,
progressBar,
} from "src/cli/utils/tui.ts"
import { getStoredToken } from "src/lib/token"
import { getUserDailyUsage } from "src/lib/token-budget"

export async function usageCommand(): Promise<void> {
const today = todayStart()
const tomorrow = new Date(today.getTime() + 86_400_000)
const token = await getStoredToken()
if (!token?.access_token) {
console.log()
console.log(heavyDivider())
console.log()
console.log(` ${chalk.hex(theme.muted)("Not authenticated. Run /login first.")}`)
console.log()
console.log(heavyDivider())
console.log()
return
}

const session = await prisma.session.findUnique({
where: { token: token.access_token as string },
include: { user: true },
})
if (!session || session.expiresAt < new Date()) {
console.log()
console.log(heavyDivider())
console.log()
console.log(` ${chalk.hex(theme.muted)("Session expired. Please re-authenticate.")}`)
console.log()
console.log(heavyDivider())
console.log()
return
}

const user = session.user
const usage = await getUserDailyUsage(user.id)

const w = Math.min(process.stdout.columns ?? 80, 72)

console.log()
console.log(heavyDivider())
console.log()
console.log(sectionHeader("Usage History", { accent: "green" }))

const allUsage = await prisma.usageEvent.groupBy({
by: ["model"],
where: {
createdAt: { gte: new Date(Date.now() - 30 * 86_400_000) },
},
_sum: { totalTokens: true },
_count: { id: true },
})
console.log(sectionHeader("Daily Usage", { accent: "green" }))
console.log(` ${chalk.hex(theme.muted)(`${user.email || user.name} · resets at midnight UTC`)}`)
console.log()

if (allUsage.length > 0) {
for (const row of allUsage) {
const tokens = row._sum.totalTokens ?? 0
const calls = row._count.id
const modelLabel = row.model.length > 30 ? row.model.slice(0, 27) + "..." : row.model
console.log(` ${chalk.hex(theme.greenGlow)(modelLabel.padEnd(32))} ${formatTokenCount(tokens).padStart(8)} ${chalk.hex(theme.muted)(`${calls} calls`)}`)
}
} else {
console.log(` ${chalk.hex(theme.muted)("No usage recorded in the last 30 days.")}`)
}
const pct = Math.min(100, Math.round((usage.used / usage.limit) * 100))
const color =
usage.remaining <= 10_000 ? theme.red
: usage.remaining <= 50_000 ? theme.amber
: theme.green

console.log(
` ${chalk.hex(theme.greenGlow)("Used")} ${formatTokenCount(usage.used).padStart(8)} ${progressBar(usage.used, usage.limit, 16)}`
)
console.log(
` ${chalk.hex(theme.greenGlow)("Limit")} ${formatTokenCount(usage.limit).padStart(8)}`
)
console.log(
` ${chalk.hex(color)(chalk.hex(theme.greenGlow)("Remaining"))} ${formatTokenCount(usage.remaining).padStart(8)}`
)
console.log()

console.log(heavyDivider())
console.log()
}
23 changes: 23 additions & 0 deletions apps/supercode-cli/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import prisma from "./lib/prisma"
import { loadEnvOnce } from "./lib/load-env"
import { recordUsage } from "./lib/track-usage"
import { computeCost } from "./lib/pricing"
import { checkDailyTokenBudget } from "./lib/token-budget"
import { registerAnalyticsRoutes } from "./routes/analytics"
import { transcribeAudio } from "./voice/speech"
import { tmpdir } from "os"
Expand Down Expand Up @@ -301,6 +302,11 @@ app.post("/api/ai/chat", async (req, res) => {
return
}

const isByok = provider === "concentrateai" && !!req.body.concentrateAiKey
if (!isByok) {
await checkDailyTokenBudget(user.id)
}

const systemMessages = messages.filter((m: any) => m.role === "system")
const nonSystemMessages = messages.filter((m: any) => m.role !== "system")
const system = systemMessages.map((m: any) => m.content).join("\n")
Expand Down Expand Up @@ -333,6 +339,7 @@ app.post("/api/ai/chat", async (req, res) => {
inputTokens, outputTokens, cachedInputTokens, totalTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, cachedInputTokens),
durationMs: Date.now() - googleStart,
userId: user.id,
})
res.write(JSON.stringify({ type: "finish", reason: await result.finishReason, usage }) + "\n")
res.end()
Expand Down Expand Up @@ -441,6 +448,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - orStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand Down Expand Up @@ -478,6 +486,7 @@ app.post("/api/ai/chat", async (req, res) => {
inputTokens, outputTokens, cachedInputTokens, totalTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, cachedInputTokens),
durationMs: Date.now() - mmStart,
userId: user.id,
})
res.write(JSON.stringify({ type: "finish", reason: await result.finishReason, usage }) + "\n")
res.end()
Expand Down Expand Up @@ -588,6 +597,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - nvidiaStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand Down Expand Up @@ -698,6 +708,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - mdStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand Down Expand Up @@ -809,6 +820,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - orStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand Down Expand Up @@ -974,6 +986,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - caStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand Down Expand Up @@ -1133,6 +1146,7 @@ app.post("/api/ai/chat", async (req, res) => {
totalTokens: inputTokens + outputTokens,
costUsd: computeCost(modelName, inputTokens, outputTokens, 0),
durationMs: Date.now() - scStart,
userId: user.id,
})
res.write(JSON.stringify({
type: "finish", reason: "stop",
Expand All @@ -1149,6 +1163,8 @@ app.post("/api/ai/chat", async (req, res) => {
const msg = String(error)
if (msg.includes("insufficient balance") || msg.includes("402")) {
res.status(402).json({ error: "MiniMax API: insufficient balance. Top up at https://platform.minimax.ai" })
} else if (msg.includes("daily limit of 128K tokens")) {
res.status(429).json({ error: msg })
} else {
res.status(500).json({ error: msg })
}
Expand All @@ -1169,6 +1185,11 @@ app.post("/api/ai/generate-object", async (req, res) => {
return
}

const isByok = provider === "concentrateai" && !!req.body.concentrateAiKey
if (!isByok) {
await checkDailyTokenBudget(user.id)
}

const { generateObject } = await import("ai")

switch (provider) {
Expand Down Expand Up @@ -1327,6 +1348,8 @@ app.post("/api/ai/generate-object", async (req, res) => {
const msg = String(error)
if (msg.includes("insufficient balance") || msg.includes("402")) {
res.status(402).json({ error: "MiniMax API: insufficient balance. Top up at https://platform.minimax.ai" })
} else if (msg.includes("daily limit of 128K tokens")) {
res.status(429).json({ error: msg })
} else {
res.status(500).json({ error: msg })
}
Expand Down
Loading
Loading