fix(quotas): unifier le décompte IA (BYOK, rollback) et combler les fuites
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 6m55s
CI / Deploy production (on server) (push) Successful in 36s

Centralise la réserve via ai-quota, corrige admin unavailable (-1), brancher les routes sans quota et le host-pays brainstorm, avec usage-meter élargi, noms de clusters, MCP et ajustements dashboard/insights.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-07-15 20:42:25 +00:00
parent 30da592ba2
commit 4fe31ebc99
75 changed files with 2949 additions and 785 deletions

View File

@@ -17,6 +17,8 @@ import { extractAndDownloadImages, extractImageUrlsFromHtml, downloadImage } fro
import { calculateNextRun } from '@/lib/agents/schedule'
import { markdownToHtml } from '@/lib/markdown-to-html'
import { createNotification } from '@/app/actions/notifications'
import { withAiQuota } from '@/lib/ai-quota'
import type { FeatureName } from '@/lib/quota-utils'
// Import tools for side-effect registration
import '../tools'
@@ -25,6 +27,12 @@ import '../tools'
export type AgentType = 'scraper' | 'researcher' | 'monitor' | 'custom' | 'slide-generator' | 'excalidraw-generator' | 'task-extractor'
function quotaFeatureForAgentType(type: string): FeatureName {
if (type === 'slide-generator') return 'slide_generate'
if (type === 'excalidraw-generator') return 'excalidraw_generate'
return 'reformulate'
}
export interface AgentExecutionResult {
success: boolean
actionId: string
@@ -1759,36 +1767,45 @@ export async function executeAgent(agentId: string, userId: string, promptOverri
// Detect user language
const lang = await getUserLanguage(userId)
const quotaFeature = quotaFeatureForAgentType(agent.type || 'scraper')
try {
let result: AgentExecutionResult
const result = await withAiQuota(
userId,
quotaFeature,
async () => {
let inner: AgentExecutionResult
const hasTools = agent.tools && agent.tools !== '[]' && agent.tools !== 'null'
if (hasTools) {
result = await executeToolUseAgent(agent, action.id, lang, promptOverride)
inner = await executeToolUseAgent(agent, action.id, lang, promptOverride)
} else {
switch ((agent.type || 'scraper') as AgentType) {
case 'scraper':
result = await executeScraperAgent(agent, action.id, lang)
inner = await executeScraperAgent(agent, action.id, lang)
break
case 'researcher':
result = await executeResearcherAgent(agent, action.id, lang)
inner = await executeResearcherAgent(agent, action.id, lang)
break
case 'monitor':
result = await executeMonitorAgent(agent, action.id, lang)
inner = await executeMonitorAgent(agent, action.id, lang)
break
case 'custom':
result = await executeCustomAgent(agent, action.id, lang)
inner = await executeCustomAgent(agent, action.id, lang)
break
case 'slide-generator':
case 'excalidraw-generator':
result = await executeToolUseAgent(agent, action.id, lang, promptOverride)
inner = await executeToolUseAgent(agent, action.id, lang, promptOverride)
break
default:
result = await executeScraperAgent(agent, action.id, lang)
inner = await executeScraperAgent(agent, action.id, lang)
}
}
return inner
},
{ lane: 'chat' },
)
const nextRunUpdate: Record<string, Date | null> = {}
if (agent.frequency !== 'manual') {