Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Additional useful commands are implemented through hooks (see below).
| `PrtProvider <provider>` | Switch the provider (empty arg triggers fzf) |
| `PrtModel <model>` | Switch the model (empty arg triggers fzf) |
| `PrtStatus` | Prints current provider and model selection |
| `PrtThinking` | Toggle or configure thinking mode for supported providers |
| __Interactive__ | |
| `PrtRewrite` | Rewrites the visual selection based on a provided prompt |
| `PrtEdit` | Like `PrtRewrite` but you can change the last prompt |
Expand Down Expand Up @@ -549,6 +550,18 @@ sources = cmp.config.sources({
...
```

## Thinking Mode

The `PrtThinking` command allows you to toggle or configure the "thinking" feature for providers that support it:

- `PrtThinking` - Toggle thinking on/off (default budget: 1024 tokens)
- `PrtThinking 2048` - Enable thinking with a specific budget of 2048 tokens
- `PrtThinking status` - Show current thinking settings

The thinking feature allows models to show their intermediate reasoning steps in a popup window
before providing a final response. Currently, this is fully implemented for the Anthropic provider,
but the framework supports adding this capability to other providers that can stream intermediate thinking steps.

## Statusline Support

Knowing the current chat or command model can be shown using your favorite statusline plugin.
Expand Down Expand Up @@ -640,4 +653,4 @@ ls -l | command nvim - -c "normal ggVGy" -c ":PrtChatNew" -c "normal p"

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=frankroeder/parrot.nvim&type=Date)](https://star-history.com/#frankroeder/parrot.nvim&Date)
[![Star History Chart](https://api.star-history.com/svg?repos=frankroeder/parrot.nvim&type=Date)](https://star-history.com/#frankroeder/parrot.nvim&Date)
39 changes: 39 additions & 0 deletions lua/parrot/chat_handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ function ChatHandler:set_provider(selected_prov, is_chat)
local _prov = init_provider(selected_prov, endpoint, api_key, style, models)
self.current_provider[is_chat and "chat" or "command"] = _prov
self.state:set_provider(_prov.name, is_chat)

-- Apply saved thinking config from state (if any)
local mode = is_chat and "chat" or "command"
local thinking_config = self.state:get_thinking(selected_prov, mode)
if thinking_config then
if not self.providers[selected_prov].params[mode].thinking then
self.providers[selected_prov].params[mode].thinking = {}
end
self.providers[selected_prov].params[mode].thinking = thinking_config
end

self.state:refresh(self.available_providers, self.available_models)
self:prepare_commands()
end
Expand All @@ -83,6 +94,16 @@ function ChatHandler:get_provider(is_chat)
end
self:set_provider(prov, is_chat)
current_prov = self.current_provider[is_chat and "chat" or "command"]

-- Apply saved thinking config from state (if any)
local mode = is_chat and "chat" or "command"
local thinking_config = self.state:get_thinking(prov, mode)
if thinking_config then
if not self.providers[prov].params[mode].thinking then
self.providers[prov].params[mode].thinking = {}
end
self.providers[prov].params[mode].thinking = thinking_config
end
end
return current_prov
end
Expand Down Expand Up @@ -1571,6 +1592,24 @@ end
---@param payload table Payload for the API.
---@param handler function Response handler function.
---@param on_exit function | nil Optional on_exit handler.
--- Toggle or configure thinking functionality for providers that support it
---@param params table Parameters for thinking configuration
function ChatHandler:thinking(params)
local buf = vim.api.nvim_get_current_buf()
local file_name = vim.api.nvim_buf_get_name(buf)
local is_chat = utils.is_chat(buf, file_name, self.options.chat_dir)
local provider = self:get_provider(is_chat)

-- Delegate to provider-specific thinking configuration if available
if provider.configure_thinking then
provider:configure_thinking(params, is_chat, self.providers, self.state)
return
end

-- If the provider doesn't have a configure_thinking method, warn and return
logger.warning("Thinking is not supported by the " .. provider.name .. " provider")
end

function ChatHandler:query(buf, provider, payload, handler, on_exit)
-- make sure handler is a function
if type(handler) ~= "function" then
Expand Down
1 change: 1 addition & 0 deletions lua/parrot/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ function M.setup(opts)
Provider = "provider",
Retry = "retry",
Edit = "edit",
Thinking = "thinking",
}

M.chat_handler = ChatHandler:new(M.options, M.providers, M.available_providers, M.available_models, M.cmd)
Expand Down
92 changes: 92 additions & 0 deletions lua/parrot/provider/anthropic.lua
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,96 @@ function Anthropic:get_available_models(online)
return ids
end

--- Toggle or configure thinking functionality for Claude models
---@param params table Parameters for thinking configuration
---@param is_chat boolean Whether this is for chat or command context
---@param providers table Provider configuration table
---@param state table|nil Optional state object for persistence
---@return nil
function Anthropic:configure_thinking(params, is_chat, providers, state)
local logger = require("parrot.logger")
local args = params.args or ""
local mode = is_chat and "chat" or "command"

-- Check current thinking settings with "status" command
if args == "status" then
local current = providers[self.name].params[mode].thinking
if current then
logger.info(string.format("Thinking is enabled with budget of %d tokens for %s",
current.budget_tokens or 0, mode))
else
logger.info("Thinking is disabled for " .. mode)
end
return
elseif args ~= "" then
-- Parse budget_tokens from args
local budget_tokens = tonumber(args)
if budget_tokens and budget_tokens > 0 then
-- Set thinking parameters in the provider config
if not providers[self.name].params[mode].thinking then
providers[self.name].params[mode].thinking = {}
end

local thinking_config = {
type = "enabled",
budget_tokens = budget_tokens
}

providers[self.name].params[mode].thinking = thinking_config

-- Save to state if provided
if state then
state:set_thinking(self.name, mode, thinking_config)
end

logger.info(string.format("Set thinking budget to %d tokens for %s",
budget_tokens, mode))
else
logger.warning("Invalid thinking budget. Please provide a positive number.")
end
else
-- Toggle thinking on/off
local current = providers[self.name].params[mode].thinking

if current then
-- Thinking is enabled, disable it
-- Store the current config before disabling for future restoration
if state then
-- Save the current state temporarily (it's already in state storage)
providers[self.name].params[mode]._stored_thinking = vim.deepcopy(current)
state:set_thinking(self.name, mode, nil)
end

providers[self.name].params[mode].thinking = nil
logger.info("Disabled thinking for " .. mode)
else
-- Thinking is disabled, enable it with previous budget if available
local stored_config = providers[self.name].params[mode]._stored_thinking
local thinking_config

if stored_config then
-- Restore previous configuration
thinking_config = vim.deepcopy(stored_config)
providers[self.name].params[mode]._stored_thinking = nil
else
-- Use default if no previous configuration exists
thinking_config = {
type = "enabled",
budget_tokens = 1024
}
end

providers[self.name].params[mode].thinking = thinking_config

-- Save to state if provided
if state then
state:set_thinking(self.name, mode, thinking_config)
end

logger.info(string.format("Enabled thinking with budget of %d tokens for %s",
thinking_config.budget_tokens, mode))
end
end
end

return Anthropic
52 changes: 50 additions & 2 deletions lua/parrot/state.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ end
function State:init_file_state(available_providers)
if next(self.file_state) == nil then
for _, prov in ipairs(available_providers) do
self.file_state[prov] = { chat_model = nil, command_model = nil }
self.file_state[prov] = {
chat_model = nil,
command_model = nil,
thinking = nil
}
end
end
self.file_state.current_provider = self.file_state.current_provider or { chat = nil, command = nil }
Expand All @@ -30,9 +34,14 @@ end
function State:init_state(available_providers, available_models)
self._state.current_provider = self._state.current_provider or { chat = nil, command = nil }
for _, provider in ipairs(available_providers) do
self._state[provider] = self._state[provider] or { chat_model = nil, command_model = nil }
self._state[provider] = self._state[provider] or {
chat_model = nil,
command_model = nil,
thinking = nil
}
self:load_models(provider, "chat_model", available_models)
self:load_models(provider, "command_model", available_models)
self:load_thinking(provider)
end
end

Expand All @@ -53,6 +62,14 @@ function State:load_models(provider, model_type, available_models)
end
end

--- Loads thinking configuration for the specified provider.
--- @param provider string # Name of the provider.
function State:load_thinking(provider)
if self.file_state and self.file_state[provider] and self.file_state[provider].thinking then
self._state[provider].thinking = vim.deepcopy(self.file_state[provider].thinking)
end
end

--- Refreshes the state with available providers and their models.
--- @param available_providers table
--- @param available_models table
Expand Down Expand Up @@ -135,4 +152,35 @@ function State:get_last_chat()
return self._state.last_chat
end

--- Sets thinking configuration for a specific provider and mode.
--- @param provider string # Provider name.
--- @param mode string # Mode type ('chat' or 'command').
--- @param thinking_config table|nil # Thinking configuration or nil to disable
function State:set_thinking(provider, mode, thinking_config)
if not self._state[provider] then
return
end

if not self._state[provider].thinking then
self._state[provider].thinking = {}
end

self._state[provider].thinking[mode] = thinking_config
self:save()
end

--- Gets thinking configuration for a specific provider and mode.
--- @param provider string # Provider name.
--- @param mode string # Mode type ('chat' or 'command').
--- @return table|nil # Thinking configuration or nil if not set
function State:get_thinking(provider, mode)
if not self._state[provider] or
not self._state[provider].thinking or
not self._state[provider].thinking[mode] then
return nil
end

return self._state[provider].thinking[mode]
end

return State