This repository was archived by the owner on Jan 14, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgenerator.lua
More file actions
236 lines (212 loc) · 7.45 KB
/
generator.lua
File metadata and controls
236 lines (212 loc) · 7.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
local codecompanion_adapter = require("codecompanion.adapters")
local codecompanion_schema = require("codecompanion.schema")
local git_utils = require("codecompanion._extensions.gitcommit.git_utils")
---@class CodeCompanion.GitCommit.Generator
local Generator = {}
--- @type string? Adapter name
local _adapter_name = nil
--- @type string? Model name
local _model_name = nil
--- @type string? Prompt template
local _prompt_template = nil
local CONSTANTS = {
STATUS_ERROR = "error",
STATUS_SUCCESS = "success",
}
--- @param adapter string? The adapter to use for generation
--- @param model string? The model of the adapter to use for generation
--- @param prompt_template string? Custom prompt template
function Generator.setup(adapter, model, prompt_template)
_adapter_name = adapter
_model_name = model
_prompt_template = prompt_template
end
---Create a client for both HTTP and ACP adapters
---@param adapter table The resolved adapter
---@return table|nil client The client instance
---@return string|nil error Error message if failed
local function create_client(adapter)
if not adapter or not adapter.type then
return nil, "Invalid adapter: missing type field"
end
if adapter.type == "http" then
local HTTPClient = require("codecompanion.http")
return HTTPClient.new({ adapter = adapter }), nil
elseif adapter.type == "acp" then
local ACPClient = require("codecompanion.acp")
local client = ACPClient.new({ adapter = adapter })
local ok = client:connect_and_initialize()
if not ok then
return nil, "Failed to connect and initialize ACP client"
end
return client, nil
else
return nil, "Unknown adapter type: " .. tostring(adapter.type)
end
end
---Send request using HTTP client
---@param client table HTTP client
---@param adapter table Adapter instance
---@param payload table Request payload
---@param callback function Callback function
local function send_http_request(client, adapter, payload, callback)
local accumulated = ""
local has_error = false
-- Prepare options for spinner events
local request_opts = {
adapter = {
name = adapter.name or "unknown",
formatted_name = adapter.formatted_name or adapter.name or "GitCommit",
model = (adapter.schema and adapter.schema.model and adapter.schema.model.default) or "",
},
strategy = "gitcommit",
silent = false,
}
-- Use async send to properly handle streaming responses
client:send(
payload,
vim.tbl_extend("force", request_opts, {
stream = true,
on_chunk = function(chunk)
if chunk and chunk ~= "" then
-- Use adapter's parse_chat handler to process the chunk (with backwards compatibility)
local result = codecompanion_adapter.call_handler(adapter, "parse_chat", chunk)
if result and result.status == CONSTANTS.STATUS_SUCCESS then
local content = result.output and result.output.content
if content and content ~= "" then
accumulated = accumulated .. content
end
end
end
end,
on_done = function()
if not has_error then
if accumulated ~= "" then
local cleaned = Generator._clean_commit_message(accumulated)
callback(cleaned, nil)
else
callback(nil, "Generated content is empty")
end
end
end,
on_error = function(err)
has_error = true
local error_msg = "HTTP request failed: " .. (err.message or vim.inspect(err))
callback(nil, error_msg)
end,
})
)
end
---Send request using ACP client
---@param client table ACP client
---@param adapter table Adapter instance
---@param messages table Array of messages
---@param callback function Callback function
local function send_acp_request(client, adapter, messages, callback)
local accumulated = ""
local has_error = false
-- Prepare options for spinner events
local request_opts = {
adapter = {
name = adapter.name or "unknown",
formatted_name = adapter.formatted_name or adapter.name or "GitCommit",
type = "acp",
model = nil,
},
strategy = "gitcommit",
silent = false,
}
-- ACP expects messages to have _meta field
-- Add it to make messages compatible with form_messages
local formatted_messages = vim.tbl_map(function(msg)
return vim.tbl_extend("force", msg, {
_meta = {
visible = true,
},
})
end, messages)
client
:session_prompt(formatted_messages)
:with_options(request_opts)
:on_message_chunk(function(chunk)
if chunk and chunk ~= "" then
accumulated = accumulated .. chunk
end
end)
:on_complete(function(stop_reason)
if not has_error and accumulated ~= "" then
-- ACP responses are plain text, wrap in expected format
local cleaned = Generator._clean_commit_message(accumulated)
callback(cleaned, nil)
elseif not has_error then
callback(nil, "ACP returned empty response")
end
end)
:on_error(function(error)
has_error = true
callback(nil, "ACP error: " .. vim.inspect(error))
end)
:send()
end
---Clean commit message by removing markdown code blocks and extra formatting
---@param message string Raw message from LLM
---@return string cleaned_message The cleaned commit message
function Generator._clean_commit_message(message)
return git_utils.clean_commit_message(message)
end
---@param commit_history? string[] Array of recent commit messages for context (optional)
function Generator.generate_commit_message(diff, lang, commit_history, callback)
-- 1. Resolve adapter
local adapter = codecompanion_adapter.resolve(_adapter_name, {
model = _model_name,
})
if not adapter then
return callback(nil, "Failed to resolve adapter: " .. tostring(_adapter_name))
end
-- Validate adapter type
if not adapter.type or (adapter.type ~= "http" and adapter.type ~= "acp") then
return callback(nil, "Invalid or unsupported adapter type: " .. tostring(adapter.type))
end
-- 2. Create prompt
local prompt = Generator._create_prompt(diff, lang, commit_history)
-- 3. Prepare messages
local messages = {
{ role = "user", content = prompt },
}
-- 4. Map schema for HTTP adapter (must be done before client creation)
if adapter.type == "http" then
local schema_opts = {}
if _model_name then
schema_opts.model = _model_name
end
adapter = adapter:map_schema_to_params(codecompanion_schema.get_default(adapter, schema_opts))
end
-- 5. Create client (after potential schema mapping for HTTP)
local client, err = create_client(adapter)
if not client then
return callback(nil, err)
end
-- 6. Send request based on adapter type
if adapter.type == "http" then
-- Prepare HTTP payload
local payload = {
messages = adapter:map_roles(messages),
}
send_http_request(client, adapter, payload, callback)
elseif adapter.type == "acp" then
send_acp_request(client, adapter, messages, function(result, error)
-- Disconnect after request completes
pcall(function()
client:disconnect()
end)
callback(result, error)
end)
end
end
---Create prompt for commit message generation
---@param diff string The git diff to include in prompt
---@param commit_history? string[] Recent commit messages for context (optional)
function Generator._create_prompt(diff, lang, commit_history)
return git_utils.build_commit_prompt(diff, lang, commit_history, _prompt_template)
end
return Generator