|
1 | 1 | import { createOpenAICompatible } from "@ai-sdk/openai-compatible" |
2 | | -import { streamText, stepCountIs, type ModelMessage, type LanguageModel } from "ai" |
| 2 | +import { streamText, stepCountIs, type ModelMessage, type LanguageModel, type LanguageModelUsage } from "ai" |
3 | 3 | import chalk from "chalk" |
4 | 4 | import { recordUsage } from "../../lib/track-usage" |
5 | 5 | import { computeCost } from "../../lib/pricing" |
| 6 | +import { checkDailyBudget, checkQueryLimit, DAILY_QUERY_LIMIT, OPUS_MODEL_ID, getOrCreateDeviceId } from "../../lib/token-budget" |
6 | 7 | import { isEmptyToolResult, isDeniedToolResult, summarizeToolResult, tcName } from "./tool-result" |
7 | 8 |
|
8 | 9 | const CONCENTRATE_API_KEY = process.env.CONCENTRATEAI_API_KEY || "" |
@@ -77,6 +78,37 @@ export class ConcentrateService { |
77 | 78 | const signalHandler = signal ? () => streamAbortController.abort() : undefined |
78 | 79 | signalHandler && signal!.addEventListener("abort", signalHandler, { once: true }) |
79 | 80 |
|
| 81 | + const isOpus = this.modelName === OPUS_MODEL_ID |
| 82 | + const deviceId = await getOrCreateDeviceId() |
| 83 | + |
| 84 | + if (isOpus) { |
| 85 | + const [tokenBudget, queryLimit] = await Promise.all([ |
| 86 | + checkDailyBudget(), |
| 87 | + checkQueryLimit(deviceId), |
| 88 | + ]) |
| 89 | + |
| 90 | + if (!tokenBudget.allowed || !queryLimit.allowed) { |
| 91 | + const reasons: string[] = [] |
| 92 | + if (!tokenBudget.allowed) reasons.push(`Token budget used: ${tokenBudget.used.toLocaleString()} / ${128_000..toLocaleString()}`) |
| 93 | + if (!queryLimit.allowed) reasons.push(`Query limit used: ${queryLimit.used} / ${DAILY_QUERY_LIMIT}`) |
| 94 | + const msg = [ |
| 95 | + chalk.red("╔══ Daily usage limit exceeded ══╗"), |
| 96 | + chalk.red(`║ Model: anthropic/claude-opus-4-8`), |
| 97 | + ...reasons.map(r => chalk.red(`║ ${r}`)), |
| 98 | + chalk.red(`║ Resets: ${new Date(tokenBudget.resetTime).toLocaleDateString()}`), |
| 99 | + chalk.red(`╚════════════════════════════════════╝`), |
| 100 | + ``, |
| 101 | + chalk.yellow(`ℹ Switch to ${chalk.cyan("/model minimax-m3")} to continue chatting.`), |
| 102 | + `Run ${chalk.cyan("/usage")} to check your usage across all models.`, |
| 103 | + ].join("\n") |
| 104 | + return { |
| 105 | + content: msg, |
| 106 | + finishReason: "stop" as const, |
| 107 | + usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0, inputTokenDetails: {}, outputTokenDetails: {} } as LanguageModelUsage, |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + |
80 | 112 | try { |
81 | 113 | const systemMessages = messages.filter(m => m.role === "system") |
82 | 114 | const nonSystemMessages = messages.filter(m => m.role !== "system") |
@@ -120,6 +152,7 @@ export class ConcentrateService { |
120 | 152 | totalTokens: inputTokens + outputTokens, |
121 | 153 | costUsd: computeCost(this.modelName, inputTokens, outputTokens, 0), |
122 | 154 | durationMs: null, |
| 155 | + userId: deviceId, |
123 | 156 | }) |
124 | 157 | return { |
125 | 158 | content, |
@@ -155,6 +188,7 @@ export class ConcentrateService { |
155 | 188 | totalTokens: usage.totalTokens ?? 0, |
156 | 189 | costUsd: computeCost(this.modelName, usage.inputTokens ?? 0, usage.outputTokens ?? 0, usage.inputTokenDetails?.cacheReadTokens ?? 0), |
157 | 190 | durationMs: null, |
| 191 | + userId: deviceId, |
158 | 192 | }) |
159 | 193 |
|
160 | 194 | return { |
@@ -182,7 +216,7 @@ export class ConcentrateService { |
182 | 216 | messages: nonSystemMessages, |
183 | 217 | system, |
184 | 218 | tools, |
185 | | - stopWhen: stepCountIs(8), |
| 219 | + stopWhen: stepCountIs(isOpus ? 5 : 8), |
186 | 220 | abortSignal: streamAbortController.signal, |
187 | 221 | prepareStep: async ({ messages }) => { |
188 | 222 | if (stopForDenialLoop) { |
@@ -296,6 +330,7 @@ export class ConcentrateService { |
296 | 330 | totalTokens: inputTokens + outputTokens, |
297 | 331 | costUsd: computeCost(this.modelName, inputTokens, outputTokens, 0), |
298 | 332 | durationMs: null, |
| 333 | + userId: deviceId, |
299 | 334 | }) |
300 | 335 | return { |
301 | 336 | content, |
@@ -326,19 +361,20 @@ export class ConcentrateService { |
326 | 361 | provider: "concentrateai", |
327 | 362 | model: this.modelName, |
328 | 363 | inputTokens: usage.inputTokens ?? 0, |
329 | | - outputTokens: usage.outputTokens ?? 0, |
330 | | - cachedInputTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0, |
331 | | - totalTokens: usage.totalTokens ?? 0, |
332 | | - costUsd: computeCost(this.modelName, usage.inputTokens ?? 0, usage.outputTokens ?? 0, usage.inputTokenDetails?.cacheReadTokens ?? 0), |
333 | | - durationMs: null, |
334 | | - }) |
| 364 | + outputTokens: usage.outputTokens ?? 0, |
| 365 | + cachedInputTokens: usage.inputTokenDetails?.cacheReadTokens ?? 0, |
| 366 | + totalTokens: usage.totalTokens ?? 0, |
| 367 | + costUsd: computeCost(this.modelName, usage.inputTokens ?? 0, usage.outputTokens ?? 0, usage.inputTokenDetails?.cacheReadTokens ?? 0), |
| 368 | + durationMs: null, |
| 369 | + userId: deviceId, |
| 370 | + }) |
335 | 371 |
|
336 | | - return { |
337 | | - content: fullResponse, |
338 | | - finishReason, |
339 | | - usage, |
340 | | - } |
341 | | - } catch (error: any) { |
| 372 | + return { |
| 373 | + content: fullResponse, |
| 374 | + finishReason, |
| 375 | + usage, |
| 376 | + } |
| 377 | + } catch (error: any) { |
342 | 378 | if (error?.name === "AbortError") throw error |
343 | 379 | console.error(chalk.red("ConcentrateAI Service Error:"), error instanceof Error ? error.message : String(error)) |
344 | 380 | throw error |
|
0 commit comments