-
-
Notifications
You must be signed in to change notification settings - Fork 49
Expand file tree
/
Copy pathquery_tool.lua
More file actions
257 lines (247 loc) · 9.46 KB
/
Copy pathquery_tool.lua
File metadata and controls
257 lines (247 loc) · 9.46 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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
---@module "codecompanion"
local cc_common = require("vectorcode.integrations.codecompanion.common")
local vc_config = require("vectorcode.config")
local check_cli_wrap = vc_config.check_cli_wrap
local logger = vc_config.logger
local job_runner = nil
---@alias QueryToolArgs { project_root:string, count: integer, query: string[] }
---@param opts VectorCode.CodeCompanion.QueryToolOpts?
---@return CodeCompanion.Agent.Tool
return check_cli_wrap(function(opts)
opts = cc_common.get_query_tool_opts(opts)
assert(
type(opts.max_num) == "number" and type(opts.default_num) == "number",
string.format("Options are not correctly formatted:%s", vim.inspect(opts))
)
---@type "file"|"chunk"
local mode
if opts.chunk_mode then
mode = "chunk"
else
mode = "file"
end
logger.info("Creating CodeCompanion tool with the following args:\n", opts)
local tool_name = "vectorcode_query"
return {
name = tool_name,
cmds = {
---@param agent CodeCompanion.Agent
---@param action QueryToolArgs
---@return nil|{ status: string, data: string }
function(agent, action, _, cb)
logger.info(
"CodeCompanion query tool called with the following arguments:\n",
action
)
job_runner = cc_common.initialise_runner(opts.use_lsp)
assert(job_runner ~= nil, "Jobrunner not initialised!")
assert(
type(cb) == "function",
"Please upgrade CodeCompanion.nvim to at least 13.5.0"
)
if action.query == nil then
return {
status = "error",
data = "Missing argument: option.query, please refine the tool argument.",
}
end
local args = { "query" }
vim.list_extend(args, action.query)
vim.list_extend(args, { "--pipe", "-n", tostring(action.count) })
if opts.chunk_mode then
vim.list_extend(args, { "--include", "path", "chunk" })
else
vim.list_extend(args, { "--include", "path", "document" })
end
if action.project_root == "" then
action.project_root = nil
end
if action.project_root ~= nil then
action.project_root = vim.fs.normalize(action.project_root)
if
vim.uv.fs_stat(action.project_root) ~= nil
and vim.uv.fs_stat(action.project_root).type == "directory"
then
action.project_root = vim.fs.abspath(vim.fs.normalize(action.project_root))
vim.list_extend(args, { "--project_root", action.project_root })
else
return {
status = "error",
data = "INVALID PROJECT ROOT! USE THE LS COMMAND!",
}
end
end
if opts.no_duplicate and agent.chat.refs ~= nil then
-- exclude files that has been added to the context
local existing_files = { "--exclude" }
for _, ref in pairs(agent.chat.refs) do
if ref.source == cc_common.tool_result_source then
table.insert(existing_files, ref.id)
elseif type(ref.path) == "string" then
table.insert(existing_files, ref.path)
elseif ref.bufnr then
local fname = vim.api.nvim_buf_get_name(ref.bufnr)
if fname ~= nil then
local stat = vim.uv.fs_stat(fname)
if stat and stat.type == "file" then
table.insert(existing_files, fname)
end
end
end
end
if #existing_files > 1 then
vim.list_extend(args, existing_files)
end
end
vim.list_extend(args, { "--absolute" })
logger.info(
"CodeCompanion query tool called the runner with the following args: ",
args
)
job_runner.run_async(args, function(result, error)
if vim.islist(result) and #result > 0 and result[1].path ~= nil then ---@cast result VectorCode.QueryResult[]
cb({ status = "success", data = result })
else
if type(error) == "table" then
error = cc_common.flatten_table_to_string(error)
end
cb({
status = "error",
data = error,
})
end
end, agent.chat.bufnr)
end,
},
schema = {
type = "function",
["function"] = {
name = tool_name,
description = [[Retrieves code documents using semantic search.
The path of a retrieved file will be wrapped in `<path>` and `</path>` tags.
Its content will be right after the `</path>` tag, wrapped by `<content>` and `</content>` tags.
Do not include the xml tags in your answers when you mention the paths.
The results may also be chunks of the source code.
In this case, the text chunks will be wrapped in <chunk></chunk>.
If the starting and ending line ranges are available, they will be wrapped in <start_line></start_line> and <end_line></end_line> tags.
Make use of the line numbers (NOT THE XML TAGS) when you're quoting the source code.
Include one single command call for VectorCode each time.
You may include multiple keywords in the command.
**The project root option MUST be a valid path on the filesystem. It can only be one of the results from the `vectorcode_ls` tool or from user input**
]],
parameters = {
type = "object",
properties = {
query = {
type = "array",
items = { type = "string" },
description = [[
Query messages used for the search. They should also contain relevant keywords.
For example, you should include `parameter`, `arguments` and `return value` for the query `function`.
]],
},
count = {
type = "integer",
description = string.format(
"Number of documents or chunks to retrieve, must be positive. Use %d by default. Do not query for more than %d.",
tonumber(opts.default_num),
tonumber(opts.max_num)
),
},
project_root = {
type = "string",
description = "Project path to search within (must be from 'ls' results or user instructions). Use empty string for the current project. If the user did not specify a project, assume that they're referring to the current project and use an empty string for this parameter. If this fails, use the `vectorcode_ls` tool and ask the user to clarify the project.",
},
},
required = { "query", "count", "project_root" },
additionalProperties = false,
},
strict = true,
},
},
output = {
---@param agent CodeCompanion.Agent
---@param cmd QueryToolArgs
---@param stderr table|string
error = function(self, agent, cmd, stderr)
logger.error(
("CodeCompanion tool with command %s thrown with the following error: %s"):format(
vim.inspect(cmd),
vim.inspect(stderr)
)
)
stderr = cc_common.flatten_table_to_string(stderr)
if string.find(stderr, "InvalidCollectionException") then
if cmd.project_root then
agent.chat:add_tool_output(
self,
string.format(
"`%s` hasn't been vectorised. Please use the `vectorcode_vectorise` tool or vectorise it from the CLI.",
cmd.project_root
)
)
else
agent.chat:add_tool_output(
self,
"Failed to query from the requested project. Please verify the available projects via the `vectorcode_ls` tool or run it from the CLI."
)
end
else
agent.chat:add_tool_output(
self,
string.format(
"**VectorCode `query` Tool**: Failed with error:\n```\n%s\n```",
stderr
)
)
end
end,
---@param agent CodeCompanion.Agent
---@param cmd QueryToolArgs
---@param stdout VectorCode.QueryResult[][]
success = function(self, agent, cmd, stdout)
stdout = stdout[1]
logger.info(
("CodeCompanion tool with command %s finished."):format(vim.inspect(cmd))
)
local user_message
local max_result = #stdout
if opts.max_num > 0 then
max_result = math.min(opts.max_num or 1, max_result)
end
for i, file in pairs(stdout) do
if i <= max_result then
if i == 1 then
user_message = string.format(
"**VectorCode Tool**: Retrieved %d %s(s)",
max_result,
mode
)
if cmd.project_root then
user_message = user_message .. " from " .. cmd.project_root
end
user_message = user_message .. "\n"
else
user_message = ""
end
agent.chat:add_tool_output(
self,
cc_common.process_result(file),
user_message
)
if not opts.chunk_mode then
-- skip referencing because there will be multiple chunks with the same path (id).
-- TODO: figure out a way to deduplicate.
agent.chat.references:add({
source = cc_common.tool_result_source,
id = file.path,
path = file.path,
opts = { visible = false },
})
end
end
end
end,
},
}
end)