forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathBuiltinExtensionChatCompletionsService.swift
More file actions
125 lines (110 loc) · 4.13 KB
/
BuiltinExtensionChatCompletionsService.swift
File metadata and controls
125 lines (110 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import AsyncAlgorithms
import BuiltinExtension
import ChatBasic
import Foundation
import XcodeInspector
#warning("This is a temporary implementation for proof of concept.")
actor BuiltinExtensionChatCompletionsService {
typealias RequestBody = ChatCompletionsRequestBody
enum CustomError: Swift.Error, LocalizedError {
case chatServiceNotFound
var errorDescription: String? {
switch self {
case .chatServiceNotFound:
return "Chat service not found."
}
}
}
var extensionManager: BuiltinExtensionManager { .shared }
let extensionIdentifier: String
let requestBody: RequestBody
init(extensionIdentifier: String, requestBody: RequestBody) {
self.extensionIdentifier = extensionIdentifier
self.requestBody = requestBody
}
}
extension BuiltinExtensionChatCompletionsService: ChatCompletionsAPI {
func callAsFunction() async throws -> ChatCompletionResponseBody {
let stream: AsyncThrowingStream<ChatCompletionsStreamDataChunk, Error> =
try await callAsFunction()
var id: String? = nil
var model = ""
var content = ""
for try await chunk in stream {
if let chunkId = chunk.id { id = chunkId }
if model.isEmpty, let chunkModel = chunk.model { model = chunkModel }
content.append(chunk.message?.content ?? "")
}
return .init(
id: id,
object: "",
model: model,
message: .init(role: .assistant, content: content),
otherChoices: [],
finishReason: ""
)
}
}
extension BuiltinExtensionChatCompletionsService: ChatCompletionsStreamAPI {
func callAsFunction(
) async throws -> AsyncThrowingStream<ChatCompletionsStreamDataChunk, Error> {
let service = try getChatService()
let (message, history) = extractMessageAndHistory(from: requestBody)
guard let workspaceURL = await XcodeInspector.shared.safe.realtimeActiveWorkspaceURL,
let projectURL = await XcodeInspector.shared.safe.realtimeActiveProjectURL
else { throw CancellationError() }
let stream = await service.sendMessage(
message,
history: history,
references: [],
workspace: .init(
workspaceURL: workspaceURL,
projectURL: projectURL
)
)
let responseID = UUID().uuidString
return stream.map { text in
ChatCompletionsStreamDataChunk(
id: responseID,
object: nil,
model: "github-copilot",
message: .init(
role: .assistant,
content: text,
toolCalls: nil
),
finishReason: nil
)
}.toStream()
}
}
extension BuiltinExtensionChatCompletionsService {
func getChatService() throws -> any BuiltinExtensionChatServiceType {
guard let ext = extensionManager.extensions
.first(where: { $0.extensionIdentifier == extensionIdentifier }),
let service = ext.chatService as? BuiltinExtensionChatServiceType
else {
throw CustomError.chatServiceNotFound
}
return service
}
}
extension BuiltinExtensionChatCompletionsService {
func extractMessageAndHistory(
from request: RequestBody
) -> (message: String, history: [ChatMessage]) {
let messages = request.messages
if let lastIndexNotUserMessage = messages.lastIndex(where: { $0.role != .user }) {
let message = messages[(lastIndexNotUserMessage + 1)...]
.map { $0.content }
.joined(separator: "\n\n")
let history = Array(messages[0...lastIndexNotUserMessage])
return (message, history.map {
.init(id: UUID().uuidString, role: $0.role.asChatMessageRole, content: $0.content)
})
} else { // everything is user message
let message = messages.map { $0.content }.joined(separator: "\n\n")
return (message, [])
}
}
}