Skip to content

Commit 4fdb4e5

Browse files
committed
Hack ChatGPTService to support GitHub copilot chat
1 parent 250c54b commit 4fdb4e5

3 files changed

Lines changed: 129 additions & 1 deletion

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import AsyncAlgorithms
2+
import BuiltinExtension
3+
import ChatBasic
4+
import Foundation
5+
import XcodeInspector
6+
7+
#warning("This is a temporary implementation for proof of concept.")
8+
9+
actor BuiltinExtensionChatCompletionsService {
10+
typealias RequestBody = ChatCompletionsRequestBody
11+
12+
enum CustomError: Swift.Error, LocalizedError {
13+
case chatServiceNotFound
14+
15+
var errorDescription: String? {
16+
switch self {
17+
case .chatServiceNotFound:
18+
return "Chat service not found."
19+
}
20+
}
21+
}
22+
23+
var extensionManager: BuiltinExtensionManager { .shared }
24+
25+
let extensionIdentifier: String
26+
let requestBody: RequestBody
27+
28+
init(extensionIdentifier: String, requestBody: RequestBody) {
29+
self.extensionIdentifier = extensionIdentifier
30+
self.requestBody = requestBody
31+
}
32+
}
33+
34+
extension BuiltinExtensionChatCompletionsService: ChatCompletionsAPI {
35+
func callAsFunction() async throws -> ChatCompletionResponseBody {
36+
fatalError()
37+
}
38+
}
39+
40+
extension BuiltinExtensionChatCompletionsService: ChatCompletionsStreamAPI {
41+
func callAsFunction(
42+
) async throws -> AsyncThrowingStream<ChatCompletionsStreamDataChunk, Error> {
43+
let service = try getChatService()
44+
let (message, history) = extractMessageAndHistory(from: requestBody)
45+
guard let workspaceURL = XcodeInspector.shared.realtimeActiveWorkspaceURL,
46+
let projectURL = XcodeInspector.shared.realtimeActiveProjectURL
47+
else { throw CancellationError() }
48+
let stream = await service.sendMessage(
49+
message,
50+
history: history,
51+
references: [],
52+
workspace: .init(
53+
workspaceURL: workspaceURL,
54+
projectURL: projectURL
55+
)
56+
)
57+
58+
return stream.map { text in
59+
ChatCompletionsStreamDataChunk(
60+
id: nil,
61+
object: nil,
62+
model: nil,
63+
message: .init(
64+
role: .assistant,
65+
content: text,
66+
toolCalls: nil
67+
),
68+
finishReason: nil
69+
)
70+
}.toStream()
71+
}
72+
}
73+
74+
extension BuiltinExtensionChatCompletionsService {
75+
func getChatService() throws -> any BuiltinExtensionChatServiceType {
76+
guard let ext = extensionManager.extensions
77+
.first(where: { $0.extensionIdentifier == extensionIdentifier }),
78+
let service = ext.chatService as? BuiltinExtensionChatServiceType
79+
else {
80+
throw CustomError.chatServiceNotFound
81+
}
82+
return service
83+
}
84+
}
85+
86+
extension BuiltinExtensionChatCompletionsService {
87+
func extractMessageAndHistory(
88+
from request: RequestBody
89+
) -> (message: String, history: [ChatMessage]) {
90+
let messages = request.messages
91+
92+
if let lastIndexNotUserMessage = messages.lastIndex(where: { $0.role != .user }) {
93+
let message = messages[(lastIndexNotUserMessage + 1)...]
94+
.map { $0.content }
95+
.joined(separator: "\n\n")
96+
let history = Array(messages[0...lastIndexNotUserMessage])
97+
return (message, history.map {
98+
.init(id: UUID().uuidString, role: $0.role.asChatMessageRole, content: $0.content)
99+
})
100+
} else { // everything is user message
101+
let message = messages.map { $0.content }.joined(separator: "\n\n")
102+
return (message, [])
103+
}
104+
}
105+
}
106+

Tool/Sources/OpenAIService/ChatGPTService.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,21 @@ public class ChatGPTService: ChatGPTServiceType {
9090
var runningTask: Task<Void, Never>?
9191
var buildCompletionStreamAPI: ChatCompletionsStreamAPIBuilder = {
9292
apiKey, model, endpoint, requestBody, prompt in
93+
94+
if model.id == "com.github.copilot" {
95+
return BuiltinExtensionChatCompletionsService(
96+
extensionIdentifier: model.id,
97+
requestBody: requestBody
98+
)
99+
}
100+
93101
switch model.format {
94102
case .googleAI:
95103
return GoogleAIChatCompletionsService(
96104
apiKey: apiKey,
97105
model: model,
98106
requestBody: requestBody,
99-
prompt: prompt,
107+
prompt: prompt,
100108
baseURL: endpoint.absoluteString
101109
)
102110
case .openAI, .openAICompatible, .azureOpenAI:
@@ -125,6 +133,14 @@ public class ChatGPTService: ChatGPTServiceType {
125133

126134
var buildCompletionAPI: ChatCompletionsAPIBuilder = {
127135
apiKey, model, endpoint, requestBody, prompt in
136+
137+
if model.id == "com.github.copilot" {
138+
return BuiltinExtensionChatCompletionsService(
139+
extensionIdentifier: model.id,
140+
requestBody: requestBody
141+
)
142+
}
143+
128144
switch model.format {
129145
case .googleAI:
130146
return GoogleAIChatCompletionsService(

Tool/Sources/OpenAIService/Configuration/UserPreferenceChatGPTConfiguration.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ public struct UserPreferenceChatGPTConfiguration: ChatGPTConfiguration {
1616

1717
if let chatModelKey {
1818
let id = UserDefaults.shared.value(for: chatModelKey)
19+
if id == "com.github.copilot" {
20+
return .init(id: id, name: "GitHub Copilot", format: .openAI, info: .init())
21+
}
1922
if let model = models.first(where: { $0.id == id }) {
2023
return model
2124
}
2225
}
2326

2427
let id = UserDefaults.shared.value(for: \.defaultChatFeatureChatModelId)
28+
if id == "com.github.copilot" {
29+
return .init(id: id, name: "GitHub Copilot", format: .openAI, info: .init())
30+
}
2531
return models.first { $0.id == id }
2632
?? models.first
2733
}

0 commit comments

Comments
 (0)