Skip to content

Commit d8f95d2

Browse files
committed
Add method to get GitHubCopilot tokens
1 parent 2710000 commit d8f95d2

3 files changed

Lines changed: 102 additions & 29 deletions

File tree

Tool/Package.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ let package = Package(
453453
"Keychain",
454454
"BuiltinExtension",
455455
"ChatBasic",
456+
"GitHubCopilotService",
456457
.product(name: "JSONRPC", package: "JSONRPC"),
457458
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
458459
.product(name: "GoogleGenerativeAI", package: "generative-ai-swift"),

Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift

Lines changed: 98 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,6 @@ public final class GitHubCopilotExtension: BuiltinExtension {
2020
extensionUsage.isSuggestionServiceInUse || extensionUsage.isChatServiceInUse
2121
}
2222

23-
public struct AuthToken: Codable {
24-
public let user: String
25-
public let oauth_token: String
26-
public let githubAppId: String
27-
}
28-
29-
public static var authToken: AuthToken? {
30-
guard let urls = try? GitHubCopilotBaseService.createFoldersIfNeeded()
31-
else { return nil }
32-
let path = urls.supportURL
33-
.appendingPathComponent("undefined")
34-
.appendingPathComponent(".config")
35-
.appendingPathComponent("github-copilot")
36-
.appendingPathComponent("apps.json").path
37-
guard FileManager.default.fileExists(atPath: path) else { return nil }
38-
39-
do {
40-
let data = try Data(contentsOf: URL(fileURLWithPath: path))
41-
let json = try JSONSerialization
42-
.jsonObject(with: data, options: []) as? [String: [String: String]]
43-
guard let firstEntry = json?.values.first else { return nil }
44-
let jsonData = try JSONSerialization.data(withJSONObject: firstEntry, options: [])
45-
return try JSONDecoder().decode(AuthToken.self, from: jsonData)
46-
} catch {
47-
Logger.gitHubCopilot.error(error.localizedDescription)
48-
return nil
49-
}
50-
}
51-
5223
let workspacePool: WorkspacePool
5324

5425
let serviceLocator: ServiceLocatorType
@@ -180,3 +151,101 @@ class ServiceLocator: ServiceLocatorType {
180151
}
181152
}
182153

154+
extension GitHubCopilotExtension {
155+
public struct Token: Codable {
156+
// let codesearch: Bool
157+
public let individual: Bool
158+
public let endpoints: Endpoints
159+
public let chat_enabled: Bool
160+
public let sku: String
161+
// public let copilotignore_enabled: Bool
162+
// public let limited_user_quotas: String?
163+
public let tracking_id: String
164+
// public let xcode: Bool
165+
// public let limited_user_reset_date: String?
166+
// public let telemetry: String
167+
// public let prompt_8k: Bool
168+
public let token: String
169+
// public let nes_enabled: Bool
170+
// public let vsc_electron_fetcher_v2: Bool
171+
// public let code_review_enabled: Bool
172+
// public let annotations_enabled: Bool
173+
// public let chat_jetbrains_enabled: Bool
174+
// public let xcode_chat: Bool
175+
// public let refresh_in: Int
176+
// public let snippy_load_test_enabled: Bool
177+
// public let trigger_completion_after_accept: Bool
178+
public let expires_at: Int
179+
// public let public_suggestions: String
180+
// public let code_quote_enabled: Bool
181+
182+
public struct Endpoints: Codable {
183+
public let api: String
184+
public let proxy: String
185+
public let telemetry: String
186+
public let origin_tracker: String
187+
}
188+
}
189+
190+
struct AuthInfo: Codable {
191+
public let user: String
192+
public let oauth_token: String
193+
public let githubAppId: String
194+
}
195+
196+
static var authInfo: AuthInfo? {
197+
guard let urls = try? GitHubCopilotBaseService.createFoldersIfNeeded()
198+
else { return nil }
199+
let path = urls.supportURL
200+
.appendingPathComponent("undefined")
201+
.appendingPathComponent(".config")
202+
.appendingPathComponent("github-copilot")
203+
.appendingPathComponent("apps.json").path
204+
guard FileManager.default.fileExists(atPath: path) else { return nil }
205+
206+
do {
207+
let data = try Data(contentsOf: URL(fileURLWithPath: path))
208+
let json = try JSONSerialization
209+
.jsonObject(with: data, options: []) as? [String: [String: String]]
210+
guard let firstEntry = json?.values.first else { return nil }
211+
let jsonData = try JSONSerialization.data(withJSONObject: firstEntry, options: [])
212+
return try JSONDecoder().decode(AuthInfo.self, from: jsonData)
213+
} catch {
214+
Logger.gitHubCopilot.error(error.localizedDescription)
215+
return nil
216+
}
217+
}
218+
219+
@MainActor
220+
static var cachedToken: Token?
221+
222+
public static func fetchToken() async throws -> Token {
223+
guard let authToken = authInfo?.oauth_token
224+
else { throw GitHubCopilotError.notLoggedIn }
225+
226+
let oldToken = await MainActor.run { cachedToken }
227+
if let oldToken {
228+
let expiresAt = Date(timeIntervalSince1970: TimeInterval(oldToken.expires_at))
229+
if expiresAt > Date() {
230+
return oldToken
231+
}
232+
}
233+
234+
let url = URL(string: "https://api.github.com/copilot_internal/v2/token")!
235+
var request = URLRequest(url: url)
236+
request.httpMethod = "GET"
237+
request.setValue("token \(authToken)", forHTTPHeaderField: "authorization")
238+
request.setValue("unknown-editor/0", forHTTPHeaderField: "editor-version")
239+
request.setValue("unknown-editor-plugin/0", forHTTPHeaderField: "editor-plugin-version")
240+
request.setValue("1.236.0", forHTTPHeaderField: "copilot-language-server-version")
241+
request.setValue("GithubCopilot/1.236.0", forHTTPHeaderField: "user-agent")
242+
request.setValue("*/*", forHTTPHeaderField: "accept")
243+
request.setValue("gzip,deflate,br", forHTTPHeaderField: "accept-encoding")
244+
245+
let (data, _) = try await URLSession.shared.data(for: request)
246+
let newToken = try JSONDecoder().decode(Token.self, from: data)
247+
await MainActor.run { cachedToken = newToken }
248+
return newToken
249+
}
250+
}
251+

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,16 @@ extension GitHubCopilotLSP {
5353
}
5454

5555
enum GitHubCopilotError: Error, LocalizedError {
56+
case notLoggedIn
5657
case languageServerNotInstalled
5758
case languageServerError(ServerError)
5859
case failedToInstallStartScript
5960
case chatEndsWithError(String)
6061

6162
var errorDescription: String? {
6263
switch self {
64+
case .notLoggedIn:
65+
return "Not logged in."
6366
case .languageServerNotInstalled:
6467
return "Language server is not installed."
6568
case .failedToInstallStartScript:

0 commit comments

Comments
 (0)