@@ -44,6 +44,7 @@ local utils = require('CopilotChat.utils')
4444--- @field get_agents nil | fun ( headers : table ): table<CopilotChat.Provider.agent>
4545--- @field get_models nil | fun ( headers : table ): table<CopilotChat.Provider.model>
4646--- @field embed nil | string | fun ( inputs : table<string> , headers : table ): table<CopilotChat.Provider.embed>
47+ --- @field search nil | string | fun ( query : string , repository : string , headers : table ): table<CopilotChat.Provider.output>
4748--- @field prepare_input nil | fun ( inputs : table<CopilotChat.Provider.input> , opts : CopilotChat.Provider.options ): table
4849--- @field prepare_output nil | fun ( output : table , opts : CopilotChat.Provider.options ): CopilotChat.Provider.output
4950--- @field get_url nil | fun ( opts : CopilotChat.Provider.options ): string
@@ -101,11 +102,41 @@ local function get_github_token()
101102 error (' Failed to find GitHub token' )
102103end
103104
105+ local cached_gh_apps_token = nil
106+
107+ --- Get the github apps token (gho_ token)
108+ --- @return string
109+ local function get_gh_apps_token ()
110+ if cached_gh_apps_token then
111+ return cached_gh_apps_token
112+ end
113+
114+ async .util .scheduler ()
115+
116+ local config_path = utils .config_path ()
117+ if not config_path then
118+ error (' Failed to find config path for GitHub token' )
119+ end
120+
121+ local file_path = config_path .. ' /gh/hosts.yml'
122+ if vim .fn .filereadable (file_path ) == 1 then
123+ local content = table.concat (vim .fn .readfile (file_path ), ' \n ' )
124+ local token = content :match (' oauth_token:%s*([%w_]+)' )
125+ if token then
126+ cached_gh_apps_token = token
127+ return token
128+ end
129+ end
130+
131+ error (' Failed to find GitHub token' )
132+ end
133+
104134--- @type table<string , CopilotChat.Provider>
105135local M = {}
106136
107137M .copilot = {
108138 embed = ' copilot_embeddings' ,
139+ search = ' copilot_search' ,
109140
110141 get_headers = function (token )
111142 return {
@@ -279,6 +310,7 @@ M.copilot = {
279310
280311M .github_models = {
281312 embed = ' copilot_embeddings' ,
313+ search = ' copilot_search' ,
282314
283315 get_headers = function (token )
284316 return {
@@ -360,4 +392,80 @@ M.copilot_embeddings = {
360392 end ,
361393}
362394
395+ M .copilot_search = {
396+ get_headers = M .copilot .get_headers ,
397+
398+ get_token = function ()
399+ return get_gh_apps_token (), nil
400+ end ,
401+
402+ search = function (query , repository , headers )
403+ utils .curl_post (
404+ ' https://api.github.com/repos/' .. repository .. ' /copilot_internal/embeddings_index' ,
405+ {
406+ headers = headers ,
407+ }
408+ )
409+
410+ local response , err = utils .curl_get (
411+ ' https://api.github.com/repos/' .. repository .. ' /copilot_internal/embeddings_index' ,
412+ {
413+ headers = headers ,
414+ }
415+ )
416+
417+ if err then
418+ error (err )
419+ end
420+
421+ if response .status ~= 200 then
422+ error (' Failed to check search: ' .. tostring (response .status ))
423+ end
424+
425+ local body = vim .json .decode (response .body )
426+
427+ if
428+ body .can_index ~= ' ok'
429+ or not body .bm25_search_ok
430+ or not body .lexical_search_ok
431+ or not body .semantic_code_search_ok
432+ or not body .semantic_doc_search_ok
433+ or not body .semantic_indexing_enabled
434+ then
435+ error (' Failed to search: ' .. vim .inspect (body ))
436+ end
437+
438+ local body = vim .json .encode ({
439+ query = query ,
440+ scopingQuery = ' (repo:' .. repository .. ' )' ,
441+ similarity = 0.766 ,
442+ limit = 100 ,
443+ })
444+
445+ local response , err = utils .curl_post (' https://api.individual.githubcopilot.com/search/code' , {
446+ headers = headers ,
447+ body = utils .temp_file (body ),
448+ })
449+
450+ if err then
451+ error (err )
452+ end
453+
454+ if response .status ~= 200 then
455+ error (' Failed to search: ' .. tostring (response .body ))
456+ end
457+
458+ local out = {}
459+ for _ , result in ipairs (vim .json .decode (response .body )) do
460+ table.insert (out , {
461+ filename = result .path ,
462+ filetype = result .languageName :lower (),
463+ score = result .score ,
464+ content = result .contents ,
465+ })
466+ end
467+ return out
468+ end ,
469+ }
470+
363471return M
0 commit comments