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
106 lines (92 loc) · 3.41 KB
/
BuiltinExtensionChatCompletionsService.swift
File metadata and controls
106 lines (92 loc) · 3.41 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
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 {
fatalError()
}
}
extension BuiltinExtensionChatCompletionsService: ChatCompletionsStreamAPI {
func callAsFunction(
) async throws -> AsyncThrowingStream<ChatCompletionsStreamDataChunk, Error> {
let service = try getChatService()
let (message, history) = extractMessageAndHistory(from: requestBody)
guard let workspaceURL = XcodeInspector.shared.realtimeActiveWorkspaceURL,
let projectURL = XcodeInspector.shared.realtimeActiveProjectURL
else { throw CancellationError() }
let stream = await service.sendMessage(
message,
history: history,
references: [],
workspace: .init(
workspaceURL: workspaceURL,
projectURL: projectURL
)
)
return stream.map { text in
ChatCompletionsStreamDataChunk(
id: nil,
object: nil,
model: nil,
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, [])
}
}
}