@@ -43,6 +43,7 @@ local utils = require('CopilotChat.utils')
43
43
--- @field get_agents nil | fun ( headers : table ): table<CopilotChat.Provider.agent>
44
44
--- @field get_models nil | fun ( headers : table ): table<CopilotChat.Provider.model>
45
45
--- @field embed nil | string | fun ( inputs : table<string> , headers : table ): table<CopilotChat.Provider.embed>
46
+ --- @field search nil | string | fun ( query : string , repository : string , headers : table ): table<CopilotChat.Provider.output>
46
47
--- @field prepare_input nil | fun ( inputs : table<CopilotChat.Provider.input> , opts : CopilotChat.Provider.options ): table
47
48
--- @field prepare_output nil | fun ( output : table , opts : CopilotChat.Provider.options ): CopilotChat.Provider.output
48
49
--- @field get_url nil | fun ( opts : CopilotChat.Provider.options ): string
@@ -100,11 +101,41 @@ local function get_github_token()
100
101
error (' Failed to find GitHub token' )
101
102
end
102
103
104
+ local cached_gh_apps_token = nil
105
+
106
+ --- Get the github apps token (gho_ token)
107
+ --- @return string
108
+ local function get_gh_apps_token ()
109
+ if cached_gh_apps_token then
110
+ return cached_gh_apps_token
111
+ end
112
+
113
+ async .util .scheduler ()
114
+
115
+ local config_path = utils .config_path ()
116
+ if not config_path then
117
+ error (' Failed to find config path for GitHub token' )
118
+ end
119
+
120
+ local file_path = config_path .. ' /gh/hosts.yml'
121
+ if vim .fn .filereadable (file_path ) == 1 then
122
+ local content = table.concat (vim .fn .readfile (file_path ), ' \n ' )
123
+ local token = content :match (' oauth_token:%s*([%w_]+)' )
124
+ if token then
125
+ cached_gh_apps_token = token
126
+ return token
127
+ end
128
+ end
129
+
130
+ error (' Failed to find GitHub token' )
131
+ end
132
+
103
133
--- @type table<string , CopilotChat.Provider>
104
134
local M = {}
105
135
106
136
M .copilot = {
107
137
embed = ' copilot_embeddings' ,
138
+ search = ' copilot_search' ,
108
139
109
140
get_headers = function ()
110
141
local response , err = utils .curl_get (' https://api.github.com/copilot_internal/v2/token' , {
@@ -271,6 +302,7 @@ M.copilot = {
271
302
272
303
M .github_models = {
273
304
embed = ' copilot_embeddings' ,
305
+ search = ' copilot_search' ,
274
306
275
307
get_headers = function ()
276
308
return {
@@ -350,4 +382,80 @@ M.copilot_embeddings = {
350
382
end ,
351
383
}
352
384
385
+ M .copilot_search = {
386
+ get_headers = M .copilot .get_headers ,
387
+
388
+ get_token = function ()
389
+ return get_gh_apps_token (), nil
390
+ end ,
391
+
392
+ search = function (query , repository , headers )
393
+ utils .curl_post (
394
+ ' https://api.github.com/repos/' .. repository .. ' /copilot_internal/embeddings_index' ,
395
+ {
396
+ headers = headers ,
397
+ }
398
+ )
399
+
400
+ local response , err = utils .curl_get (
401
+ ' https://api.github.com/repos/' .. repository .. ' /copilot_internal/embeddings_index' ,
402
+ {
403
+ headers = headers ,
404
+ }
405
+ )
406
+
407
+ if err then
408
+ error (err )
409
+ end
410
+
411
+ if response .status ~= 200 then
412
+ error (' Failed to check search: ' .. tostring (response .status ))
413
+ end
414
+
415
+ local body = vim .json .decode (response .body )
416
+
417
+ if
418
+ body .can_index ~= ' ok'
419
+ or not body .bm25_search_ok
420
+ or not body .lexical_search_ok
421
+ or not body .semantic_code_search_ok
422
+ or not body .semantic_doc_search_ok
423
+ or not body .semantic_indexing_enabled
424
+ then
425
+ error (' Failed to search: ' .. vim .inspect (body ))
426
+ end
427
+
428
+ local body = vim .json .encode ({
429
+ query = query ,
430
+ scopingQuery = ' (repo:' .. repository .. ' )' ,
431
+ similarity = 0.766 ,
432
+ limit = 100 ,
433
+ })
434
+
435
+ local response , err = utils .curl_post (' https://api.individual.githubcopilot.com/search/code' , {
436
+ headers = headers ,
437
+ body = utils .temp_file (body ),
438
+ })
439
+
440
+ if err then
441
+ error (err )
442
+ end
443
+
444
+ if response .status ~= 200 then
445
+ error (' Failed to search: ' .. tostring (response .body ))
446
+ end
447
+
448
+ local out = {}
449
+ for _ , result in ipairs (vim .json .decode (response .body )) do
450
+ table.insert (out , {
451
+ filename = result .path ,
452
+ filetype = result .languageName :lower (),
453
+ score = result .score ,
454
+ content = result .contents ,
455
+ })
456
+ end
457
+ return out
458
+ end ,
459
+ }
460
+
353
461
return M
0 commit comments