Skip to content

Commit 39f3e3f

Browse files
committed
refactor(gitcommit): improve commit message generation and client handling
- refactor generator to support both HTTP and ACP adapters - enhance client creation and request handling for different adapter types - improve error handling and response processing for commit message generation
1 parent 715080b commit 39f3e3f

File tree

1 file changed

+161
-88
lines changed

1 file changed

+161
-88
lines changed
Lines changed: 161 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
local codecompanion_client = require("codecompanion.http")
2-
local codecompanion_config = require("codecompanion.config")
31
local codecompanion_adapter = require("codecompanion.adapters")
42
local codecompanion_schema = require("codecompanion.schema")
53

64
---@class CodeCompanion.GitCommit.Generator
75
local Generator = {}
86

9-
--- @type string?
10-
local _adapter = nil
11-
--- @type string?
12-
local _model = nil
7+
--- @type string? Adapter name
8+
local _adapter_name = nil
9+
--- @type string? Model name
10+
local _model_name = nil
1311

1412
local CONSTANTS = {
1513
STATUS_ERROR = "error",
@@ -19,65 +17,178 @@ local CONSTANTS = {
1917
--- @param adapter string? The adapter to use for generation
2018
--- @param model string? The model of the adapter to use for generation
2119
function Generator.setup(adapter, model)
22-
_adapter = adapter
23-
_model = model
20+
_adapter_name = adapter
21+
_model_name = model
2422
end
2523

26-
---@param commit_history? string[] Array of recent commit messages for context (optional)
27-
function Generator.generate_commit_message(diff, lang, commit_history, callback)
28-
-- Setup adapter with proper resolution
29-
local adapter
30-
local success, result = pcall(function()
31-
return codecompanion_adapter.resolve({
32-
name = _adapter,
33-
model = _model,
34-
})
35-
end)
24+
---Create a client for both HTTP and ACP adapters
25+
---@param adapter table The resolved adapter
26+
---@return table|nil client The client instance
27+
---@return string|nil error Error message if failed
28+
local function create_client(adapter)
29+
if not adapter or not adapter.type then
30+
return nil, "Invalid adapter: missing type field"
31+
end
3632

37-
if not success then
38-
-- Fallback to simple resolve
39-
adapter = codecompanion_adapter.resolve(_adapter)
33+
if adapter.type == "http" then
34+
local HTTPClient = require("codecompanion.http")
35+
return HTTPClient.new({ adapter = adapter }), nil
36+
elseif adapter.type == "acp" then
37+
local ACPClient = require("codecompanion.acp")
38+
local client = ACPClient.new({ adapter = adapter })
39+
local ok = client:connect_and_initialize()
40+
if not ok then
41+
return nil, "Failed to connect and initialize ACP client"
42+
end
43+
return client, nil
4044
else
41-
adapter = result
45+
return nil, "Unknown adapter type: " .. tostring(adapter.type)
4246
end
47+
end
4348

44-
if not adapter then
45-
return callback(nil, "Failed to resolve adapter: " .. tostring(_adapter))
46-
end
49+
---Send request using HTTP client
50+
---@param client table HTTP client
51+
---@param adapter table Adapter instance
52+
---@param payload table Request payload
53+
---@param callback function Callback function
54+
local function send_http_request(client, adapter, payload, callback)
55+
local accumulated = ""
56+
local has_error = false
4757

48-
-- Configure adapter for non-streaming
49-
adapter.opts = adapter.opts or {}
50-
adapter.opts.stream = false
58+
-- Use async send to properly handle streaming responses
59+
client:send(payload, {
60+
stream = true,
61+
on_chunk = function(chunk)
62+
if chunk and chunk ~= "" then
63+
-- Use adapter's chat_output handler to process the chunk
64+
local result = adapter.handlers.chat_output(adapter, chunk)
65+
if result and result.status == CONSTANTS.STATUS_SUCCESS then
66+
local content = result.output and result.output.content
67+
if content and content ~= "" then
68+
accumulated = accumulated .. content
69+
end
70+
end
71+
end
72+
end,
73+
on_done = function()
74+
if not has_error then
75+
if accumulated ~= "" then
76+
callback(vim.trim(accumulated), nil)
77+
else
78+
callback(nil, "Generated content is empty")
79+
end
80+
end
81+
end,
82+
on_error = function(err)
83+
has_error = true
84+
local error_msg = "HTTP request failed: " .. (err.message or vim.inspect(err))
85+
callback(nil, error_msg)
86+
end,
87+
})
88+
end
5189

52-
-- Map schema with model override if specified
53-
local schema_opts = {}
54-
if _model then
55-
schema_opts.model = _model
56-
end
57-
adapter = adapter:map_schema_to_params(codecompanion_schema.get_default(adapter, schema_opts))
90+
---Send request using ACP client
91+
---@param client table ACP client
92+
---@param adapter table Adapter instance
93+
---@param messages table Array of messages
94+
---@param callback function Callback function
95+
local function send_acp_request(client, adapter, messages, callback)
96+
local accumulated = ""
97+
local has_error = false
98+
99+
-- ACP expects messages to have _meta field
100+
-- Add it to make messages compatible with form_messages
101+
local formatted_messages = vim.tbl_map(function(msg)
102+
return vim.tbl_extend("force", msg, {
103+
_meta = {
104+
visible = true,
105+
},
106+
})
107+
end, messages)
58108

59-
-- Create HTTP client
60-
local new_client = codecompanion_client.new({
61-
adapter = adapter,
109+
client
110+
:session_prompt(formatted_messages)
111+
:on_message_chunk(function(chunk)
112+
if chunk and chunk ~= "" then
113+
accumulated = accumulated .. chunk
114+
end
115+
end)
116+
:on_complete(function(stop_reason)
117+
if not has_error and accumulated ~= "" then
118+
-- ACP responses are plain text, wrap in expected format
119+
callback(vim.trim(accumulated), nil)
120+
elseif not has_error then
121+
callback(nil, "ACP returned empty response")
122+
end
123+
end)
124+
:on_error(function(error)
125+
has_error = true
126+
callback(nil, "ACP error: " .. vim.inspect(error))
127+
end)
128+
:send()
129+
end
130+
131+
---@param commit_history? string[] Array of recent commit messages for context (optional)
132+
function Generator.generate_commit_message(diff, lang, commit_history, callback)
133+
-- 1. Resolve adapter
134+
local adapter = codecompanion_adapter.resolve(_adapter_name, {
135+
model = _model_name,
62136
})
137+
if not adapter then
138+
return callback(nil, "Failed to resolve adapter: " .. tostring(_adapter_name))
139+
end
140+
141+
-- Validate adapter type
142+
if not adapter.type or (adapter.type ~= "http" and adapter.type ~= "acp") then
143+
return callback(nil, "Invalid or unsupported adapter type: " .. tostring(adapter.type))
144+
end
63145

64-
-- Create prompt for LLM
146+
-- 2. Create prompt
65147
local prompt = Generator._create_prompt(diff, lang, commit_history)
66148

67-
local payload = {
68-
messages = adapter:map_roles({
69-
{ role = "user", content = prompt },
70-
}),
149+
-- 3. Prepare messages
150+
local messages = {
151+
{ role = "user", content = prompt },
71152
}
72153

73-
-- Send request to LLM
74-
new_client:request(payload, {
75-
callback = function(err, data, adapter)
76-
Generator._handle_response(err, data, adapter, callback)
77-
end,
78-
}, {
79-
silent = true,
80-
})
154+
-- 4. Handle HTTP and ACP adapters differently
155+
if adapter.type == "http" then
156+
-- Map schema for HTTP adapter
157+
local schema_opts = {}
158+
if _model_name then
159+
schema_opts.model = _model_name
160+
end
161+
adapter = adapter:map_schema_to_params(codecompanion_schema.get_default(adapter, schema_opts))
162+
163+
-- Create client AFTER mapping schema
164+
-- This is important because HTTPClient.request() does vim.deepcopy(self.adapter)
165+
local client, err = create_client(adapter)
166+
if not client then
167+
return callback(nil, err)
168+
end
169+
170+
-- Prepare HTTP payload
171+
local payload = {
172+
messages = adapter:map_roles(messages),
173+
}
174+
175+
send_http_request(client, adapter, payload, callback)
176+
elseif adapter.type == "acp" then
177+
-- Create ACP client
178+
local client, err = create_client(adapter)
179+
if not client then
180+
return callback(nil, err)
181+
end
182+
183+
-- ACP doesn't need schema mapping for simple prompts
184+
send_acp_request(client, adapter, messages, function(result, error)
185+
-- Disconnect after request completes
186+
pcall(function()
187+
client:disconnect()
188+
end)
189+
callback(result, error)
190+
end)
191+
end
81192
end
82193

83194
---Create prompt for commit message generation
@@ -144,42 +255,4 @@ Return ONLY the commit message in the exact format shown above.]],
144255
)
145256
end
146257

147-
---Handle LLM response
148-
---@param err table|nil Request error
149-
---@param data table|nil Response data
150-
---@param adapter table The adapter used
151-
---@param callback fun(result: string|nil, error: string|nil) Callback function
152-
function Generator._handle_response(err, data, adapter, callback)
153-
-- Handle request errors
154-
if err then
155-
local error_msg = "Error generating commit message: " .. (err.stderr or err.message or "Unknown error")
156-
return callback(nil, error_msg)
157-
end
158-
159-
-- Check for empty/invalid data
160-
if not data then
161-
return callback(nil, "No response received from LLM")
162-
end
163-
164-
-- Process response
165-
if data then
166-
local result = adapter.handlers.chat_output(adapter, data)
167-
if result and result.status then
168-
if result.status == CONSTANTS.STATUS_SUCCESS then
169-
local content = result.output and result.output.content
170-
if content and vim.trim(content) ~= "" then
171-
return callback(vim.trim(content), nil)
172-
else
173-
return callback(nil, "Generated content is empty")
174-
end
175-
elseif result.status == CONSTANTS.STATUS_ERROR then
176-
local error_msg = result.output or "Unknown error occurred"
177-
return callback(nil, error_msg)
178-
end
179-
end
180-
end
181-
182-
return callback(nil, "No valid response received")
183-
end
184-
185258
return Generator

0 commit comments

Comments
 (0)