Skip to content

Commit 81fc588

Browse files
committed
Pre-release 0.37.126
1 parent 64a0691 commit 81fc588

38 files changed

+1209
-271
lines changed

Core/Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ let package = Package(
181181
.product(name: "GitHubCopilotService", package: "Tool"),
182182
.product(name: "Workspace", package: "Tool"),
183183
.product(name: "Terminal", package: "Tool"),
184-
.product(name: "SystemUtils", package: "Tool")
184+
.product(name: "SystemUtils", package: "Tool"),
185+
.product(name: "AppKitExtension", package: "Tool")
185186
]),
186187
.testTarget(
187188
name: "ChatServiceTests",

Core/Sources/ChatService/ChatService.swift

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import SystemUtils
1818

1919
public protocol ChatServiceType {
2020
var memory: ContextAwareAutoManagedChatMemory { get set }
21-
func send(_ id: String, content: String, skillSet: [ConversationSkill], references: [FileReference], model: String?, agentMode: Bool, userLanguage: String?, turnId: String?) async throws
21+
func send(_ id: String, content: String, contentImages: [ChatCompletionContentPartImage], contentImageReferences: [ImageReference], skillSet: [ConversationSkill], references: [FileReference], model: String?, agentMode: Bool, userLanguage: String?, turnId: String?) async throws
2222
func stopReceivingMessage() async
2323
func upvote(_ id: String, _ rating: ConversationRating) async
2424
func downvote(_ id: String, _ rating: ConversationRating) async
@@ -316,10 +316,23 @@ public final class ChatService: ChatServiceType, ObservableObject {
316316
}
317317
}
318318
}
319+
320+
public enum ChatServiceError: Error, LocalizedError {
321+
case conflictingImageFormats(String)
322+
323+
public var errorDescription: String? {
324+
switch self {
325+
case .conflictingImageFormats(let message):
326+
return message
327+
}
328+
}
329+
}
319330

320331
public func send(
321332
_ id: String,
322333
content: String,
334+
contentImages: Array<ChatCompletionContentPartImage> = [],
335+
contentImageReferences: Array<ImageReference> = [],
323336
skillSet: Array<ConversationSkill>,
324337
references: Array<FileReference>,
325338
model: String? = nil,
@@ -331,11 +344,31 @@ public final class ChatService: ChatServiceType, ObservableObject {
331344
let workDoneToken = UUID().uuidString
332345
activeRequestId = workDoneToken
333346

347+
let finalImageReferences: [ImageReference]
348+
let finalContentImages: [ChatCompletionContentPartImage]
349+
350+
if !contentImageReferences.isEmpty {
351+
// User attached images are all parsed as ImageReference
352+
finalImageReferences = contentImageReferences
353+
finalContentImages = contentImageReferences
354+
.map {
355+
ChatCompletionContentPartImage(
356+
url: $0.dataURL(imageType: $0.source == .screenshot ? "png" : "")
357+
)
358+
}
359+
} else {
360+
// In current implementation, only resend message will have contentImageReferences
361+
// No need to convert ChatCompletionContentPartImage to ImageReference for persistence
362+
finalImageReferences = []
363+
finalContentImages = contentImages
364+
}
365+
334366
var chatMessage = ChatMessage(
335367
id: id,
336368
chatTabID: self.chatTabInfo.id,
337369
role: .user,
338370
content: content,
371+
contentImageReferences: finalImageReferences,
339372
references: references.toConversationReferences()
340373
)
341374

@@ -406,6 +439,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
406439
let request = createConversationRequest(
407440
workDoneToken: workDoneToken,
408441
content: content,
442+
contentImages: finalContentImages,
409443
activeDoc: activeDoc,
410444
references: references,
411445
model: model,
@@ -417,12 +451,13 @@ public final class ChatService: ChatServiceType, ObservableObject {
417451

418452
self.lastUserRequest = request
419453
self.skillSet = validSkillSet
420-
try await send(request)
454+
try await sendConversationRequest(request)
421455
}
422456

423457
private func createConversationRequest(
424458
workDoneToken: String,
425459
content: String,
460+
contentImages: [ChatCompletionContentPartImage] = [],
426461
activeDoc: Doc?,
427462
references: [FileReference],
428463
model: String? = nil,
@@ -443,6 +478,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
443478
return ConversationRequest(
444479
workDoneToken: workDoneToken,
445480
content: newContent,
481+
contentImages: contentImages,
446482
workspaceFolder: "",
447483
activeDoc: activeDoc,
448484
skills: skillCapabilities,
@@ -504,6 +540,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
504540
try await send(
505541
id,
506542
content: lastUserRequest.content,
543+
contentImages: lastUserRequest.contentImages,
507544
skillSet: skillSet,
508545
references: lastUserRequest.references ?? [],
509546
model: model != nil ? model : lastUserRequest.model,
@@ -720,12 +757,14 @@ public final class ChatService: ChatServiceType, ObservableObject {
720757
await Status.shared
721758
.updateCLSStatus(.warning, busy: false, message: CLSError.message)
722759
let errorMessage = buildErrorMessage(
723-
turnId: progress.turnId,
760+
turnId: progress.turnId,
724761
panelMessages: [.init(type: .error, title: String(CLSError.code ?? 0), message: CLSError.message, location: .Panel)])
725762
// will persist in resetongoingRequest()
726763
await memory.appendMessage(errorMessage)
727764

728-
if let lastUserRequest {
765+
if let lastUserRequest,
766+
let currentUserPlan = await Status.shared.currentUserPlan(),
767+
currentUserPlan != "free" {
729768
guard let fallbackModel = CopilotModelManager.getFallbackLLM(
730769
scope: lastUserRequest.agentMode ? .agentPanel : .chatPanel
731770
) else {
@@ -852,7 +891,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
852891
}
853892
}
854893

855-
private func send(_ request: ConversationRequest) async throws {
894+
private func sendConversationRequest(_ request: ConversationRequest) async throws {
856895
guard !isReceivingMessage else { throw CancellationError() }
857896
isReceivingMessage = true
858897

@@ -892,7 +931,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
892931

893932
switch fileEdit.toolName {
894933
case .insertEditIntoFile:
895-
try InsertEditIntoFileTool.applyEdit(for: fileURL, content: fileEdit.originalContent, contextProvider: self)
934+
InsertEditIntoFileTool.applyEdit(for: fileURL, content: fileEdit.originalContent, contextProvider: self)
896935
case .createFile:
897936
try CreateFileTool.undo(for: fileURL)
898937
default:

Core/Sources/ChatService/ToolCalls/CreateFileTool.swift

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,43 @@ public class CreateFileTool: ICopilotTool {
1717
let filePath = input["filePath"]?.value as? String,
1818
let content = input["content"]?.value as? String
1919
else {
20-
completeResponse(request, response: "Invalid parameters", completion: completion)
20+
completeResponse(request, status: .error, response: "Invalid parameters", completion: completion)
2121
return true
2222
}
2323

2424
let fileURL = URL(fileURLWithPath: filePath)
2525

2626
guard !FileManager.default.fileExists(atPath: filePath)
2727
else {
28-
completeResponse(request, response: "File already exists at \(filePath)", completion: completion)
28+
completeResponse(request, status: .error, response: "File already exists at \(filePath)", completion: completion)
2929
return true
3030
}
3131

3232
do {
3333
try content.write(to: fileURL, atomically: true, encoding: .utf8)
3434
} catch {
35-
completeResponse(request, response: "Failed to write content to file: \(error)", completion: completion)
35+
completeResponse(request, status: .error, response: "Failed to write content to file: \(error)", completion: completion)
3636
return true
3737
}
3838

3939
guard FileManager.default.fileExists(atPath: filePath),
40-
let writtenContent = try? String(contentsOf: fileURL, encoding: .utf8),
41-
!writtenContent.isEmpty
40+
let writtenContent = try? String(contentsOf: fileURL, encoding: .utf8)
4241
else {
43-
completeResponse(request, response: "Failed to verify file creation.", completion: completion)
42+
completeResponse(request, status: .error, response: "Failed to verify file creation.", completion: completion)
4443
return true
4544
}
4645

4746
contextProvider?.updateFileEdits(by: .init(
4847
fileURL: URL(fileURLWithPath: filePath),
4948
originalContent: "",
50-
modifiedContent: content,
49+
modifiedContent: writtenContent,
5150
toolName: CreateFileTool.name
5251
))
5352

54-
do {
55-
if let workspacePath = contextProvider?.chatTabInfo.workspacePath,
56-
let xcodeIntance = Utils.getXcode(by: workspacePath) {
57-
try Utils.openFileInXcode(fileURL: URL(fileURLWithPath: filePath), xcodeInstance: xcodeIntance)
53+
Utils.openFileInXcode(fileURL: URL(fileURLWithPath: filePath)) { _, error in
54+
if let error = error {
55+
Logger.client.info("Failed to open file at \(filePath), \(error)")
5856
}
59-
} catch {
60-
Logger.client.info("Failed to open file in Xcode, \(error)")
6157
}
6258

6359
let editAgentRounds: [AgentRound] = [

Core/Sources/ChatService/ToolCalls/ICopilotTool.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import ConversationServiceProvider
22
import JSONRPC
33
import ChatTab
44

5+
enum ToolInvocationStatus: String {
6+
case success, error, cancelled
7+
}
8+
59
public protocol ToolContextProvider {
610
// MARK: insert_edit_into_file
711
var chatTabInfo: ChatTabInfo { get }
@@ -34,16 +38,21 @@ extension ICopilotTool {
3438
* Completes a tool response.
3539
* - Parameters:
3640
* - request: The original tool invocation request.
41+
* - status: The completion status of the tool execution (success, error, or cancelled).
3742
* - response: The string value to include in the response content.
3843
* - completion: The completion handler to call with the response.
3944
*/
4045
func completeResponse(
4146
_ request: InvokeClientToolRequest,
47+
status: ToolInvocationStatus = .success,
4248
response: String = "",
4349
completion: @escaping (AnyJSONRPCResponse) -> Void
4450
) {
4551
let result: JSONValue = .array([
46-
.hash(["content": .array([.hash(["value": .string(response)])])]),
52+
.hash([
53+
"status": .string(status.rawValue),
54+
"content": .array([.hash(["value": .string(response)])])
55+
]),
4756
.null
4857
])
4958
completion(AnyJSONRPCResponse(id: request.id, result: result))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy