(line)
| 248 | } |
| 249 | |
| 250 | function processLogLine(line) { |
| 251 | enqueueLogLine(line); |
| 252 | // Detect LLM usage: service=llm providerID=... modelID=... |
| 253 | // Example: service=llm providerID=openai modelID=gpt-5.2-codex sessionID=... |
| 254 | const isUsage = line.includes('service=llm') && line.includes('stream'); |
| 255 | const isError = line.includes('service=llm') && (line.includes('error=') || line.includes('status=429')); |
| 256 | |
| 257 | if (!isUsage && !isError) return; |
| 258 | |
| 259 | const providerMatch = line.match(/providerID=([^\s]+)/); |
| 260 | const modelMatch = line.match(/modelID=([^\s]+)/); |
| 261 | |
| 262 | if (providerMatch) { |
| 263 | let provider = providerMatch[1]; |
| 264 | const model = modelMatch ? modelMatch[1] : 'unknown'; |
| 265 | |
| 266 | // Normalize providers |
| 267 | if (provider === 'codex') provider = 'openai'; |
| 268 | if (provider === 'claude') provider = 'anthropic'; |
| 269 | |
| 270 | // Map provider to pool namespace |
| 271 | let namespace = provider; |
| 272 | if (provider === 'google') { |
| 273 | const activePlugin = getActiveGooglePlugin(); |
| 274 | namespace = 'google.antigravity'; |
| 275 | } |
| 276 | |
| 277 | const metadata = loadPoolMetadata(); |
| 278 | if (!metadata._quota) metadata._quota = {}; |
| 279 | if (!metadata._quota[namespace]) metadata._quota[namespace] = {}; |
| 280 | |
| 281 | const today = new Date().toISOString().split('T')[0]; |
| 282 | |
| 283 | if (isUsage) { |
| 284 | // Increment usage |
| 285 | metadata._quota[namespace][today] = (metadata._quota[namespace][today] || 0) + 1; |
| 286 | |
| 287 | // Also update active account usage if possible |
| 288 | const studio = loadStudioConfig(); |
| 289 | const activeProfile = studio.activeProfiles?.[provider]; |
| 290 | if (activeProfile && metadata[namespace]?.[activeProfile]) { |
| 291 | metadata[namespace][activeProfile].usageCount = (metadata[namespace][activeProfile].usageCount || 0) + 1; |
| 292 | metadata[namespace][activeProfile].lastUsed = Date.now(); |
| 293 | } |
| 294 | } else if (isError) { |
| 295 | // Check for quota exhaustion patterns |
| 296 | const errorMsg = line.match(/error=(.+)/)?.[1] || ''; |
| 297 | const isQuotaError = line.includes('status=429') || |
| 298 | errorMsg.toLowerCase().includes('quota') || |
| 299 | errorMsg.toLowerCase().includes('rate limit'); |
| 300 | |
| 301 | if (isQuotaError) { |
| 302 | console.log(`[LogWatcher] Detected quota exhaustion for ${namespace}`); |
| 303 | |
| 304 | // Reload metadata to ensure freshness |
| 305 | const currentMeta = loadPoolMetadata(); |
| 306 | if (!currentMeta._quota) currentMeta._quota = {}; |
| 307 | if (!currentMeta._quota[namespace]) currentMeta._quota[namespace] = {}; |
nothing calls this directly
no test coverage detected