summaryrefslogtreecommitdiff
path: root/lua/99/request/init.lua
blob: d020caa481eecfae5e7487bdcdf27d6f21c9e0a8 (plain)
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
local Logger = require("99.logger.logger")
local editor = require("99.editor")
local geo = require("99.geo")
local Point = geo.Point

--- TODO: some people change their current working directory as they open new
--- directories.  if this is still the case in neovim land, then we will need
--- to make the _99_state have the project directory.
--- @return string
local function random_file()
	return string.format("%s/tmp/99-%d", vim.uv.cwd(), math.floor(math.random() * 10000))
end

--- no, i am not going to use a uuid, in case of collision, call the police
--- @return string
local function get_id()
	return tostring(math.floor(math.random() * 100000000))
end

--- @alias _99.Request.State "ready" | "calling-model" | "parsing-result" | "updating-file"

--- @class _99.Request.Opts
--- @field model string
--- @field md_files string[]

--- @class _99.Request
--- @field query string
--- @field tmp_name string
--- @field state _99.Request.State
--- @field buffer number
--- @field model string
--- @field id string
--- @field mark string
--- @field scopes Scope
--- @field system_prompt string
local Request = {}
Request.__index = Request

--- @param opts _99.Request.Opts
function Request.new(opts)
    assert(opts.model, "you must provide a model for hange requests to work")

	local ts = editor.treesitter
	local cursor = Point:from_cursor()
	local scopes = ts.function_scopes(cursor)
	local buffer = vim.api.nvim_get_current_buf()

    return setmetatable({
        cursor = cursor,
        scopes = scopes,
        buffer = buffer,
        model = opts.model,
        tmp_name = random_file(),
        id = get_id(),
        state = "ready",
        mark = "",
    }, Request)
end

function Request:has_scopes()
    return self.scopes ~= nil and #self.scopes > 0
end

function Request:get_inner_scope()
    assert(self:has_scopes(), "you cannot get inner scope if you dont have scopes")
    return self.scopes.range[#self.scopes.range]
end

--- @param prompt string
function Request:set_system_prompt(prompt)
    self.system_prompt = prompt
end

function Request:_retrieve_response()
	local success, result = pcall(function()
		return vim.fn.readfile(self.tmp_name)
	end)

	if not success then
		Logger:error("retrieve_results: failed to read file", "tmp_name", self.tmp_name, "error", result)
		return false, ""
	end

	return true, table.concat(result, "\n")
end

--- @param res string
function Request:_update_file_with_changes(res)
	local mark_pos = vim.api.nvim_buf_get_mark(self.buffer, self.mark)
	local mark_point = Point:new(mark_pos[1], mark_pos[2] + 1)

	local ts = editor.treesitter
	local scopes = ts.function_scopes(mark_point)

	if not scopes or not scopes:has_scope() then
		Logger:error("update_file_with_changes: unable to find function at mark location")
		return
	end

	local range = scopes.range[#scopes.range]

	local function_start_row, _ = range.start:to_vim()
	local function_end_row, _ = range.end_:to_vim()

	local lines = vim.split(res, "\n")
	vim.api.nvim_buf_set_lines(self.buffer, function_start_row, function_end_row + 1, false, lines)
end

function Request:start()
	Logger:debug("99#make_query", "id", self.id, "query", self.query)
	vim.system({ "opencode", "run", "-m", "anthropic/claude-sonnet-4-5", self.query }, {
		text = true,
		stdout = vim.schedule_wrap(function(err, data)
			Logger:debug("STDOUT#data", "id", self.id, "data", data)
			Logger:debug("STDOUT#error", "id", self.id, "err", err)
		end),
		stderr = vim.schedule_wrap(function(err, data)
			Logger:debug("STDERR#data", "id", self.id, "data", data)
			Logger:debug("STDERR#error", "id", self.id, "err", err)
		end),
	}, function(obj)
		if obj.code ~= 0 then
			Logger:fatal("opencode make_query failed", "request", self, "obj from results", obj)
			return
		end
		vim.schedule(function()
			local ok, res = self:_retrieve_response()
			if ok then
				self:_update_file_with_changes(res)
			end
		end)
	end)
end

return Request