diff --git a/public/api-docs.html b/public/api-docs.html index ec671ecc..9b8a266d 100644 --- a/public/api-docs.html +++ b/public/api-docs.html @@ -585,7 +585,7 @@

Server-sent events (SSE) format with real-time updates. Content-Type: text/event-stream

Response (Non-Streaming)

-

JSON object containing session details, assistant messages only (filtered), and token usage summary. Content-Type: application/json

+

JSON object containing session details and assistant messages only (filtered). Content-Type: application/json

Error Response

Returns error details with appropriate HTTP status code.

@@ -674,21 +674,10 @@ data: {"type":"done"} "type": "text", "text": "I've completed the task..." } - ], - "usage": { - "input_tokens": 150, - "output_tokens": 50 - } + ] } } ], - "tokens": { - "inputTokens": 150, - "outputTokens": 50, - "cacheReadTokens": 0, - "cacheCreationTokens": 0, - "totalTokens": 200 - }, "projectPath": "/path/to/project", "branch": { "name": "fix-authentication-bug-abc123", diff --git a/server/claude-sdk.js b/server/claude-sdk.js index 918a7bd6..eb1f5c3f 100644 --- a/server/claude-sdk.js +++ b/server/claude-sdk.js @@ -274,46 +274,6 @@ function transformMessage(sdkMessage) { return sdkMessage; } -/** - * Extracts token usage from SDK result messages - * @param {Object} resultMessage - SDK result message - * @returns {Object|null} Token budget object or null - */ -function extractTokenBudget(resultMessage) { - if (resultMessage.type !== 'result' || !resultMessage.modelUsage) { - return null; - } - - // Get the first model's usage data - const modelKey = Object.keys(resultMessage.modelUsage)[0]; - const modelData = resultMessage.modelUsage[modelKey]; - - if (!modelData) { - return null; - } - - // Use cumulative tokens if available (tracks total for the session) - // Otherwise fall back to per-request tokens - const inputTokens = modelData.cumulativeInputTokens || modelData.inputTokens || 0; - const outputTokens = modelData.cumulativeOutputTokens || modelData.outputTokens || 0; - const cacheReadTokens = modelData.cumulativeCacheReadInputTokens || modelData.cacheReadInputTokens || 0; - const cacheCreationTokens = modelData.cumulativeCacheCreationInputTokens || modelData.cacheCreationInputTokens || 0; - - // Total used = input + output + cache tokens - const totalUsed = inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens; - - // Use configured context window budget from environment (default 160000) - // This is the user's budget limit, not the model's context window - const contextWindow = parseInt(process.env.CONTEXT_WINDOW) || 160000; - - // Token calc logged via token-budget WS event - - return { - used: totalUsed, - total: contextWindow - }; -} - /** * Handles image processing for SDK queries * Saves base64 images to temporary files and returns modified prompt with file paths @@ -657,18 +617,6 @@ async function queryClaudeSDK(command, options = {}, ws) { } ws.send(msg); } - - // Extract and send token budget updates from result messages - if (message.type === 'result') { - const models = Object.keys(message.modelUsage || {}); - if (models.length > 0) { - // Model info available in result message - } - const tokenBudgetData = extractTokenBudget(message); - if (tokenBudgetData) { - ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' })); - } - } } // Clean up session on completion diff --git a/server/index.js b/server/index.js index 235f143b..791fac8d 100755 --- a/server/index.js +++ b/server/index.js @@ -2218,194 +2218,6 @@ app.post('/api/projects/:projectName/upload-images', authenticateToken, async (r } }); -// Get token usage for a specific session -app.get('/api/projects/:projectName/sessions/:sessionId/token-usage', authenticateToken, async (req, res) => { - try { - const { projectName, sessionId } = req.params; - const { provider = 'claude' } = req.query; - const homeDir = os.homedir(); - - // Allow only safe characters in sessionId - const safeSessionId = String(sessionId).replace(/[^a-zA-Z0-9._-]/g, ''); - if (!safeSessionId || safeSessionId !== String(sessionId)) { - return res.status(400).json({ error: 'Invalid sessionId' }); - } - - // Handle Cursor sessions - they use SQLite and don't have token usage info - if (provider === 'cursor') { - return res.json({ - used: 0, - total: 0, - breakdown: { input: 0, cacheCreation: 0, cacheRead: 0 }, - unsupported: true, - message: 'Token usage tracking not available for Cursor sessions' - }); - } - - // Handle Gemini sessions - they are raw logs in our current setup - if (provider === 'gemini') { - return res.json({ - used: 0, - total: 0, - breakdown: { input: 0, cacheCreation: 0, cacheRead: 0 }, - unsupported: true, - message: 'Token usage tracking not available for Gemini sessions' - }); - } - - // Handle Codex sessions - if (provider === 'codex') { - const codexSessionsDir = path.join(homeDir, '.codex', 'sessions'); - - // Find the session file by searching for the session ID - const findSessionFile = async (dir) => { - try { - const entries = await fsPromises.readdir(dir, { withFileTypes: true }); - for (const entry of entries) { - const fullPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - const found = await findSessionFile(fullPath); - if (found) return found; - } else if (entry.name.includes(safeSessionId) && entry.name.endsWith('.jsonl')) { - return fullPath; - } - } - } catch (error) { - // Skip directories we can't read - } - return null; - }; - - const sessionFilePath = await findSessionFile(codexSessionsDir); - - if (!sessionFilePath) { - return res.status(404).json({ error: 'Codex session file not found', sessionId: safeSessionId }); - } - - // Read and parse the Codex JSONL file - let fileContent; - try { - fileContent = await fsPromises.readFile(sessionFilePath, 'utf8'); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ error: 'Session file not found', path: sessionFilePath }); - } - throw error; - } - const lines = fileContent.trim().split('\n'); - let totalTokens = 0; - let contextWindow = 200000; // Default for Codex/OpenAI - - // Find the latest token_count event with info (scan from end) - for (let i = lines.length - 1; i >= 0; i--) { - try { - const entry = JSON.parse(lines[i]); - - // Codex stores token info in event_msg with type: "token_count" - if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) { - const tokenInfo = entry.payload.info; - if (tokenInfo.total_token_usage) { - totalTokens = tokenInfo.total_token_usage.total_tokens || 0; - } - if (tokenInfo.model_context_window) { - contextWindow = tokenInfo.model_context_window; - } - break; // Stop after finding the latest token count - } - } catch (parseError) { - // Skip lines that can't be parsed - continue; - } - } - - return res.json({ - used: totalTokens, - total: contextWindow - }); - } - - // Handle Claude sessions (default) - // Extract actual project path - let projectPath; - try { - projectPath = await extractProjectDirectory(projectName); - } catch (error) { - console.error('Error extracting project directory:', error); - return res.status(500).json({ error: 'Failed to determine project path' }); - } - - // Construct the JSONL file path - // Claude stores session files in ~/.claude/projects/[encoded-project-path]/[session-id].jsonl - // The encoding replaces any non-alphanumeric character (except -) with - - const encodedPath = projectPath.replace(/[^a-zA-Z0-9-]/g, '-'); - const projectDir = path.join(homeDir, '.claude', 'projects', encodedPath); - - const jsonlPath = path.join(projectDir, `${safeSessionId}.jsonl`); - - // Constrain to projectDir - const rel = path.relative(path.resolve(projectDir), path.resolve(jsonlPath)); - if (rel.startsWith('..') || path.isAbsolute(rel)) { - return res.status(400).json({ error: 'Invalid path' }); - } - - // Read and parse the JSONL file - let fileContent; - try { - fileContent = await fsPromises.readFile(jsonlPath, 'utf8'); - } catch (error) { - if (error.code === 'ENOENT') { - return res.status(404).json({ error: 'Session file not found', path: jsonlPath }); - } - throw error; // Re-throw other errors to be caught by outer try-catch - } - const lines = fileContent.trim().split('\n'); - - const parsedContextWindow = parseInt(process.env.CONTEXT_WINDOW, 10); - const contextWindow = Number.isFinite(parsedContextWindow) ? parsedContextWindow : 160000; - let inputTokens = 0; - let cacheCreationTokens = 0; - let cacheReadTokens = 0; - - // Find the latest assistant message with usage data (scan from end) - for (let i = lines.length - 1; i >= 0; i--) { - try { - const entry = JSON.parse(lines[i]); - - // Only count assistant messages which have usage data - if (entry.type === 'assistant' && entry.message?.usage) { - const usage = entry.message.usage; - - // Use token counts from latest assistant message only - inputTokens = usage.input_tokens || 0; - cacheCreationTokens = usage.cache_creation_input_tokens || 0; - cacheReadTokens = usage.cache_read_input_tokens || 0; - - break; // Stop after finding the latest assistant message - } - } catch (parseError) { - // Skip lines that can't be parsed - continue; - } - } - - // Calculate total context usage (excluding output_tokens, as per ccusage) - const totalUsed = inputTokens + cacheCreationTokens + cacheReadTokens; - - res.json({ - used: totalUsed, - total: contextWindow, - breakdown: { - input: inputTokens, - cacheCreation: cacheCreationTokens, - cacheRead: cacheReadTokens - } - }); - } catch (error) { - console.error('Error reading session token usage:', error); - res.status(500).json({ error: 'Failed to read session token usage' }); - } -}); - // Serve React app for all other routes (excluding static files) app.get('*', (req, res) => { // Skip requests for static assets (files with extensions) diff --git a/server/openai-codex.js b/server/openai-codex.js index 0169a3b6..b4aaa292 100644 --- a/server/openai-codex.js +++ b/server/openai-codex.js @@ -129,8 +129,7 @@ function transformCodexEvent(event) { case 'turn.completed': return { - type: 'turn_complete', - usage: event.usage + type: 'turn_complete' }; case 'turn.failed': @@ -279,12 +278,6 @@ export async function queryCodex(command, options = {}, ws) { error: terminalFailure }); } - - // Extract and send token usage if available (normalized to match Claude format) - if (event.type === 'turn.completed' && event.usage) { - const totalTokens = (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0); - sendMessage(ws, createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: { used: totalTokens, total: 200000 }, sessionId: currentSessionId, provider: 'codex' })); - } } // Send completion event diff --git a/server/projects.js b/server/projects.js index d8ccaeb7..e07a29e7 100755 --- a/server/projects.js +++ b/server/projects.js @@ -1618,7 +1618,6 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) { } const messages = []; - let tokenUsage = null; const fileStream = fsSync.createReadStream(sessionFilePath); const rl = readline.createInterface({ input: fileStream, @@ -1647,17 +1646,6 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) { try { const entry = JSON.parse(line); - // Extract token usage from token_count events (keep latest) - if (entry.type === 'event_msg' && entry.payload?.type === 'token_count' && entry.payload?.info) { - const info = entry.payload.info; - if (info.total_token_usage) { - tokenUsage = { - used: info.total_token_usage.total_tokens || 0, - total: info.model_context_window || 200000 - }; - } - } - // Use event_msg.user_message for user-visible inputs. if (entry.type === 'event_msg' && isVisibleCodexUserMessage(entry.payload)) { messages.push({ @@ -1820,11 +1808,10 @@ async function getCodexSessionMessages(sessionId, limit = null, offset = 0) { hasMore, offset, limit, - tokenUsage }; } - return { messages, tokenUsage }; + return { messages }; } catch (error) { console.error(`Error reading Codex session messages for ${sessionId}:`, error); diff --git a/server/providers/codex/adapter.js b/server/providers/codex/adapter.js index c9cae00f..437e6dd2 100644 --- a/server/providers/codex/adapter.js +++ b/server/providers/codex/adapter.js @@ -214,7 +214,6 @@ export const codexAdapter = { const rawMessages = Array.isArray(result) ? result : (result.messages || []); const total = Array.isArray(result) ? rawMessages.length : (result.total || 0); const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore); - const tokenUsage = result.tokenUsage || null; const normalized = []; for (const raw of rawMessages) { @@ -242,7 +241,6 @@ export const codexAdapter = { hasMore, offset, limit, - tokenUsage, }; }, }; diff --git a/server/providers/gemini/adapter.js b/server/providers/gemini/adapter.js index df303c36..831f9522 100644 --- a/server/providers/gemini/adapter.js +++ b/server/providers/gemini/adapter.js @@ -53,14 +53,7 @@ export function normalizeMessage(raw, sessionId) { } if (raw.type === 'result') { - const msgs = [createNormalizedMessage({ sessionId, timestamp: ts, provider: PROVIDER, kind: 'stream_end' })]; - if (raw.stats?.total_tokens) { - msgs.push(createNormalizedMessage({ - sessionId, timestamp: ts, provider: PROVIDER, - kind: 'status', text: 'Complete', tokens: raw.stats.total_tokens, canInterrupt: false, - })); - } - return msgs; + return [createNormalizedMessage({ sessionId, timestamp: ts, provider: PROVIDER, kind: 'stream_end' })]; } if (raw.type === 'error') { diff --git a/server/providers/types.js b/server/providers/types.js index 5541525b..a1b20e6e 100644 --- a/server/providers/types.js +++ b/server/providers/types.js @@ -41,7 +41,7 @@ * - stream_end: (no extra fields) * - error: content * - complete: (no extra fields) - * - status: text, tokens?, canInterrupt? + * - status: text, canInterrupt? * - permission_request: requestId, toolName, input, context? * - permission_cancelled: requestId * - session_created: newSessionId @@ -66,7 +66,6 @@ * @property {boolean} hasMore - Whether more messages exist before the current page * @property {number} offset - Current offset * @property {number|null} limit - Page size used - * @property {object} [tokenUsage] - Token usage data (provider-specific) */ // ─── Provider Adapter Interface ────────────────────────────────────────────── diff --git a/server/routes/agent.js b/server/routes/agent.js index d027b91c..74f4f97d 100644 --- a/server/routes/agent.js +++ b/server/routes/agent.js @@ -546,7 +546,12 @@ class ResponseCollector { const parsed = JSON.parse(msg); // Only include claude-response messages with assistant type if (parsed.type === 'claude-response' && parsed.data && parsed.data.type === 'assistant') { - assistantMessages.push(parsed.data); + const assistantMessage = { ...parsed.data }; + if (assistantMessage.message?.usage) { + assistantMessage.message = { ...assistantMessage.message }; + delete assistantMessage.message.usage; + } + assistantMessages.push(assistantMessage); } } catch (e) { // Not JSON, skip @@ -556,49 +561,6 @@ class ResponseCollector { return assistantMessages; } - - /** - * Calculate total tokens from all messages - */ - getTotalTokens() { - let totalInput = 0; - let totalOutput = 0; - let totalCacheRead = 0; - let totalCacheCreation = 0; - - for (const msg of this.messages) { - let data = msg; - - // Parse if string - if (typeof msg === 'string') { - try { - data = JSON.parse(msg); - } catch (e) { - continue; - } - } - - // Extract usage from claude-response messages - if (data && data.type === 'claude-response' && data.data) { - const msgData = data.data; - if (msgData.message && msgData.message.usage) { - const usage = msgData.message.usage; - totalInput += usage.input_tokens || 0; - totalOutput += usage.output_tokens || 0; - totalCacheRead += usage.cache_read_input_tokens || 0; - totalCacheCreation += usage.cache_creation_input_tokens || 0; - } - } - } - - return { - inputTokens: totalInput, - outputTokens: totalOutput, - cacheReadTokens: totalCacheRead, - cacheCreationTokens: totalCacheCreation, - totalTokens: totalInput + totalOutput + totalCacheRead + totalCacheCreation - }; - } } // =============================== @@ -789,13 +751,6 @@ class ResponseCollector { * success: true, * sessionId: "session-123", * messages: [...], // Assistant messages only (filtered) - * tokens: { - * inputTokens: 150, - * outputTokens: 50, - * cacheReadTokens: 0, - * cacheCreationTokens: 0, - * totalTokens: 200 - * }, * projectPath: "/path/to/project", * branch: { // Only if createBranch=true * name: "feature/xyz", @@ -1173,15 +1128,13 @@ router.post('/', validateExternalApiKey, async (req, res) => { // Streaming mode: end the SSE stream writer.end(); } else { - // Non-streaming mode: send filtered messages and token summary as JSON + // Non-streaming mode: send filtered messages as JSON const assistantMessages = writer.getAssistantMessages(); - const tokenSummary = writer.getTotalTokens(); const response = { success: true, sessionId: writer.getSessionId(), messages: assistantMessages, - tokens: tokenSummary, projectPath: finalProjectPath }; diff --git a/server/routes/commands.js b/server/routes/commands.js index 388a8f76..7c3909e5 100644 --- a/server/routes/commands.js +++ b/server/routes/commands.js @@ -97,12 +97,6 @@ const builtInCommands = [ namespace: 'builtin', metadata: { type: 'builtin' } }, - { - name: '/cost', - description: 'Display token usage and cost information', - namespace: 'builtin', - metadata: { type: 'builtin' } - }, { name: '/memory', description: 'Open CLAUDE.md memory file for editing', @@ -209,86 +203,6 @@ Custom commands can be created in: }; }, - '/cost': async (args, context) => { - const tokenUsage = context?.tokenUsage || {}; - const provider = context?.provider || 'claude'; - const model = - context?.model || - (provider === 'cursor' - ? CURSOR_MODELS.DEFAULT - : provider === 'codex' - ? CODEX_MODELS.DEFAULT - : CLAUDE_MODELS.DEFAULT); - - const used = Number(tokenUsage.used ?? tokenUsage.totalUsed ?? tokenUsage.total_tokens ?? 0) || 0; - const total = - Number( - tokenUsage.total ?? - tokenUsage.contextWindow ?? - parseInt(process.env.CONTEXT_WINDOW || '160000', 10), - ) || 160000; - const percentage = total > 0 ? Number(((used / total) * 100).toFixed(1)) : 0; - - const inputTokensRaw = - Number( - tokenUsage.inputTokens ?? - tokenUsage.input ?? - tokenUsage.cumulativeInputTokens ?? - tokenUsage.promptTokens ?? - 0, - ) || 0; - const outputTokens = - Number( - tokenUsage.outputTokens ?? - tokenUsage.output ?? - tokenUsage.cumulativeOutputTokens ?? - tokenUsage.completionTokens ?? - 0, - ) || 0; - const cacheTokens = - Number( - tokenUsage.cacheReadTokens ?? - tokenUsage.cacheCreationTokens ?? - tokenUsage.cacheTokens ?? - tokenUsage.cachedTokens ?? - 0, - ) || 0; - - // If we only have total used tokens, treat them as input for display/estimation. - const inputTokens = - inputTokensRaw > 0 || outputTokens > 0 || cacheTokens > 0 ? inputTokensRaw + cacheTokens : used; - - // Rough default rates by provider (USD / 1M tokens). - const pricingByProvider = { - claude: { input: 3, output: 15 }, - cursor: { input: 3, output: 15 }, - codex: { input: 1.5, output: 6 }, - }; - const rates = pricingByProvider[provider] || pricingByProvider.claude; - - const inputCost = (inputTokens / 1_000_000) * rates.input; - const outputCost = (outputTokens / 1_000_000) * rates.output; - const totalCost = inputCost + outputCost; - - return { - type: 'builtin', - action: 'cost', - data: { - tokenUsage: { - used, - total, - percentage, - }, - cost: { - input: inputCost.toFixed(4), - output: outputCost.toFixed(4), - total: totalCost.toFixed(4), - }, - model, - }, - }; - }, - '/status': async (args, context) => { // Read version from package.json const packageJsonPath = path.join(path.dirname(__dirname), '..', 'package.json'); diff --git a/src/components/chat/hooks/useChatComposerState.ts b/src/components/chat/hooks/useChatComposerState.ts index 6e84982d..bb8b0420 100644 --- a/src/components/chat/hooks/useChatComposerState.ts +++ b/src/components/chat/hooks/useChatComposerState.ts @@ -42,7 +42,6 @@ interface UseChatComposerStateArgs { geminiModel: string; isLoading: boolean; canAbortSession: boolean; - tokenBudget: Record | null; sendMessage: (message: unknown) => void; sendByCtrlEnter?: boolean; onSessionActive?: (sessionId?: string | null) => void; @@ -57,7 +56,7 @@ interface UseChatComposerStateArgs { rewindMessages: (count: number) => void; setIsLoading: (loading: boolean) => void; setCanAbortSession: (canAbort: boolean) => void; - setClaudeStatus: (status: { text: string; tokens: number; can_interrupt: boolean } | null) => void; + setClaudeStatus: (status: { text: string; can_interrupt: boolean } | null) => void; setIsUserScrolledUp: (isScrolledUp: boolean) => void; setPendingPermissionRequests: Dispatch>; } @@ -114,7 +113,6 @@ export function useChatComposerState({ geminiModel, isLoading, canAbortSession, - tokenBudget, sendMessage, sendByCtrlEnter, onSessionActive, @@ -176,12 +174,6 @@ export function useChatComposerState({ }); break; - case 'cost': { - const costMessage = `**Token Usage**: ${data.tokenUsage.used.toLocaleString()} / ${data.tokenUsage.total.toLocaleString()} (${data.tokenUsage.percentage}%)\n\n**Estimated Cost**:\n- Input: $${data.cost.input}\n- Output: $${data.cost.output}\n- **Total**: $${data.cost.total}\n\n**Model**: ${data.model}`; - addMessage({ type: 'assistant', content: costMessage, timestamp: Date.now() }); - break; - } - case 'status': { const statusMessage = `**System Status**\n\n- Version: ${data.version}\n- Uptime: ${data.uptime}\n- Model: ${data.model}\n- Provider: ${data.provider}\n- Node.js: ${data.nodeVersion}\n- Platform: ${data.platform}`; addMessage({ type: 'assistant', content: statusMessage, timestamp: Date.now() }); @@ -282,7 +274,6 @@ export function useChatComposerState({ sessionId: currentSessionId, provider, model: provider === 'cursor' ? cursorModel : provider === 'codex' ? codexModel : provider === 'gemini' ? geminiModel : claudeModel, - tokenUsage: tokenBudget, }; const response = await authenticatedFetch('/api/commands/execute', { @@ -339,7 +330,6 @@ export function useChatComposerState({ provider, selectedProject, addMessage, - tokenBudget, ], ); @@ -543,7 +533,6 @@ export function useChatComposerState({ setCanAbortSession(true); setClaudeStatus({ text: 'Processing', - tokens: 0, can_interrupt: true, }); diff --git a/src/components/chat/hooks/useChatRealtimeHandlers.ts b/src/components/chat/hooks/useChatRealtimeHandlers.ts index 6d734730..0c600da6 100644 --- a/src/components/chat/hooks/useChatRealtimeHandlers.ts +++ b/src/components/chat/hooks/useChatRealtimeHandlers.ts @@ -38,9 +38,7 @@ type LatestChatMessage = { provider?: string; content?: string; text?: string; - tokens?: number; canInterrupt?: boolean; - tokenBudget?: unknown; newSessionId?: string; aborted?: boolean; [key: string]: any; @@ -55,8 +53,7 @@ interface UseChatRealtimeHandlersArgs { setCurrentSessionId: (sessionId: string | null) => void; setIsLoading: (loading: boolean) => void; setCanAbortSession: (canAbort: boolean) => void; - setClaudeStatus: (status: { text: string; tokens: number; can_interrupt: boolean } | null) => void; - setTokenBudget: (budget: Record | null) => void; + setClaudeStatus: (status: { text: string; can_interrupt: boolean } | null) => void; setPendingPermissionRequests: Dispatch>; pendingViewSessionRef: MutableRefObject; streamBufferRef: MutableRefObject; @@ -85,7 +82,6 @@ export function useChatRealtimeHandlers({ setIsLoading, setCanAbortSession, setClaudeStatus, - setTokenBudget, setPendingPermissionRequests, pendingViewSessionRef, streamBufferRef, @@ -140,7 +136,6 @@ export function useChatRealtimeHandlers({ if (status) { const statusInfo = { text: status.text || 'Working...', - tokens: status.tokens || 0, can_interrupt: status.can_interrupt !== undefined ? status.can_interrupt : true, }; setClaudeStatus(statusInfo); @@ -311,7 +306,7 @@ export function useChatRealtimeHandlers({ }); setIsLoading(true); setCanAbortSession(true); - setClaudeStatus({ text: 'Waiting for permission', tokens: 0, can_interrupt: true }); + setClaudeStatus({ text: 'Waiting for permission', can_interrupt: true }); break; } @@ -323,12 +318,9 @@ export function useChatRealtimeHandlers({ } case 'status': { - if (msg.text === 'token_budget' && msg.tokenBudget) { - setTokenBudget(msg.tokenBudget as Record); - } else if (msg.text) { + if (msg.text) { setClaudeStatus({ text: msg.text, - tokens: msg.tokens || 0, can_interrupt: msg.canInterrupt !== undefined ? msg.canInterrupt : true, }); setIsLoading(true); @@ -352,7 +344,6 @@ export function useChatRealtimeHandlers({ setIsLoading, setCanAbortSession, setClaudeStatus, - setTokenBudget, setPendingPermissionRequests, pendingViewSessionRef, streamBufferRef, diff --git a/src/components/chat/hooks/useChatSessionState.ts b/src/components/chat/hooks/useChatSessionState.ts index e952ee1a..6fee3261 100644 --- a/src/components/chat/hooks/useChatSessionState.ts +++ b/src/components/chat/hooks/useChatSessionState.ts @@ -1,6 +1,5 @@ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import type { MutableRefObject } from 'react'; -import { authenticatedFetch } from '../../../utils/api'; import type { ChatMessage, Provider } from '../types/types'; import type { Project, ProjectSession, SessionProvider } from '../../../types/app'; import { createCachedDiffCalculator, type DiffCalculator } from '../utils/messageTransforms'; @@ -108,9 +107,8 @@ export function useChatSessionState({ const [totalMessages, setTotalMessages] = useState(0); const [canAbortSession, setCanAbortSession] = useState(false); const [isUserScrolledUp, setIsUserScrolledUp] = useState(false); - const [tokenBudget, setTokenBudget] = useState | null>(null); const [visibleMessageCount, setVisibleMessageCount] = useState(INITIAL_VISIBLE_MESSAGES); - const [claudeStatus, setClaudeStatus] = useState<{ text: string; tokens: number; can_interrupt: boolean } | null>(null); + const [claudeStatus, setClaudeStatus] = useState<{ text: string; can_interrupt: boolean } | null>(null); const [allMessagesLoaded, setAllMessagesLoaded] = useState(false); const [isLoadingAllMessages, setIsLoadingAllMessages] = useState(false); const [loadAllJustFinished, setLoadAllJustFinished] = useState(false); @@ -319,7 +317,6 @@ export function useChatSessionState({ messagesOffsetRef.current = 0; setHasMoreMessages(false); setTotalMessages(0); - setTokenBudget(null); lastLoadedSessionKeyRef.current = null; return; } @@ -355,7 +352,6 @@ export function useChatSessionState({ if (loadAllFinishedTimerRef.current) clearTimeout(loadAllFinishedTimerRef.current); if (sessionChanged) { - setTokenBudget(null); setIsLoading(false); } @@ -383,7 +379,6 @@ export function useChatSessionState({ if (slot) { setHasMoreMessages(slot.hasMore); setTotalMessages(slot.total); - if (slot.tokenUsage) setTokenBudget(slot.tokenUsage as Record); } setIsLoadingSessionMessages(false); }).catch(() => { @@ -539,31 +534,6 @@ export function useChatSessionState({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [chatMessages.length, isLoadingSessionMessages, searchTarget]); - // Token usage fetch for Claude - useEffect(() => { - if (!selectedProject || !selectedSession?.id || selectedSession.id.startsWith('new-session-')) { - setTokenBudget(null); - return; - } - const sessionProvider = selectedSession.__provider || 'claude'; - if (sessionProvider !== 'claude') return; - - const fetchInitialTokenUsage = async () => { - try { - const url = `/api/projects/${selectedProject.name}/sessions/${selectedSession.id}/token-usage`; - const response = await authenticatedFetch(url); - if (response.ok) { - setTokenBudget(await response.json()); - } else { - setTokenBudget(null); - } - } catch (error) { - console.error('Failed to fetch initial token usage:', error); - } - }; - fetchInitialTokenUsage(); - }, [selectedProject, selectedSession?.id, selectedSession?.__provider]); - const visibleMessages = useMemo(() => { if (chatMessages.length <= visibleMessageCount) return chatMessages; return chatMessages.slice(-visibleMessageCount); @@ -713,8 +683,6 @@ export function useChatSessionState({ setCanAbortSession, isUserScrolledUp, setIsUserScrolledUp, - tokenBudget, - setTokenBudget, visibleMessageCount, visibleMessages, loadEarlierMessages, diff --git a/src/components/chat/view/ChatInterface.tsx b/src/components/chat/view/ChatInterface.tsx index cb78222c..3da23278 100644 --- a/src/components/chat/view/ChatInterface.tsx +++ b/src/components/chat/view/ChatInterface.tsx @@ -96,8 +96,6 @@ function ChatInterface({ setCanAbortSession, isUserScrolledUp, setIsUserScrolledUp, - tokenBudget, - setTokenBudget, visibleMessageCount, visibleMessages, loadEarlierMessages, @@ -183,7 +181,6 @@ function ChatInterface({ geminiModel, isLoading, canAbortSession, - tokenBudget, sendMessage, sendByCtrlEnter, onSessionActive, @@ -227,7 +224,6 @@ function ChatInterface({ setIsLoading, setCanAbortSession, setClaudeStatus, - setTokenBudget, setPendingPermissionRequests, pendingViewSessionRef, streamBufferRef, @@ -352,7 +348,6 @@ function ChatInterface({ onModeSwitch={cyclePermissionMode} thinkingMode={thinkingMode} setThinkingMode={setThinkingMode} - tokenBudget={tokenBudget} slashCommandsCount={slashCommandsCount} onToggleCommandMenu={handleToggleCommandMenu} hasInput={Boolean(input.trim())} diff --git a/src/components/chat/view/subcomponents/ChatComposer.tsx b/src/components/chat/view/subcomponents/ChatComposer.tsx index 2bf8eb50..8a77ab96 100644 --- a/src/components/chat/view/subcomponents/ChatComposer.tsx +++ b/src/components/chat/view/subcomponents/ChatComposer.tsx @@ -41,7 +41,7 @@ interface ChatComposerProps { decision: { allow?: boolean; message?: string; rememberEntry?: string | null; updatedInput?: unknown }, ) => void; handleGrantToolPermission: (suggestion: { entry: string; toolName: string }) => { success: boolean }; - claudeStatus: { text: string; tokens: number; can_interrupt: boolean } | null; + claudeStatus: { text: string; can_interrupt: boolean } | null; isLoading: boolean; onAbortSession: () => void; provider: Provider | string; @@ -49,7 +49,6 @@ interface ChatComposerProps { onModeSwitch: () => void; thinkingMode: string; setThinkingMode: Dispatch>; - tokenBudget: { used?: number; total?: number } | null; slashCommandsCount: number; onToggleCommandMenu: () => void; hasInput: boolean; @@ -106,7 +105,6 @@ export default function ChatComposer({ onModeSwitch, thinkingMode, setThinkingMode, - tokenBudget, slashCommandsCount, onToggleCommandMenu, hasInput, @@ -194,7 +192,6 @@ export default function ChatComposer({ provider={provider} thinkingMode={thinkingMode} setThinkingMode={setThinkingMode} - tokenBudget={tokenBudget} slashCommandsCount={slashCommandsCount} onToggleCommandMenu={onToggleCommandMenu} hasInput={hasInput} diff --git a/src/components/chat/view/subcomponents/ChatInputControls.tsx b/src/components/chat/view/subcomponents/ChatInputControls.tsx index 1c05c02a..7715f534 100644 --- a/src/components/chat/view/subcomponents/ChatInputControls.tsx +++ b/src/components/chat/view/subcomponents/ChatInputControls.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import type { PermissionMode, Provider } from '../../types/types'; import ThinkingModeSelector from './ThinkingModeSelector'; -import TokenUsagePie from './TokenUsagePie'; interface ChatInputControlsProps { permissionMode: PermissionMode | string; @@ -10,7 +9,6 @@ interface ChatInputControlsProps { provider: Provider | string; thinkingMode: string; setThinkingMode: React.Dispatch>; - tokenBudget: { used?: number; total?: number } | null; slashCommandsCount: number; onToggleCommandMenu: () => void; hasInput: boolean; @@ -26,7 +24,6 @@ export default function ChatInputControls({ provider, thinkingMode, setThinkingMode, - tokenBudget, slashCommandsCount, onToggleCommandMenu, hasInput, @@ -78,8 +75,6 @@ export default function ChatInputControls({ {}} className="" /> )} - -