@@ -43,6 +43,7 @@ local utils = require('CopilotChat.utils')
4343--- @field get_agents nil | fun ( headers : table ): table<CopilotChat.Provider.agent>
4444--- @field get_models nil | fun ( headers : table ): table<CopilotChat.Provider.model>
4545--- @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>
4647--- @field prepare_input nil | fun ( inputs : table<CopilotChat.Provider.input> , opts : CopilotChat.Provider.options ): table
4748--- @field prepare_output nil | fun ( output : table , opts : CopilotChat.Provider.options ): CopilotChat.Provider.output
4849--- @field get_url nil | fun ( opts : CopilotChat.Provider.options ): string
@@ -100,11 +101,41 @@ local function get_github_token()
100101 error (' Failed to find GitHub token' )
101102end
102103
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+
103133--- @type table<string , CopilotChat.Provider>
104134local M = {}
105135
106136M .copilot = {
107137 embed = ' copilot_embeddings' ,
138+ search = ' copilot_search' ,
108139
109140 get_headers = function ()
110141 local response , err = utils .curl_get (' https://api.github.com/copilot_internal/v2/token' , {
@@ -271,6 +302,7 @@ M.copilot = {
271302
272303M .github_models = {
273304 embed = ' copilot_embeddings' ,
305+ search = ' copilot_search' ,
274306
275307 get_headers = function ()
276308 return {
@@ -350,4 +382,80 @@ M.copilot_embeddings = {
350382 end ,
351383}
352384
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+
353461return M
0 commit comments