Skip to content

Commit 64c2e65

Browse files
committed
Merge branch 'feature/prompt-to-code-service' into develop
2 parents 26c7489 + 9da0c25 commit 64c2e65

39 files changed

Lines changed: 1461 additions & 310 deletions

Copilot for Xcode.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
C841BB242994CAD400B0B336 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C841BB232994CAD400B0B336 /* SettingsView.swift */; };
3333
C8520301293C4D9000460097 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8520300293C4D9000460097 /* Helpers.swift */; };
3434
C85AF32D29CF0C170031E18B /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C85AF32C29CF0C170031E18B /* DebugView.swift */; };
35+
C861A6A329E5503F005C41A3 /* PromptToCodeCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */; };
3536
C861E6112994F6070056CB02 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C861E6102994F6070056CB02 /* AppDelegate.swift */; };
3637
C861E6152994F6080056CB02 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C861E6142994F6080056CB02 /* Assets.xcassets */; };
3738
C861E61E2994F6150056CB02 /* Service in Frameworks */ = {isa = PBXBuildFile; productRef = C861E61D2994F6150056CB02 /* Service */; };
@@ -180,6 +181,7 @@
180181
C8520300293C4D9000460097 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = "<group>"; };
181182
C8520308293D805800460097 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
182183
C85AF32C29CF0C170031E18B /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = "<group>"; };
184+
C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptToCodeCommand.swift; sourceTree = "<group>"; };
183185
C861E60E2994F6070056CB02 /* CopilotForXcodeExtensionService.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CopilotForXcodeExtensionService.app; sourceTree = BUILT_PRODUCTS_DIR; };
184186
C861E6102994F6070056CB02 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
185187
C861E6142994F6080056CB02 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@@ -262,6 +264,7 @@
262264
C800DBB0294C624D00B04CAC /* PrefetchSuggestionsCommand.swift */,
263265
C8EE07A029CC9ED30043B6D9 /* ExplainSelectionCommand.swift */,
264266
C8DCEFFF29CE11D500FDDDD7 /* ChatWithSelection.swift */,
267+
C861A6A229E5503F005C41A3 /* PromptToCodeCommand.swift */,
265268
C81458972939EFDC00135263 /* Info.plist */,
266269
C81458982939EFDC00135263 /* EditorExtension.entitlements */,
267270
);
@@ -527,6 +530,7 @@
527530
files = (
528531
C8DCF00029CE11D500FDDDD7 /* ChatWithSelection.swift in Sources */,
529532
C81458942939EFDC00135263 /* SourceEditorExtension.swift in Sources */,
533+
C861A6A329E5503F005C41A3 /* PromptToCodeCommand.swift in Sources */,
530534
C8520301293C4D9000460097 /* Helpers.swift in Sources */,
531535
C8009BFF2941C551007AA7E8 /* ToggleRealtimeSuggestionsCommand.swift in Sources */,
532536
C87B03A5293B261200C77EAE /* AcceptSuggestionCommand.swift in Sources */,

Core/Package.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ let package = Package(
8787
"AXExtension",
8888
"Logger",
8989
"ChatService",
90+
"PromptToCodeService",
9091
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
9192
]
9293
),
@@ -154,5 +155,10 @@ let package = Package(
154155
.target(name: "ChatPlugins", dependencies: ["OpenAIService", "Environment", "Terminal"]),
155156
.target(name: "Terminal"),
156157
.target(name: "ChatService", dependencies: ["OpenAIService", "ChatPlugins", "Environment"]),
158+
.target(
159+
name: "PromptToCodeService",
160+
dependencies: ["OpenAIService", "Environment", "CopilotService", "CopilotModel"]
161+
),
162+
.testTarget(name: "PromptToCodeServiceTests", dependencies: ["PromptToCodeService"]),
157163
]
158164
)

Core/Sources/ChatPlugins/AITerminalChatPlugin.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public actor AITerminalChatPlugin: ChatPlugin {
5252
delegate?.pluginDidEndResponding(self)
5353
await chatGPTService.mutateHistory { history in
5454
history.append(.init(role: .assistant, content: """
55-
Confirm to run?
55+
Should I run this command? You can instruct me to modify it again.
5656
```
5757
\(result)
5858
```
@@ -63,7 +63,7 @@ public actor AITerminalChatPlugin: ChatPlugin {
6363
await chatGPTService.mutateHistory { history in
6464
history.append(.init(
6565
role: .assistant,
66-
content: "Should I run it? Or should I modify it?"
66+
content: "Sorry, I don't understand. Do you want me to run it?"
6767
))
6868
}
6969
}
@@ -77,7 +77,7 @@ public actor AITerminalChatPlugin: ChatPlugin {
7777
if isCancelled { return }
7878
await chatGPTService.mutateHistory { history in
7979
history.append(.init(role: .assistant, content: """
80-
Confirm to run?
80+
Should I run this command? You can instruct me to modify it.
8181
```
8282
\(result)
8383
```
@@ -118,7 +118,7 @@ public actor AITerminalChatPlugin: ChatPlugin {
118118
return extractCodeFromMarkdown(try await askChatGPT(
119119
systemPrompt: p,
120120
question: "the task is: \"\(task)\""
121-
))
121+
) ?? "")
122122
}
123123

124124
func modifyCommand(command: String, requirement: String) async throws -> String {
@@ -139,7 +139,7 @@ public actor AITerminalChatPlugin: ChatPlugin {
139139
return extractCodeFromMarkdown(try await askChatGPT(
140140
systemPrompt: p,
141141
question: "The requirement is: \"\(requirement)\""
142-
))
142+
) ?? "")
143143
}
144144

145145
func checkConfirmation(content: String) async throws -> Tone {
@@ -165,7 +165,8 @@ public actor AITerminalChatPlugin: ChatPlugin {
165165
systemPrompt: p,
166166
question: "The content is: \"\(content)\""
167167
)
168-
return Tone(rawValue: Int(result) ?? 2) ?? .cancellation
168+
let tone = result.flatMap(Int.init).flatMap(Tone.init(rawValue:)) ?? .other
169+
return tone
169170
}
170171

171172
enum Tone: Int {

Core/Sources/ChatPlugins/AskChatGPT.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import OpenAIService
33

44
/// Quickly ask a question to ChatGPT.
5-
func askChatGPT(systemPrompt: String, question: String) async throws -> String {
5+
func askChatGPT(systemPrompt: String, question: String) async throws -> String? {
66
let service = ChatGPTService(systemPrompt: systemPrompt)
77
return try await service.sendAndWait(content: question)
88
}

Core/Sources/ChatPlugins/CallAIFunction.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ func callAIFunction(
77
function: String,
88
args: [Any?],
99
description: String
10-
) async throws -> String {
10+
) async throws -> String? {
1111
let args = args.map { arg -> String in
1212
if let arg = arg {
1313
return String(describing: arg)

Core/Sources/Client/AsyncXPCService.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,14 @@ public struct AsyncXPCService {
186186
{ $0.chatWithSelection }
187187
)
188188
}
189+
190+
public func promptToCode(editorContent: EditorContent) async throws -> UpdatedContent? {
191+
try await suggestionRequest(
192+
connection,
193+
editorContent,
194+
{ $0.promptToCode }
195+
)
196+
}
189197
}
190198

191199
struct AutoFinishContinuation<T> {

Core/Sources/CopilotModel/CopilotCompletion.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,14 @@ public struct CopilotCompletion: Codable, Equatable {
1515
self.displayText = displayText
1616
}
1717

18+
/// The new code to be inserted and the original code on the first line.
1819
public var text: String
20+
/// The position of the cursor before generating the completion.
1921
public var position: CursorPosition
22+
/// An id.
2023
public var uuid: String
24+
/// The range of the original code that should be replaced.
2125
public var range: CursorRange
26+
/// The new code to be inserted.
2227
public var displayText: String
2328
}

Core/Sources/CopilotModel/ExportedFromLSP.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ public typealias CursorRange = LanguageServerProtocol.LSPRange
66
public extension CursorPosition {
77
static var outOfScope: CursorPosition { .init(line: -1, character: -1) }
88
}
9+
10+
public extension CursorRange {
11+
static var outOfScope: CursorRange { .init(start: .outOfScope, end: .outOfScope) }
12+
}

Core/Sources/OpenAIService/ChatGPTService.swift

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public actor ChatGPTService: ChatGPTServiceType {
8888
var uuidGenerator: () -> String = { UUID().uuidString }
8989
var cancelTask: Cancellable?
9090
var buildCompletionStreamAPI: CompletionStreamAPIBuilder = OpenAICompletionStreamAPI.init
91+
var buildCompletionAPI: CompletionAPIBuilder = OpenAICompletionAPI.init
9192

9293
public init(
9394
systemPrompt: String = "",
@@ -121,72 +122,99 @@ public actor ChatGPTService: ChatGPTServiceType {
121122

122123
isReceivingMessage = true
123124

124-
do {
125-
let api = buildCompletionStreamAPI(apiKey, url, requestBody)
125+
let api = buildCompletionStreamAPI(apiKey, url, requestBody)
126126

127-
return AsyncThrowingStream<String, Error> { continuation in
128-
Task {
129-
do {
130-
let (trunks, cancel) = try await api()
131-
guard isReceivingMessage else {
132-
continuation.finish()
133-
return
134-
}
135-
cancelTask = cancel
136-
for try await trunk in trunks {
137-
guard let delta = trunk.choices.first?.delta else { continue }
138-
139-
if history.last?.id == trunk.id {
140-
if let role = delta.role {
141-
history[history.endIndex - 1].role = role
142-
}
143-
if let content = delta.content {
144-
history[history.endIndex - 1].content.append(content)
145-
}
146-
} else {
147-
history.append(.init(
148-
id: trunk.id,
149-
role: delta.role ?? .assistant,
150-
content: delta.content ?? ""
151-
))
152-
}
127+
return AsyncThrowingStream<String, Error> { continuation in
128+
Task {
129+
do {
130+
let (trunks, cancel) = try await api()
131+
guard isReceivingMessage else {
132+
continuation.finish()
133+
return
134+
}
135+
cancelTask = cancel
136+
for try await trunk in trunks {
137+
guard let delta = trunk.choices.first?.delta else { continue }
153138

139+
if history.last?.id == trunk.id {
140+
if let role = delta.role {
141+
history[history.endIndex - 1].role = role
142+
}
154143
if let content = delta.content {
155-
continuation.yield(content)
144+
history[history.endIndex - 1].content.append(content)
156145
}
146+
} else {
147+
history.append(.init(
148+
id: trunk.id,
149+
role: delta.role ?? .assistant,
150+
content: delta.content ?? ""
151+
))
157152
}
158153

159-
continuation.finish()
160-
isReceivingMessage = false
161-
} catch let error as CancellationError {
162-
isReceivingMessage = false
163-
continuation.finish(throwing: error)
164-
} catch let error as NSError where error.code == NSURLErrorCancelled {
165-
isReceivingMessage = false
166-
continuation.finish(throwing: error)
167-
} catch {
168-
history.append(.init(
169-
role: .assistant,
170-
content: error.localizedDescription
171-
))
172-
isReceivingMessage = false
173-
continuation.finish(throwing: error)
154+
if let content = delta.content {
155+
continuation.yield(content)
156+
}
174157
}
158+
159+
continuation.finish()
160+
isReceivingMessage = false
161+
} catch let error as CancellationError {
162+
isReceivingMessage = false
163+
continuation.finish(throwing: error)
164+
} catch let error as NSError where error.code == NSURLErrorCancelled {
165+
isReceivingMessage = false
166+
continuation.finish(throwing: error)
167+
} catch {
168+
history.append(.init(
169+
role: .assistant,
170+
content: error.localizedDescription
171+
))
172+
isReceivingMessage = false
173+
continuation.finish(throwing: error)
175174
}
176175
}
177176
}
178177
}
179-
178+
180179
public func sendAndWait(
181180
content: String,
182181
summary: String? = nil
183-
) async throws -> String {
184-
let stream = try await send(content: content, summary: summary)
185-
var content = ""
186-
for try await fragment in stream {
187-
content.append(fragment)
182+
) async throws -> String? {
183+
guard !isReceivingMessage else { throw CancellationError() }
184+
guard let url = URL(string: endpoint) else { throw ChatGPTServiceError.endpointIncorrect }
185+
let newMessage = ChatMessage(
186+
id: uuidGenerator(),
187+
role: .user,
188+
content: content,
189+
summary: summary
190+
)
191+
history.append(newMessage)
192+
193+
let requestBody = CompletionRequestBody(
194+
model: model,
195+
messages: combineHistoryWithSystemPrompt(),
196+
temperature: temperature,
197+
stream: true,
198+
max_tokens: maxToken
199+
)
200+
201+
isReceivingMessage = true
202+
defer { isReceivingMessage = false }
203+
204+
let api = buildCompletionAPI(apiKey, url, requestBody)
205+
let response = try await api()
206+
207+
if let choice = response.choices.first {
208+
history.append(.init(
209+
id: response.id,
210+
role: choice.message.role,
211+
content: choice.message.content
212+
))
213+
214+
return choice.message.content
188215
}
189-
return content
216+
217+
return nil
190218
}
191219

192220
public func stopReceivingMessage() {
@@ -231,7 +259,7 @@ extension ChatGPTService {
231259
all.append(.init(role: message.role, content: message.content))
232260
count += 1
233261
}
234-
262+
235263
all.append(.init(role: .system, content: systemPrompt))
236264
return all.reversed()
237265
}

0 commit comments

Comments
 (0)