Skip to content

Commit 85ec675

Browse files
committed
Support Codeium sign in
1 parent 4ea3d0e commit 85ec675

File tree

5 files changed

+244
-30
lines changed

5 files changed

+244
-30
lines changed
Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,68 @@
1-
//
2-
// File.swift
3-
//
4-
//
5-
// Created by Shangxin Guo on 2023/5/10.
6-
//
7-
81
import Foundation
2+
import KeychainAccess
3+
4+
public final class CodeiumAuthService {
5+
public init() {}
6+
7+
let codeiumKeyKey = "codeiumKey"
8+
let keychain: Keychain = {
9+
let info = Bundle.main.infoDictionary
10+
return Keychain(
11+
service: info?["KEYCHAIN_SERVICE"] as! String,
12+
accessGroup: info?["KEYCHAIN_GROUP"] as! String
13+
)
14+
}()
15+
16+
var key: String? { try? keychain.getString(codeiumKeyKey) }
17+
18+
public var isSignedIn: Bool { return key != nil }
19+
20+
public func signIn(token: String) async throws {
21+
let key = try await generate(token: token)
22+
let info = Bundle.main.infoDictionary
23+
let keychain = Keychain(
24+
service: info?["KEYCHAIN_SERVICE"] as! String,
25+
accessGroup: info?["KEYCHAIN_GROUP"] as! String
26+
)
27+
try keychain.set(key, key: codeiumKeyKey)
28+
}
29+
30+
public func signOut() async throws {
31+
try keychain.remove(codeiumKeyKey)
32+
}
33+
34+
struct GenerateKeyRequestBody: Codable {
35+
var firebase_id_token: String
36+
}
37+
38+
struct GenerateKeyResponseBody: Codable {
39+
var api_key: String
40+
}
41+
42+
struct GenerateKeyErrorResponseBody: Codable, Error, LocalizedError {
43+
var detail: String
44+
var errorDescription: String? { detail }
45+
}
46+
47+
func generate(token: String) async throws -> String {
48+
var request = URLRequest(url: URL(string: "https://api.codeium.com/register_user/")!)
49+
request.httpMethod = "POST"
50+
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
51+
let requestBody = GenerateKeyRequestBody(firebase_id_token: token)
52+
let requestData = try JSONEncoder().encode(requestBody)
53+
request.httpBody = requestData
54+
let (data, _) = try await URLSession.shared.data(for: request)
55+
do {
56+
let response = try JSONDecoder().decode(GenerateKeyResponseBody.self, from: data)
57+
return response.api_key
58+
} catch {
59+
if let response = try? JSONDecoder()
60+
.decode(GenerateKeyErrorResponseBody.self, from: data)
61+
{
62+
throw response
63+
}
64+
throw error
65+
}
66+
}
67+
}
68+

Core/Sources/CodeiumService/CodeiumModels.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ struct CodeiumEditorOptions: Codable {
110110
struct Metadata: Codable {
111111
var ide_name: String
112112
var ide_version: String
113-
var extension_name: String
113+
// var extension_name: String
114114
var extension_version: String
115115
var api_key: String
116116

Core/Sources/CodeiumService/CodeiumService.swift

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ enum CodeiumError: Error, LocalizedError {
3131
}
3232
}
3333

34-
let token = ""
35-
3634
public class CodeiumSuggestionService {
3735
static let sessionId = UUID().uuidString
3836
let projectRootURL: URL
@@ -45,6 +43,10 @@ public class CodeiumSuggestionService {
4543
let languageServerURL: URL
4644
let supportURL: URL
4745

46+
let authService = CodeiumAuthService()
47+
48+
var xcodeVersion = "14.0"
49+
4850
init(designatedServer: CodeiumLSP) {
4951
projectRootURL = URL(fileURLWithPath: "/")
5052
server = designatedServer
@@ -62,12 +64,16 @@ public class CodeiumSuggestionService {
6264
guard FileManager.default.fileExists(atPath: languageServerURL.path) else {
6365
throw CodeiumError.languageServerNotInstalled
6466
}
65-
try setupServerIfNeeded()
67+
Task {
68+
try await setupServerIfNeeded()
69+
}
6670
}
6771

6872
@discardableResult
69-
func setupServerIfNeeded() throws -> CodeiumLSP {
73+
func setupServerIfNeeded() async throws -> CodeiumLSP {
7074
if let server { return server }
75+
let metadata = try getMetadata()
76+
xcodeVersion = (try? await getXcodeVersion()) ?? xcodeVersion
7177
let tempFolderURL = FileManager.default.temporaryDirectory
7278
let managerDirectoryURL = tempFolderURL
7379
.appendingPathComponent("com.intii.CopilotForXcode")
@@ -94,9 +100,8 @@ public class CodeiumSuggestionService {
94100

95101
server.launchHandler = { [weak self] in
96102
guard let self else { return }
97-
let metadata = self.getMetadata()
98103
self.onServiceLaunched()
99-
self.heartbeatTask = Task { [weak self] in
104+
self.heartbeatTask = Task { [weak self, metadata] in
100105
while true {
101106
try Task.checkCancellation()
102107
_ = try? await self?.server?.sendRequest(
@@ -151,13 +156,18 @@ public class CodeiumSuggestionService {
151156
}
152157

153158
extension CodeiumSuggestionService {
154-
func getMetadata() -> Metadata {
155-
Metadata(
156-
ide_name: "jetbrains",
157-
ide_version: "14.3",
158-
extension_name: "Copilot for Xcode",
159+
func getMetadata() throws -> Metadata {
160+
guard let key = authService.key else {
161+
struct E: Error, LocalizedError {
162+
var errorDescription: String? { "Codeium not signed in." }
163+
}
164+
throw E()
165+
}
166+
return Metadata(
167+
ide_name: "xcode",
168+
ide_version: xcodeVersion,
159169
extension_version: "14.0.0",
160-
api_key: token,
170+
api_key: key,
161171
session_id: CodeiumSuggestionService.sessionId,
162172
request_id: requestCounter
163173
)
@@ -195,7 +205,7 @@ extension CodeiumSuggestionService: CodeiumSuggestionServiceType {
195205
let relativePath = getRelativePath(of: fileURL)
196206

197207
let request = CodeiumRequest.GetCompletion(requestBody: .init(
198-
metadata: getMetadata(),
208+
metadata: try getMetadata(),
199209
document: .init(
200210
absolute_path: fileURL.path,
201211
relative_path: relativePath,
@@ -221,7 +231,7 @@ extension CodeiumSuggestionService: CodeiumSuggestionServiceType {
221231
}
222232
))
223233

224-
let result = try await (try setupServerIfNeeded()).sendRequest(request)
234+
let result = try await (try await setupServerIfNeeded()).sendRequest(request)
225235

226236
return result.completionItems?.filter { item in
227237
if ignoreSpaceOnlySuggestions {
@@ -279,3 +289,36 @@ extension CodeiumSuggestionService: CodeiumSuggestionServiceType {
279289
}
280290
}
281291

292+
func getXcodeVersion() async throws -> String {
293+
let task = Process()
294+
task.launchPath = "/usr/bin/xcodebuild"
295+
task.arguments = ["-version"]
296+
let outpipe = Pipe()
297+
task.standardOutput = outpipe
298+
task.standardError = Pipe()
299+
return try await withUnsafeThrowingContinuation { continuation in
300+
do {
301+
task.terminationHandler = { _ in
302+
do {
303+
if let data = try outpipe.fileHandleForReading.readToEnd(),
304+
let content = String(data: data, encoding: .utf8)
305+
{
306+
let firstLine = content.split(separator: "\n").first ?? ""
307+
var version = firstLine.replacingOccurrences(of: "Xcode ", with: "")
308+
if version.isEmpty {
309+
version = "14.0"
310+
}
311+
continuation.resume(returning: version)
312+
return
313+
}
314+
continuation.resume(returning: "")
315+
} catch {
316+
continuation.resume(throwing: error)
317+
}
318+
}
319+
try task.run()
320+
} catch {
321+
continuation.resume(throwing: error)
322+
}
323+
}
324+
}
Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,110 @@
1-
//
2-
// File.swift
3-
//
4-
//
5-
// Created by Shangxin Guo on 2023/5/10.
6-
//
7-
1+
import CodeiumService
82
import Foundation
3+
import SwiftUI
4+
5+
struct CodeiumView: View {
6+
class ViewModel: ObservableObject {
7+
let codeiumAuthService = CodeiumAuthService()
8+
@Published var isSignedIn: Bool
9+
10+
init() {
11+
isSignedIn = codeiumAuthService.isSignedIn
12+
}
13+
14+
func generateAuthURL() -> URL {
15+
return URL(
16+
string: "https://www.codeium.com/profile?response_type=token&redirect_uri=show-auth-token&state=\(UUID().uuidString)&scope=openid%20profile%20email&redirect_parameters_type=query"
17+
)!
18+
}
19+
20+
func signIn(token: String) async throws {
21+
try await codeiumAuthService.signIn(token: token)
22+
Task { @MainActor in isSignedIn = true }
23+
24+
}
25+
26+
func signOut() async throws {
27+
try await codeiumAuthService.signOut()
28+
Task { @MainActor in isSignedIn = false }
29+
}
30+
}
31+
32+
@StateObject var viewModel = ViewModel()
33+
@State var isSignInPanelPresented = false
34+
35+
var body: some View {
36+
Form {
37+
if viewModel.isSignedIn {
38+
Button(action: {
39+
viewModel.isSignedIn = false
40+
}) {
41+
Text("Sign Out")
42+
}
43+
} else {
44+
Button(action: {
45+
isSignInPanelPresented = true
46+
}) {
47+
Text("Sign In")
48+
}
49+
}
50+
}
51+
.sheet(isPresented: $isSignInPanelPresented) {
52+
CodeiumSignInView(viewModel: viewModel, isPresented: $isSignInPanelPresented)
53+
}
54+
}
55+
}
56+
57+
struct CodeiumSignInView: View {
58+
let viewModel: CodeiumView.ViewModel
59+
@Binding var isPresented: Bool
60+
@Environment(\.openURL) var openURL
61+
@Environment(\.toast) var toast
62+
@State var isGeneratingKey = false
63+
@State var token = ""
64+
65+
var body: some View {
66+
VStack {
67+
Text(
68+
"You will be redirected to codeium.com. Please paste the generated token below and click the \"Sign In\" button."
69+
)
70+
71+
TextEditor(text: $token)
72+
.font(Font.system(.body, design: .monospaced))
73+
.padding(4)
74+
.frame(minHeight: 120)
75+
.multilineTextAlignment(.leading)
76+
.overlay(
77+
RoundedRectangle(cornerRadius: 4)
78+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
79+
)
80+
81+
Button(action: {
82+
isGeneratingKey = true
83+
Task {
84+
do {
85+
try await viewModel.signIn(token: token)
86+
isGeneratingKey = false
87+
isPresented = false
88+
} catch {
89+
isGeneratingKey = false
90+
toast(Text(error.localizedDescription), .error)
91+
}
92+
}
93+
}) {
94+
Text(isGeneratingKey ? "Signing In.." : "Sign In")
95+
}.disabled(isGeneratingKey)
96+
}
97+
.padding()
98+
.onAppear {
99+
openURL(viewModel.generateAuthURL())
100+
}
101+
}
102+
}
103+
104+
struct CodeiumView_Previews: PreviewProvider {
105+
static var previews: some View {
106+
CodeiumView()
107+
.frame(width: 600, height: 500)
108+
}
109+
}
110+

Core/Sources/HostApp/ServiceView.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,18 @@ struct ServiceView: View {
1414
)
1515

1616
ScrollView {
17-
OpenAIView().padding()
17+
CodeiumView().padding()
1818
}.sidebarItem(
1919
tag: 1,
20+
title: "Codeium",
21+
subtitle: "Suggestion",
22+
image: "globe"
23+
)
24+
25+
ScrollView {
26+
OpenAIView().padding()
27+
}.sidebarItem(
28+
tag: 2,
2029
title: "OpenAI",
2130
subtitle: "Chat, Prompt to Code",
2231
image: "globe"

0 commit comments

Comments
 (0)