From 9687b352d5d6148b728c663db45ef1a4152c7b91 Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Tue, 24 Mar 2026 21:53:14 -0400 Subject: [PATCH] migrating the logs file --- lua/codetyper/adapters/nvim/ui/logs.lua | 400 ++---------------- lua/codetyper/adapters/nvim/ui/logs/add.lua | 15 + .../adapters/nvim/ui/logs/add_listener.lua | 11 + lua/codetyper/adapters/nvim/ui/logs/clear.lua | 16 + lua/codetyper/adapters/nvim/ui/logs/debug.lua | 10 + lua/codetyper/adapters/nvim/ui/logs/error.lua | 10 + .../adapters/nvim/ui/logs/explore.lua | 9 + .../adapters/nvim/ui/logs/explore_done.lua | 14 + .../adapters/nvim/ui/logs/format_entry.lua | 30 ++ .../adapters/nvim/ui/logs/format_for_chat.lua | 45 ++ .../adapters/nvim/ui/logs/get_entries.lua | 9 + .../nvim/ui/logs/get_provider_info.lua | 10 + .../nvim/ui/logs/get_token_totals.lua | 10 + lua/codetyper/adapters/nvim/ui/logs/info.lua | 10 + lua/codetyper/adapters/nvim/ui/logs/log.lua | 23 + lua/codetyper/adapters/nvim/ui/logs/read.lua | 14 + .../adapters/nvim/ui/logs/reason.lua | 9 + .../adapters/nvim/ui/logs/remove_listener.lua | 11 + .../adapters/nvim/ui/logs/request.lua | 24 ++ .../adapters/nvim/ui/logs/response.lua | 33 ++ lua/codetyper/adapters/nvim/ui/logs/task.lua | 14 + .../adapters/nvim/ui/logs/task_done.lua | 13 + .../adapters/nvim/ui/logs/thinking.lua | 9 + lua/codetyper/adapters/nvim/ui/logs/tool.lua | 23 + .../adapters/nvim/ui/logs/update.lua | 24 ++ .../adapters/nvim/ui/logs/warning.lua | 10 + lua/codetyper/state/state.lua | 5 + lua/codetyper/utils/estimate_tokens.lua | 8 + lua/codetyper/utils/get_timestamp.lua | 7 + 29 files changed, 452 insertions(+), 374 deletions(-) create mode 100644 lua/codetyper/adapters/nvim/ui/logs/add.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/add_listener.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/clear.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/debug.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/error.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/explore.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/explore_done.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/format_entry.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/format_for_chat.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/get_entries.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/get_provider_info.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/get_token_totals.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/info.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/log.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/read.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/reason.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/remove_listener.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/request.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/response.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/task.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/task_done.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/thinking.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/tool.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/update.lua create mode 100644 lua/codetyper/adapters/nvim/ui/logs/warning.lua create mode 100644 lua/codetyper/utils/estimate_tokens.lua create mode 100644 lua/codetyper/utils/get_timestamp.lua diff --git a/lua/codetyper/adapters/nvim/ui/logs.lua b/lua/codetyper/adapters/nvim/ui/logs.lua index 8e3bb78..d6edb5f 100644 --- a/lua/codetyper/adapters/nvim/ui/logs.lua +++ b/lua/codetyper/adapters/nvim/ui/logs.lua @@ -4,379 +4,31 @@ local M = {} -local params = require("codetyper.params.agents.logs") - ----@class LogEntry ----@field timestamp string ISO timestamp ----@field level string "info" | "debug" | "request" | "response" | "tool" | "error" ----@field message string Log message ----@field data? table Optional structured data - ----@class LogState ----@field entries LogEntry[] All log entries ----@field listeners table[] Functions to call when new entries are added ----@field total_prompt_tokens number Running total of prompt tokens ----@field total_response_tokens number Running total of response tokens - -local state = { - entries = {}, - listeners = {}, - total_prompt_tokens = 0, - total_response_tokens = 0, - current_provider = nil, - current_model = nil, -} - ---- Get current timestamp ----@return string -local function get_timestamp() - return os.date("%H:%M:%S") -end - ---- Add a log entry ----@param level string Log level ----@param message string Log message ----@param data? table Optional data -function M.log(level, message, data) - local entry = { - timestamp = get_timestamp(), - level = level, - message = message, - data = data, - } - - table.insert(state.entries, entry) - - -- Notify all listeners - for _, listener in ipairs(state.listeners) do - pcall(listener, entry) - end -end - ---- Log info message ----@param message string ----@param data? table -function M.info(message, data) - M.log("info", message, data) -end - ---- Log debug message ----@param message string ----@param data? table -function M.debug(message, data) - M.log("debug", message, data) -end - ---- Log API request ----@param provider string LLM provider ----@param model string Model name ----@param prompt_tokens? number Estimated prompt tokens -function M.request(provider, model, prompt_tokens) - state.current_provider = provider - state.current_model = model - - local msg = string.format("[%s] %s", provider:upper(), model) - if prompt_tokens then - msg = msg .. string.format(" | Prompt: ~%d tokens", prompt_tokens) - end - - M.log("request", msg, { - provider = provider, - model = model, - prompt_tokens = prompt_tokens, - }) -end - ---- Log API response with token usage ----@param prompt_tokens number Tokens used in prompt ----@param response_tokens number Tokens in response ----@param stop_reason? string Why the response stopped -function M.response(prompt_tokens, response_tokens, stop_reason) - state.total_prompt_tokens = state.total_prompt_tokens + prompt_tokens - state.total_response_tokens = state.total_response_tokens + response_tokens - - local msg = string.format( - "Tokens: %d in / %d out | Total: %d in / %d out", - prompt_tokens, - response_tokens, - state.total_prompt_tokens, - state.total_response_tokens - ) - - if stop_reason then - msg = msg .. " | Stop: " .. stop_reason - end - - M.log("response", msg, { - prompt_tokens = prompt_tokens, - response_tokens = response_tokens, - total_prompt = state.total_prompt_tokens, - total_response = state.total_response_tokens, - stop_reason = stop_reason, - }) -end - ---- Log tool execution ----@param tool_name string Name of the tool ----@param status string "start" | "success" | "error" | "approval" ----@param details? string Additional details -function M.tool(tool_name, status, details) - local icons = params.icons - - local msg = string.format("[%s] %s", icons[status] or status, tool_name) - if details then - msg = msg .. ": " .. details - end - - M.log("tool", msg, { - tool = tool_name, - status = status, - details = details, - }) -end - ---- Log error ----@param message string ----@param data? table -function M.error(message, data) - M.log("error", "ERROR: " .. message, data) -end - ---- Log warning ----@param message string ----@param data? table -function M.warning(message, data) - M.log("warning", "WARN: " .. message, data) -end - ---- Add log entry (compatibility function for scheduler) ---- Accepts {type = "info", message = "..."} format ----@param entry table Log entry with type and message -function M.add(entry) - if entry.type == "clear" then - M.clear() - return - end - M.log(entry.type or "info", entry.message or "", entry.data) -end - ---- Log thinking/reasoning step (Claude Code style) ----@param step string Description of what's happening -function M.thinking(step) - M.log("thinking", step) -end - ---- Log a reasoning/explanation message (shown prominently) ----@param message string The reasoning message -function M.reason(message) - M.log("reason", message) -end - ---- Log file read operation ----@param filepath string Path of file being read ----@param lines? number Number of lines read -function M.read(filepath, lines) - local msg = string.format("Read(%s)", vim.fn.fnamemodify(filepath, ":~:.")) - if lines then - msg = msg .. string.format("\n ⎿ Read %d lines", lines) - end - M.log("action", msg) -end - ---- Log explore/search operation ----@param description string What we're exploring -function M.explore(description) - M.log("action", string.format("Explore(%s)", description)) -end - ---- Log explore done ----@param tool_uses number Number of tool uses ----@param tokens number Tokens used ----@param duration number Duration in seconds -function M.explore_done(tool_uses, tokens, duration) - M.log( - "result", - string.format(" ⎿ Done (%d tool uses · %.1fk tokens · %.1fs)", tool_uses, tokens / 1000, duration) - ) -end - ---- Log update/edit operation ----@param filepath string Path of file being edited ----@param added? number Lines added ----@param removed? number Lines removed -function M.update(filepath, added, removed) - local msg = string.format("Update(%s)", vim.fn.fnamemodify(filepath, ":~:.")) - if added or removed then - local parts = {} - if added and added > 0 then - table.insert(parts, string.format("Added %d lines", added)) - end - if removed and removed > 0 then - table.insert(parts, string.format("Removed %d lines", removed)) - end - if #parts > 0 then - msg = msg .. "\n ⎿ " .. table.concat(parts, ", ") - end - end - M.log("action", msg) -end - ---- Log a task/step that's in progress ----@param task string Task name ----@param status string Status message (optional) -function M.task(task, status) - local msg = task - if status then - msg = msg .. " " .. status - end - M.log("task", msg) -end - ---- Log task completion ----@param next_task? string Next task (optional) -function M.task_done(next_task) - local msg = " ⎿ Done" - if next_task then - msg = msg .. "\n✶ " .. next_task - end - M.log("result", msg) -end - ---- Register a listener for new log entries ----@param callback fun(entry: LogEntry) ----@return number Listener ID for removal -function M.add_listener(callback) - table.insert(state.listeners, callback) - return #state.listeners -end - ---- Remove a listener ----@param id number Listener ID -function M.remove_listener(id) - if id > 0 and id <= #state.listeners then - table.remove(state.listeners, id) - end -end - ---- Get all log entries ----@return LogEntry[] -function M.get_entries() - return state.entries -end - ---- Get token totals ----@return number, number prompt_tokens, response_tokens -function M.get_token_totals() - return state.total_prompt_tokens, state.total_response_tokens -end - ---- Get current provider info ----@return string?, string? provider, model -function M.get_provider_info() - return state.current_provider, state.current_model -end - ---- Clear all logs and reset counters -function M.clear() - state.entries = {} - state.total_prompt_tokens = 0 - state.total_response_tokens = 0 - state.current_provider = nil - state.current_model = nil - - -- Notify listeners of clear - for _, listener in ipairs(state.listeners) do - pcall(listener, { level = "clear" }) - end -end - ---- Format entry for display ----@param entry LogEntry ----@return string -function M.format_entry(entry) - -- Claude Code style formatting for thinking/action entries - local thinking_types = params.thinking_types - local is_thinking = vim.tbl_contains(thinking_types, entry.level) - - if is_thinking then - local prefix = params.thinking_prefixes[entry.level] or "⏺" - - if prefix ~= "" then - return prefix .. " " .. entry.message - else - return entry.message - end - end - - -- Traditional log format for other types - local level_prefix = params.level_icons[entry.level] or "?" - - local base = string.format("[%s] %s %s", entry.timestamp, level_prefix, entry.message) - - -- If this is a response entry with raw_response, append the full response - if entry.data and entry.data.raw_response then - local response = entry.data.raw_response - -- Add separator and the full response - base = base .. "\n" .. string.rep("-", 40) .. "\n" .. response .. "\n" .. string.rep("-", 40) - end - - return base -end - ---- Format entry for display in chat (compact Claude Code style) ----@param entry LogEntry ----@return string|nil Formatted string or nil to skip -function M.format_for_chat(entry) - -- Skip certain log types in chat view - local skip_types = { "debug", "queue", "patch" } - if vim.tbl_contains(skip_types, entry.level) then - return nil - end - - -- Claude Code style formatting - local thinking_types = params.thinking_types - if vim.tbl_contains(thinking_types, entry.level) then - local prefix = params.thinking_prefixes[entry.level] or "⏺" - - if prefix ~= "" then - return prefix .. " " .. entry.message - else - return entry.message - end - end - - -- Tool logs - if entry.level == "tool" then - return "⏺ " .. entry.message:gsub("^%[.-%] ", "") - end - - -- Info/success - if entry.level == "info" or entry.level == "success" then - return "⏺ " .. entry.message - end - - -- Errors - if entry.level == "error" then - return "⚠ " .. entry.message - end - - -- Request/response (compact) - if entry.level == "request" then - return "⏺ " .. entry.message - end - if entry.level == "response" then - return " ⎿ " .. entry.message - end - - return nil -end - ---- Estimate token count for a string (rough approximation) ----@param text string ----@return number -function M.estimate_tokens(text) - -- Rough estimate: ~4 characters per token for English text - return math.ceil(#text / 4) -end +M.log = require("codetyper.adapters.nvim.ui.logs.log") +M.clear = require("codetyper.adapters.nvim.ui.logs.clear") +M.info = require("codetyper.adapters.nvim.ui.logs.info") +M.debug = require("codetyper.adapters.nvim.ui.logs.debug") +M.error = require("codetyper.adapters.nvim.ui.logs.error") +M.warning = require("codetyper.adapters.nvim.ui.logs.warning") +M.thinking = require("codetyper.adapters.nvim.ui.logs.thinking") +M.reason = require("codetyper.adapters.nvim.ui.logs.reason") +M.request = require("codetyper.adapters.nvim.ui.logs.request") +M.response = require("codetyper.adapters.nvim.ui.logs.response") +M.tool = require("codetyper.adapters.nvim.ui.logs.tool") +M.add = require("codetyper.adapters.nvim.ui.logs.add") +M.read = require("codetyper.adapters.nvim.ui.logs.read") +M.explore = require("codetyper.adapters.nvim.ui.logs.explore") +M.explore_done = require("codetyper.adapters.nvim.ui.logs.explore_done") +M.update = require("codetyper.adapters.nvim.ui.logs.update") +M.task = require("codetyper.adapters.nvim.ui.logs.task") +M.task_done = require("codetyper.adapters.nvim.ui.logs.task_done") +M.add_listener = require("codetyper.adapters.nvim.ui.logs.add_listener") +M.remove_listener = require("codetyper.adapters.nvim.ui.logs.remove_listener") +M.get_entries = require("codetyper.adapters.nvim.ui.logs.get_entries") +M.get_token_totals = require("codetyper.adapters.nvim.ui.logs.get_token_totals") +M.get_provider_info = require("codetyper.adapters.nvim.ui.logs.get_provider_info") +M.format_entry = require("codetyper.adapters.nvim.ui.logs.format_entry") +M.format_for_chat = require("codetyper.adapters.nvim.ui.logs.format_for_chat") +M.estimate_tokens = require("codetyper.utils.estimate_tokens") return M diff --git a/lua/codetyper/adapters/nvim/ui/logs/add.lua b/lua/codetyper/adapters/nvim/ui/logs/add.lua new file mode 100644 index 0000000..e2325ec --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/add.lua @@ -0,0 +1,15 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") +local clear = require("codetyper.adapters.nvim.ui.logs.clear") + +--- Add log entry (compatibility function for scheduler) +--- Accepts {type = "info", message = "..."} format +---@param entry table Log entry with type and message +local function add(entry) + if entry.type == "clear" then + clear() + return + end + log(entry.type or "info", entry.message or "", entry.data) +end + +return add diff --git a/lua/codetyper/adapters/nvim/ui/logs/add_listener.lua b/lua/codetyper/adapters/nvim/ui/logs/add_listener.lua new file mode 100644 index 0000000..2652597 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/add_listener.lua @@ -0,0 +1,11 @@ +local state = require("codetyper.state.state") + +--- Register a listener for new log entries +---@param callback fun(entry: LogEntry) +---@return number listener_id Listener ID for removal +local function add_listener(callback) + table.insert(state.listeners, callback) + return #state.listeners +end + +return add_listener diff --git a/lua/codetyper/adapters/nvim/ui/logs/clear.lua b/lua/codetyper/adapters/nvim/ui/logs/clear.lua new file mode 100644 index 0000000..65cebd8 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/clear.lua @@ -0,0 +1,16 @@ +local state = require("codetyper.state.state") + +--- Clear all logs and reset counters +local function clear() + state.entries = {} + state.total_prompt_tokens = 0 + state.total_response_tokens = 0 + state.current_provider = nil + state.current_model = nil + + for _, listener in ipairs(state.listeners) do + pcall(listener, { level = "clear" }) + end +end + +return clear diff --git a/lua/codetyper/adapters/nvim/ui/logs/debug.lua b/lua/codetyper/adapters/nvim/ui/logs/debug.lua new file mode 100644 index 0000000..c943ae3 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/debug.lua @@ -0,0 +1,10 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log debug message +---@param message string +---@param data? table +local function debug(message, data) + log("debug", message, data) +end + +return debug diff --git a/lua/codetyper/adapters/nvim/ui/logs/error.lua b/lua/codetyper/adapters/nvim/ui/logs/error.lua new file mode 100644 index 0000000..fb6ed6a --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/error.lua @@ -0,0 +1,10 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log error message +---@param message string +---@param data? table +local function log_error(message, data) + log("error", "ERROR: " .. message, data) +end + +return log_error diff --git a/lua/codetyper/adapters/nvim/ui/logs/explore.lua b/lua/codetyper/adapters/nvim/ui/logs/explore.lua new file mode 100644 index 0000000..c00e734 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/explore.lua @@ -0,0 +1,9 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log explore/search operation +---@param description string What we're exploring +local function explore(description) + log("action", string.format("Explore(%s)", description)) +end + +return explore diff --git a/lua/codetyper/adapters/nvim/ui/logs/explore_done.lua b/lua/codetyper/adapters/nvim/ui/logs/explore_done.lua new file mode 100644 index 0000000..34608dd --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/explore_done.lua @@ -0,0 +1,14 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log explore done with stats +---@param tool_uses number Number of tool uses +---@param tokens number Tokens used +---@param duration number Duration in seconds +local function explore_done(tool_uses, tokens, duration) + log( + "result", + string.format(" ⎿ Done (%d tool uses · %.1fk tokens · %.1fs)", tool_uses, tokens / 1000, duration) + ) +end + +return explore_done diff --git a/lua/codetyper/adapters/nvim/ui/logs/format_entry.lua b/lua/codetyper/adapters/nvim/ui/logs/format_entry.lua new file mode 100644 index 0000000..d2d32c0 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/format_entry.lua @@ -0,0 +1,30 @@ +local params = require("codetyper.params.agents.logs") + +--- Format a log entry for display +---@param entry LogEntry +---@return string +local function format_entry(entry) + local thinking_types = params.thinking_types + local is_thinking = vim.tbl_contains(thinking_types, entry.level) + + if is_thinking then + local prefix = params.thinking_prefixes[entry.level] or "⏺" + if prefix ~= "" then + return prefix .. " " .. entry.message + else + return entry.message + end + end + + local level_prefix = params.level_icons[entry.level] or "?" + local base = string.format("[%s] %s %s", entry.timestamp, level_prefix, entry.message) + + if entry.data and entry.data.raw_response then + local separator = string.rep("-", 40) + base = base .. "\n" .. separator .. "\n" .. entry.data.raw_response .. "\n" .. separator + end + + return base +end + +return format_entry diff --git a/lua/codetyper/adapters/nvim/ui/logs/format_for_chat.lua b/lua/codetyper/adapters/nvim/ui/logs/format_for_chat.lua new file mode 100644 index 0000000..616aaa9 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/format_for_chat.lua @@ -0,0 +1,45 @@ +local params = require("codetyper.params.agents.logs") + +--- Format entry for display in chat (compact Claude Code style) +---@param entry LogEntry +---@return string|nil formatted Formatted string or nil to skip +local function format_for_chat(entry) + local skip_types = { "debug", "queue", "patch" } + if vim.tbl_contains(skip_types, entry.level) then + return nil + end + + local thinking_types = params.thinking_types + if vim.tbl_contains(thinking_types, entry.level) then + local prefix = params.thinking_prefixes[entry.level] or "⏺" + if prefix ~= "" then + return prefix .. " " .. entry.message + else + return entry.message + end + end + + if entry.level == "tool" then + return "⏺ " .. entry.message:gsub("^%[.-%] ", "") + end + + if entry.level == "info" or entry.level == "success" then + return "⏺ " .. entry.message + end + + if entry.level == "error" then + return "⚠ " .. entry.message + end + + if entry.level == "request" then + return "⏺ " .. entry.message + end + + if entry.level == "response" then + return " ⎿ " .. entry.message + end + + return nil +end + +return format_for_chat diff --git a/lua/codetyper/adapters/nvim/ui/logs/get_entries.lua b/lua/codetyper/adapters/nvim/ui/logs/get_entries.lua new file mode 100644 index 0000000..32d4f9c --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/get_entries.lua @@ -0,0 +1,9 @@ +local state = require("codetyper.state.state") + +--- Get all log entries +---@return LogEntry[] +local function get_entries() + return state.entries +end + +return get_entries diff --git a/lua/codetyper/adapters/nvim/ui/logs/get_provider_info.lua b/lua/codetyper/adapters/nvim/ui/logs/get_provider_info.lua new file mode 100644 index 0000000..6208cc3 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/get_provider_info.lua @@ -0,0 +1,10 @@ +local state = require("codetyper.state.state") + +--- Get current provider info +---@return string|nil provider +---@return string|nil model +local function get_provider_info() + return state.current_provider, state.current_model +end + +return get_provider_info diff --git a/lua/codetyper/adapters/nvim/ui/logs/get_token_totals.lua b/lua/codetyper/adapters/nvim/ui/logs/get_token_totals.lua new file mode 100644 index 0000000..6a8ffdb --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/get_token_totals.lua @@ -0,0 +1,10 @@ +local state = require("codetyper.state.state") + +--- Get token totals +---@return number prompt_tokens +---@return number response_tokens +local function get_token_totals() + return state.total_prompt_tokens, state.total_response_tokens +end + +return get_token_totals diff --git a/lua/codetyper/adapters/nvim/ui/logs/info.lua b/lua/codetyper/adapters/nvim/ui/logs/info.lua new file mode 100644 index 0000000..45a09fb --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/info.lua @@ -0,0 +1,10 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log info message +---@param message string +---@param data? table +local function info(message, data) + log("info", message, data) +end + +return info diff --git a/lua/codetyper/adapters/nvim/ui/logs/log.lua b/lua/codetyper/adapters/nvim/ui/logs/log.lua new file mode 100644 index 0000000..8511cb3 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/log.lua @@ -0,0 +1,23 @@ +local state = require("codetyper.state.state") +local get_timestamp = require("codetyper.utils.get_timestamp") + +--- Add a log entry and notify all listeners +---@param level string Log level +---@param message string Log message +---@param data? table Optional data +local function log(level, message, data) + local entry = { + timestamp = get_timestamp(), + level = level, + message = message, + data = data, + } + + table.insert(state.entries, entry) + + for _, listener in ipairs(state.listeners) do + pcall(listener, entry) + end +end + +return log diff --git a/lua/codetyper/adapters/nvim/ui/logs/read.lua b/lua/codetyper/adapters/nvim/ui/logs/read.lua new file mode 100644 index 0000000..79b7762 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/read.lua @@ -0,0 +1,14 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log file read operation +---@param filepath string Path of file being read +---@param lines? number Number of lines read +local function read(filepath, lines) + local message = string.format("Read(%s)", vim.fn.fnamemodify(filepath, ":~:.")) + if lines then + message = message .. string.format("\n ⎿ Read %d lines", lines) + end + log("action", message) +end + +return read diff --git a/lua/codetyper/adapters/nvim/ui/logs/reason.lua b/lua/codetyper/adapters/nvim/ui/logs/reason.lua new file mode 100644 index 0000000..b09cac6 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/reason.lua @@ -0,0 +1,9 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log a reasoning/explanation message (shown prominently) +---@param message string The reasoning message +local function reason(message) + log("reason", message) +end + +return reason diff --git a/lua/codetyper/adapters/nvim/ui/logs/remove_listener.lua b/lua/codetyper/adapters/nvim/ui/logs/remove_listener.lua new file mode 100644 index 0000000..dcf4fe5 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/remove_listener.lua @@ -0,0 +1,11 @@ +local state = require("codetyper.state.state") + +--- Remove a listener by ID +---@param listener_id number Listener ID +local function remove_listener(listener_id) + if listener_id > 0 and listener_id <= #state.listeners then + table.remove(state.listeners, listener_id) + end +end + +return remove_listener diff --git a/lua/codetyper/adapters/nvim/ui/logs/request.lua b/lua/codetyper/adapters/nvim/ui/logs/request.lua new file mode 100644 index 0000000..1c9a249 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/request.lua @@ -0,0 +1,24 @@ +local state = require("codetyper.state.state") +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log API request +---@param provider string LLM provider +---@param model string Model name +---@param prompt_tokens? number Estimated prompt tokens +local function request(provider, model, prompt_tokens) + state.current_provider = provider + state.current_model = model + + local message = string.format("[%s] %s", provider:upper(), model) + if prompt_tokens then + message = message .. string.format(" | Prompt: ~%d tokens", prompt_tokens) + end + + log("request", message, { + provider = provider, + model = model, + prompt_tokens = prompt_tokens, + }) +end + +return request diff --git a/lua/codetyper/adapters/nvim/ui/logs/response.lua b/lua/codetyper/adapters/nvim/ui/logs/response.lua new file mode 100644 index 0000000..6df4d3f --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/response.lua @@ -0,0 +1,33 @@ +local state = require("codetyper.state.state") +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log API response with token usage +---@param prompt_tokens number Tokens used in prompt +---@param response_tokens number Tokens in response +---@param stop_reason? string Why the response stopped +local function response(prompt_tokens, response_tokens, stop_reason) + state.total_prompt_tokens = state.total_prompt_tokens + prompt_tokens + state.total_response_tokens = state.total_response_tokens + response_tokens + + local message = string.format( + "Tokens: %d in / %d out | Total: %d in / %d out", + prompt_tokens, + response_tokens, + state.total_prompt_tokens, + state.total_response_tokens + ) + + if stop_reason then + message = message .. " | Stop: " .. stop_reason + end + + log("response", message, { + prompt_tokens = prompt_tokens, + response_tokens = response_tokens, + total_prompt = state.total_prompt_tokens, + total_response = state.total_response_tokens, + stop_reason = stop_reason, + }) +end + +return response diff --git a/lua/codetyper/adapters/nvim/ui/logs/task.lua b/lua/codetyper/adapters/nvim/ui/logs/task.lua new file mode 100644 index 0000000..9254c62 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/task.lua @@ -0,0 +1,14 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log a task/step that's in progress +---@param task_name string Task name +---@param status string|nil Status message +local function task(task_name, status) + local message = task_name + if status then + message = message .. " " .. status + end + log("task", message) +end + +return task diff --git a/lua/codetyper/adapters/nvim/ui/logs/task_done.lua b/lua/codetyper/adapters/nvim/ui/logs/task_done.lua new file mode 100644 index 0000000..629ced8 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/task_done.lua @@ -0,0 +1,13 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log task completion +---@param next_task? string Next task +local function task_done(next_task) + local message = " ⎿ Done" + if next_task then + message = message .. "\n✶ " .. next_task + end + log("result", message) +end + +return task_done diff --git a/lua/codetyper/adapters/nvim/ui/logs/thinking.lua b/lua/codetyper/adapters/nvim/ui/logs/thinking.lua new file mode 100644 index 0000000..569f60b --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/thinking.lua @@ -0,0 +1,9 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log thinking/reasoning step +---@param step string Description of what's happening +local function thinking(step) + log("thinking", step) +end + +return thinking diff --git a/lua/codetyper/adapters/nvim/ui/logs/tool.lua b/lua/codetyper/adapters/nvim/ui/logs/tool.lua new file mode 100644 index 0000000..7901c95 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/tool.lua @@ -0,0 +1,23 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") +local params = require("codetyper.params.agents.logs") + +--- Log tool execution +---@param tool_name string Name of the tool +---@param status string "start" | "success" | "error" | "approval" +---@param details? string Additional details +local function tool(tool_name, status, details) + local icons = params.icons + + local message = string.format("[%s] %s", icons[status] or status, tool_name) + if details then + message = message .. ": " .. details + end + + log("tool", message, { + tool = tool_name, + status = status, + details = details, + }) +end + +return tool diff --git a/lua/codetyper/adapters/nvim/ui/logs/update.lua b/lua/codetyper/adapters/nvim/ui/logs/update.lua new file mode 100644 index 0000000..4ef00f4 --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/update.lua @@ -0,0 +1,24 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log update/edit operation +---@param filepath string Path of file being edited +---@param added? number Lines added +---@param removed? number Lines removed +local function update(filepath, added, removed) + local message = string.format("Update(%s)", vim.fn.fnamemodify(filepath, ":~:.")) + if added or removed then + local parts = {} + if added and added > 0 then + table.insert(parts, string.format("Added %d lines", added)) + end + if removed and removed > 0 then + table.insert(parts, string.format("Removed %d lines", removed)) + end + if #parts > 0 then + message = message .. "\n ⎿ " .. table.concat(parts, ", ") + end + end + log("action", message) +end + +return update diff --git a/lua/codetyper/adapters/nvim/ui/logs/warning.lua b/lua/codetyper/adapters/nvim/ui/logs/warning.lua new file mode 100644 index 0000000..54add7c --- /dev/null +++ b/lua/codetyper/adapters/nvim/ui/logs/warning.lua @@ -0,0 +1,10 @@ +local log = require("codetyper.adapters.nvim.ui.logs.log") + +--- Log warning message +---@param message string +---@param data? table +local function warning(message, data) + log("warning", "WARN: " .. message, data) +end + +return warning diff --git a/lua/codetyper/state/state.lua b/lua/codetyper/state/state.lua index 8964dd8..34753df 100644 --- a/lua/codetyper/state/state.lua +++ b/lua/codetyper/state/state.lua @@ -12,6 +12,11 @@ local state = { diff_buf = nil, diff_win = nil, is_open = false, + listeners = {}, + total_prompt_tokens = 0, + total_response_tokens = 0, + current_provider = nil, + current_model = nil, } return state diff --git a/lua/codetyper/utils/estimate_tokens.lua b/lua/codetyper/utils/estimate_tokens.lua new file mode 100644 index 0000000..b36848d --- /dev/null +++ b/lua/codetyper/utils/estimate_tokens.lua @@ -0,0 +1,8 @@ +--- Estimate token count for a string (rough approximation ~4 chars per token) +---@param text string +---@return number +local function estimate_tokens(text) + return math.ceil(#text / 4) +end + +return estimate_tokens diff --git a/lua/codetyper/utils/get_timestamp.lua b/lua/codetyper/utils/get_timestamp.lua new file mode 100644 index 0000000..2b2ca13 --- /dev/null +++ b/lua/codetyper/utils/get_timestamp.lua @@ -0,0 +1,7 @@ +--- Get current timestamp formatted as HH:MM:SS +---@return string +local function get_timestamp() + return os.date("%H:%M:%S") +end + +return get_timestamp