From 3c9fdd66b79a55254e856e9120dcc7dbcc8d54e7 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Wed, 23 Jul 2025 14:51:33 +0200 Subject: [PATCH 1/3] fix issues with token counting --- package.json | 2 +- src/agent/grok-agent.ts | 15 +++++++++++---- src/ui/components/loading-spinner.tsx | 3 ++- src/utils/token-counter.ts | 17 +++++++++++++++++ 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 018b867a..f455f80d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vibe-kit/grok-cli", - "version": "0.0.5", + "version": "0.0.6", "description": "An open-source AI agent that brings the power of Grok directly into your terminal.", "main": "dist/index.js", "bin": { diff --git a/src/agent/grok-agent.ts b/src/agent/grok-agent.ts index 6abf6272..2f98a50a 100644 --- a/src/agent/grok-agent.ts +++ b/src/agent/grok-agent.ts @@ -325,7 +325,7 @@ Current working directory: ${process.cwd()}`, this.messages.push({ role: "user", content: message }); // Calculate input tokens - const inputTokens = this.tokenCounter.countMessageTokens( + let inputTokens = this.tokenCounter.countMessageTokens( this.messages as any ); yield { @@ -396,9 +396,9 @@ Current working directory: ${process.cwd()}`, if (chunk.choices[0].delta?.content) { accumulatedContent += chunk.choices[0].delta.content; - // Update token count in real-time - const currentOutputTokens = - this.tokenCounter.estimateStreamingTokens(accumulatedContent); + // Update token count in real-time including accumulated content and any tool calls + const currentOutputTokens = this.tokenCounter.estimateStreamingTokens(accumulatedContent) + + (accumulatedMessage.tool_calls ? this.tokenCounter.countTokens(JSON.stringify(accumulatedMessage.tool_calls)) : 0); totalOutputTokens = currentOutputTokens; yield { @@ -483,6 +483,13 @@ Current working directory: ${process.cwd()}`, }); } + // Update token count after processing all tool calls to include tool results + inputTokens = this.tokenCounter.countMessageTokens(this.messages as any); + yield { + type: "token_count", + tokenCount: inputTokens + totalOutputTokens, + }; + // Continue the loop to get the next response (which might have more tool calls) } else { // No tool calls, we're done diff --git a/src/ui/components/loading-spinner.tsx b/src/ui/components/loading-spinner.tsx index f36d562f..8baa8049 100644 --- a/src/ui/components/loading-spinner.tsx +++ b/src/ui/components/loading-spinner.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect } from "react"; import { Box, Text } from "ink"; +import { formatTokenCount } from "../../utils/token-counter"; interface LoadingSpinnerProps { isActive: boolean; @@ -61,7 +62,7 @@ export function LoadingSpinner({ isActive, processingTime, tokenCount }: Loading {spinnerFrames[spinnerFrame]} {loadingTexts[loadingTextIndex]}{" "} - ({processingTime}s · ↑ {tokenCount} tokens · esc to interrupt) + ({processingTime}s · ↑ {formatTokenCount(tokenCount)} tokens · esc to interrupt) ); diff --git a/src/utils/token-counter.ts b/src/utils/token-counter.ts index a049d5a6..9404a02d 100644 --- a/src/utils/token-counter.ts +++ b/src/utils/token-counter.ts @@ -66,6 +66,23 @@ export class TokenCounter { } } +/** + * Format token count for display (e.g., 1.2k for 1200) + */ +export function formatTokenCount(count: number): string { + if (count <= 999) { + return count.toString(); + } + + if (count < 1_000_000) { + const k = count / 1000; + return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`; + } + + const m = count / 1_000_000; + return m % 1 === 0 ? `${m}m` : `${m.toFixed(1)}m`; +} + /** * Create a token counter instance */ From 70ff43d63cf4ba93fc06e124cf5ee675b53fe9b4 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Thu, 24 Jul 2025 23:21:43 +0200 Subject: [PATCH 2/3] save selected model --- .grok/settings.json | 3 ++ package.json | 2 +- src/agent/grok-agent.ts | 8 +++- src/hooks/use-input-handler.ts | 3 ++ src/ui/components/chat-interface.tsx | 8 +++- src/utils/settings.ts | 60 ++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 .grok/settings.json create mode 100644 src/utils/settings.ts diff --git a/.grok/settings.json b/.grok/settings.json new file mode 100644 index 00000000..823c3171 --- /dev/null +++ b/.grok/settings.json @@ -0,0 +1,3 @@ +{ + "selectedModel": "grok-3-latest" +} \ No newline at end of file diff --git a/package.json b/package.json index f455f80d..ba7a1651 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vibe-kit/grok-cli", - "version": "0.0.6", + "version": "0.0.10", "description": "An open-source AI agent that brings the power of Grok directly into your terminal.", "main": "dist/index.js", "bin": { diff --git a/src/agent/grok-agent.ts b/src/agent/grok-agent.ts index 2f98a50a..c5688905 100644 --- a/src/agent/grok-agent.ts +++ b/src/agent/grok-agent.ts @@ -11,6 +11,7 @@ import { ToolResult } from "../types"; import { EventEmitter } from "events"; import { createTokenCounter, TokenCounter } from "../utils/token-counter"; import { loadCustomInstructions } from "../utils/custom-instructions"; +import { getSetting } from "../utils/settings"; export interface ChatEntry { type: "user" | "assistant" | "tool_result" | "tool_call"; @@ -45,13 +46,16 @@ export class GrokAgent extends EventEmitter { constructor(apiKey: string, baseURL?: string, model?: string) { super(); - this.grokClient = new GrokClient(apiKey, model, baseURL); + // Use saved model if no model is explicitly provided + const savedModel = getSetting('selectedModel'); + const modelToUse = model || savedModel || 'grok-4-latest'; + this.grokClient = new GrokClient(apiKey, modelToUse, baseURL); this.textEditor = new TextEditorTool(); this.bash = new BashTool(); this.todoTool = new TodoTool(); this.confirmationTool = new ConfirmationTool(); this.search = new SearchTool(); - this.tokenCounter = createTokenCounter("grok-4-latest"); + this.tokenCounter = createTokenCounter(modelToUse); // Load custom instructions const customInstructions = loadCustomInstructions(); diff --git a/src/hooks/use-input-handler.ts b/src/hooks/use-input-handler.ts index 8fc11a3a..347d8261 100644 --- a/src/hooks/use-input-handler.ts +++ b/src/hooks/use-input-handler.ts @@ -2,6 +2,7 @@ import { useState, useRef } from "react"; import { useInput, useApp } from "ink"; import { GrokAgent, ChatEntry } from "../agent/grok-agent"; import { ConfirmationService } from "../utils/confirmation-service"; +import { updateSetting } from "../utils/settings"; interface UseInputHandlerProps { agent: GrokAgent; @@ -139,6 +140,7 @@ Examples: if (modelNames.includes(modelArg)) { agent.setModel(modelArg); + updateSetting('selectedModel', modelArg); const confirmEntry: ChatEntry = { type: "assistant", content: `✓ Switched to model: ${modelArg}`, @@ -435,6 +437,7 @@ Available models: ${modelNames.join(", ")}`, if (key.tab || key.return) { const selectedModel = availableModels[selectedModelIndex]; agent.setModel(selectedModel.model); + updateSetting('selectedModel', selectedModel.model); const confirmEntry: ChatEntry = { type: "assistant", content: `✓ Switched to model: ${selectedModel.model}`, diff --git a/src/ui/components/chat-interface.tsx b/src/ui/components/chat-interface.tsx index 52ee3c56..5d7e7d45 100644 --- a/src/ui/components/chat-interface.tsx +++ b/src/ui/components/chat-interface.tsx @@ -204,11 +204,17 @@ function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) { isStreaming={isStreaming} /> - + {autoEditEnabled ? "▶" : "⏸"} auto-edit:{" "} {autoEditEnabled ? "on" : "off"} + + (shift + tab) + + + ⚡{agent.getCurrentModel()} + (key: K, value: Settings[K]): void { + const currentSettings = loadSettings(); + currentSettings[key] = value; + saveSettings(currentSettings); +} + +export function getSetting(key: K): Settings[K] { + const settings = loadSettings(); + return settings[key]; +} \ No newline at end of file From df3d78e2bb9d273b75c4dc1d43aab62348739056 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Thu, 24 Jul 2025 23:30:57 +0200 Subject: [PATCH 3/3] minor tweaks --- .gitignore | 3 ++- .grok/settings.json | 3 --- package.json | 2 +- src/index.ts | 28 ++++++++++++++++++++++++++++ src/utils/settings.ts | 7 ++++++- 5 files changed, 37 insertions(+), 6 deletions(-) delete mode 100644 .grok/settings.json diff --git a/.gitignore b/.gitignore index 49f5e1fc..ae35f3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -90,4 +90,5 @@ temp/ *.sw? # Coding agents -.claude/ \ No newline at end of file +.claude/ +.grok/ \ No newline at end of file diff --git a/.grok/settings.json b/.grok/settings.json deleted file mode 100644 index 823c3171..00000000 --- a/.grok/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "selectedModel": "grok-3-latest" -} \ No newline at end of file diff --git a/package.json b/package.json index ba7a1651..d7f8da9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vibe-kit/grok-cli", - "version": "0.0.10", + "version": "0.0.11", "description": "An open-source AI agent that brings the power of Grok directly into your terminal.", "main": "dist/index.js", "bin": { diff --git a/src/index.ts b/src/index.ts index 7d00b14f..0c232d36 100755 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,32 @@ import { ConfirmationService } from "./utils/confirmation-service"; // Load environment variables dotenv.config(); +// Ensure user .grok directory exists with default settings +function ensureUserSettingsDirectory(): void { + try { + const homeDir = os.homedir(); + const grokDir = path.join(homeDir, ".grok"); + const settingsFile = path.join(grokDir, "user-settings.json"); + + // Create .grok directory if it doesn't exist + if (!fs.existsSync(grokDir)) { + fs.mkdirSync(grokDir, { recursive: true }); + } + + // Create default user-settings.json if it doesn't exist + if (!fs.existsSync(settingsFile)) { + const defaultSettings = { + apiKey: "", + baseURL: "", + defaultModel: "grok-4-latest" + }; + fs.writeFileSync(settingsFile, JSON.stringify(defaultSettings, null, 2)); + } + } catch (error) { + // Silently ignore errors during setup + } +} + // Load API key from user settings if not in environment function loadApiKey(): string | undefined { // First check environment variables @@ -22,6 +48,7 @@ function loadApiKey(): string | undefined { if (!apiKey) { // Try to load from user settings file try { + ensureUserSettingsDirectory(); const homeDir = os.homedir(); const settingsFile = path.join(homeDir, ".grok", "user-settings.json"); @@ -45,6 +72,7 @@ function loadBaseURL(): string | undefined { if (!baseURL) { // Try to load from user settings file try { + ensureUserSettingsDirectory(); const homeDir = os.homedir(); const settingsFile = path.join(homeDir, ".grok", "user-settings.json"); diff --git a/src/utils/settings.ts b/src/utils/settings.ts index 605263fa..7cc07d49 100644 --- a/src/utils/settings.ts +++ b/src/utils/settings.ts @@ -20,13 +20,18 @@ function ensureSettingsDirectory(): void { } } +const DEFAULT_SETTINGS: Settings = { + selectedModel: 'grok-4-latest' +}; + export function loadSettings(): Settings { try { ensureSettingsDirectory(); const settingsPath = getSettingsPath(); if (!fs.existsSync(settingsPath)) { - return {}; + saveSettings(DEFAULT_SETTINGS); + return DEFAULT_SETTINGS; } const settingsContent = fs.readFileSync(settingsPath, 'utf-8');