Skip to content

Commit d3cd006

Browse files
committed
Pre-release 0.36.123
1 parent 2e8e989 commit d3cd006

34 files changed

+1036
-285
lines changed

Core/Package.swift

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

Core/Sources/ChatService/ChatService.swift

Lines changed: 122 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Logger
1414
import Workspace
1515
import XcodeInspector
1616
import OrderedCollections
17+
import SystemUtils
1718

1819
public protocol ChatServiceType {
1920
var memory: ContextAwareAutoManagedChatMemory { get set }
@@ -330,22 +331,42 @@ public final class ChatService: ChatServiceType, ObservableObject {
330331
let workDoneToken = UUID().uuidString
331332
activeRequestId = workDoneToken
332333

333-
let chatMessage = ChatMessage(
334+
var chatMessage = ChatMessage(
334335
id: id,
335336
chatTabID: self.chatTabInfo.id,
336337
role: .user,
337338
content: content,
338339
references: references.toConversationReferences()
339340
)
340341

342+
let currentEditorSkill = skillSet.first(where: { $0.id == CurrentEditorSkill.ID }) as? CurrentEditorSkill
343+
let currentFileReadability = currentEditorSkill == nil
344+
? nil
345+
: FileUtils.checkFileReadability(at: currentEditorSkill!.currentFilePath)
346+
var errorMessage: ChatMessage?
347+
348+
var currentTurnId: String? = turnId
341349
// If turnId is provided, it is used to update the existing message, no need to append the user message
342350
if turnId == nil {
351+
if let currentFileReadability, !currentFileReadability.isReadable {
352+
// For associating error message with user message
353+
currentTurnId = UUID().uuidString
354+
chatMessage.clsTurnID = currentTurnId
355+
errorMessage = buildErrorMessage(
356+
turnId: currentTurnId!,
357+
errorMessages: [
358+
currentFileReadability.errorMessage(
359+
using: CurrentEditorSkill.readabilityErrorMessageProvider
360+
)
361+
].compactMap { $0 }.filter { !$0.isEmpty }
362+
)
363+
}
343364
await memory.appendMessage(chatMessage)
344365
}
345366

346367
// reset file edits
347368
self.resetFileEdits()
348-
369+
349370
// persist
350371
saveChatMessageToStorage(chatMessage)
351372

@@ -370,32 +391,68 @@ public final class ChatService: ChatServiceType, ObservableObject {
370391
return
371392
}
372393

373-
let skillCapabilities: [String] = [ CurrentEditorSkill.ID, ProblemsInActiveDocumentSkill.ID ]
394+
if let errorMessage {
395+
Task { await memory.appendMessage(errorMessage) }
396+
}
397+
398+
var activeDoc: Doc?
399+
var validSkillSet: [ConversationSkill] = skillSet
400+
if let currentEditorSkill, currentFileReadability?.isReadable == true {
401+
activeDoc = Doc(uri: currentEditorSkill.currentFile.url.absoluteString)
402+
} else {
403+
validSkillSet.removeAll(where: { $0.id == CurrentEditorSkill.ID || $0.id == ProblemsInActiveDocumentSkill.ID })
404+
}
405+
406+
let request = createConversationRequest(
407+
workDoneToken: workDoneToken,
408+
content: content,
409+
activeDoc: activeDoc,
410+
references: references,
411+
model: model,
412+
agentMode: agentMode,
413+
userLanguage: userLanguage,
414+
turnId: currentTurnId,
415+
skillSet: validSkillSet
416+
)
417+
418+
self.lastUserRequest = request
419+
self.skillSet = validSkillSet
420+
try await send(request)
421+
}
422+
423+
private func createConversationRequest(
424+
workDoneToken: String,
425+
content: String,
426+
activeDoc: Doc?,
427+
references: [FileReference],
428+
model: String? = nil,
429+
agentMode: Bool = false,
430+
userLanguage: String? = nil,
431+
turnId: String? = nil,
432+
skillSet: [ConversationSkill]
433+
) -> ConversationRequest {
434+
let skillCapabilities: [String] = [CurrentEditorSkill.ID, ProblemsInActiveDocumentSkill.ID]
374435
let supportedSkills: [String] = skillSet.map { $0.id }
375436
let ignoredSkills: [String] = skillCapabilities.filter {
376437
!supportedSkills.contains($0)
377438
}
378-
let currentEditorSkill = skillSet.first { $0.id == CurrentEditorSkill.ID }
379-
let activeDoc: Doc? = (currentEditorSkill as? CurrentEditorSkill).map { Doc(uri: $0.currentFile.url.absoluteString) }
380439

381440
/// replace the `@workspace` to `@project`
382441
let newContent = replaceFirstWord(in: content, from: "@workspace", to: "@project")
383442

384-
let request = ConversationRequest(workDoneToken: workDoneToken,
385-
content: newContent,
386-
workspaceFolder: "",
387-
activeDoc: activeDoc,
388-
skills: skillCapabilities,
389-
ignoredSkills: ignoredSkills,
390-
references: references,
391-
model: model,
392-
agentMode: agentMode,
393-
userLanguage: userLanguage,
394-
turnId: turnId
443+
return ConversationRequest(
444+
workDoneToken: workDoneToken,
445+
content: newContent,
446+
workspaceFolder: "",
447+
activeDoc: activeDoc,
448+
skills: skillCapabilities,
449+
ignoredSkills: ignoredSkills,
450+
references: references,
451+
model: model,
452+
agentMode: agentMode,
453+
userLanguage: userLanguage,
454+
turnId: turnId
395455
)
396-
self.lastUserRequest = request
397-
self.skillSet = skillSet
398-
try await send(request)
399456
}
400457

401458
public func sendAndWait(_ id: String, content: String) async throws -> String {
@@ -444,20 +501,16 @@ public final class ChatService: ChatServiceType, ObservableObject {
444501
{
445502
// TODO: clean up contents for resend message
446503
activeRequestId = nil
447-
do {
448-
try await send(
449-
id,
450-
content: lastUserRequest.content,
451-
skillSet: skillSet,
452-
references: lastUserRequest.references ?? [],
453-
model: model != nil ? model : lastUserRequest.model,
454-
agentMode: lastUserRequest.agentMode,
455-
userLanguage: lastUserRequest.userLanguage,
456-
turnId: id
457-
)
458-
} catch {
459-
print("Failed to resend message")
460-
}
504+
try await send(
505+
id,
506+
content: lastUserRequest.content,
507+
skillSet: skillSet,
508+
references: lastUserRequest.references ?? [],
509+
model: model != nil ? model : lastUserRequest.model,
510+
agentMode: lastUserRequest.agentMode,
511+
userLanguage: lastUserRequest.userLanguage,
512+
turnId: id
513+
)
461514
}
462515
}
463516

@@ -569,6 +622,19 @@ public final class ChatService: ChatServiceType, ObservableObject {
569622

570623
Task {
571624
if var lastUserMessage = await memory.history.last(where: { $0.role == .user }) {
625+
626+
// Case: New conversation where error message was generated before CLS request
627+
// Using clsTurnId to associate this error message with the corresponding user message
628+
// When merging error messages with bot responses from CLS, these properties need to be updated
629+
await memory.mutateHistory { history in
630+
if let existingBotIndex = history.lastIndex(where: {
631+
$0.role == .assistant && $0.clsTurnID == lastUserMessage.clsTurnID
632+
}) {
633+
history[existingBotIndex].id = turnId
634+
history[existingBotIndex].clsTurnID = turnId
635+
}
636+
}
637+
572638
lastUserMessage.clsTurnID = progress.turnId
573639
saveChatMessageToStorage(lastUserMessage)
574640
}
@@ -653,14 +719,9 @@ public final class ChatService: ChatServiceType, ObservableObject {
653719
Task {
654720
await Status.shared
655721
.updateCLSStatus(.warning, busy: false, message: CLSError.message)
656-
let errorMessage = ChatMessage(
657-
id: progress.turnId,
658-
chatTabID: self.chatTabInfo.id,
659-
clsTurnID: progress.turnId,
660-
role: .assistant,
661-
content: "",
662-
panelMessages: [.init(type: .error, title: String(CLSError.code ?? 0), message: CLSError.message, location: .Panel)]
663-
)
722+
let errorMessage = buildErrorMessage(
723+
turnId: progress.turnId,
724+
panelMessages: [.init(type: .error, title: String(CLSError.code ?? 0), message: CLSError.message, location: .Panel)])
664725
// will persist in resetongoingRequest()
665726
await memory.appendMessage(errorMessage)
666727

@@ -683,27 +744,17 @@ public final class ChatService: ChatServiceType, ObservableObject {
683744
}
684745
} else if CLSError.code == 400 && CLSError.message.contains("model is not supported") {
685746
Task {
686-
let errorMessage = ChatMessage(
687-
id: progress.turnId,
688-
chatTabID: self.chatTabInfo.id,
689-
role: .assistant,
690-
content: "",
691-
errorMessage: "Oops, the model is not supported. Please enable it first in [GitHub Copilot settings](https://github.com/settings/copilot)."
747+
let errorMessage = buildErrorMessage(
748+
turnId: progress.turnId,
749+
errorMessages: ["Oops, the model is not supported. Please enable it first in [GitHub Copilot settings](https://github.com/settings/copilot)."]
692750
)
693751
await memory.appendMessage(errorMessage)
694752
resetOngoingRequest()
695753
return
696754
}
697755
} else {
698756
Task {
699-
let errorMessage = ChatMessage(
700-
id: progress.turnId,
701-
chatTabID: self.chatTabInfo.id,
702-
clsTurnID: progress.turnId,
703-
role: .assistant,
704-
content: "",
705-
errorMessage: CLSError.message
706-
)
757+
let errorMessage = buildErrorMessage(turnId: progress.turnId, errorMessages: [CLSError.message])
707758
// will persist in resetOngoingRequest()
708759
await memory.appendMessage(errorMessage)
709760
resetOngoingRequest()
@@ -728,6 +779,22 @@ public final class ChatService: ChatServiceType, ObservableObject {
728779
}
729780
}
730781

782+
private func buildErrorMessage(
783+
turnId: String,
784+
errorMessages: [String] = [],
785+
panelMessages: [CopilotShowMessageParams] = []
786+
) -> ChatMessage {
787+
return .init(
788+
id: turnId,
789+
chatTabID: chatTabInfo.id,
790+
clsTurnID: turnId,
791+
role: .assistant,
792+
content: "",
793+
errorMessages: errorMessages,
794+
panelMessages: panelMessages
795+
)
796+
}
797+
731798
private func resetOngoingRequest() {
732799
activeRequestId = nil
733800
isReceivingMessage = false

Core/Sources/ChatService/Skills/CurrentEditorSkill.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import ConversationServiceProvider
22
import Foundation
33
import GitHubCopilotService
44
import JSONRPC
5+
import SystemUtils
56

67
public class CurrentEditorSkill: ConversationSkill {
78
public static let ID = "current-editor"
89
public let currentFile: FileReference
910
public var id: String {
1011
return CurrentEditorSkill.ID
1112
}
13+
public var currentFilePath: String { currentFile.url.path }
1214

1315
public init(
1416
currentFile: FileReference
@@ -20,6 +22,17 @@ public class CurrentEditorSkill: ConversationSkill {
2022
return params.skillId == self.id
2123
}
2224

25+
public static let readabilityErrorMessageProvider: FileUtils.ReadabilityErrorMessageProvider = { status in
26+
switch status {
27+
case .readable:
28+
return nil
29+
case .notFound:
30+
return "Copilot can’t find the current file, so it's not included."
31+
case .permissionDenied:
32+
return "Copilot can't access the current file. Enable \"Files & Folders\" access in [System Settings](x-apple.systempreferences:com.apple.preference.security?Privacy_FilesAndFolders)."
33+
}
34+
}
35+
2336
public func resolveSkill(request: ConversationContextRequest, completion: JSONRPCResponseHandler){
2437
let uri: String? = self.currentFile.url.absoluteString
2538
completion(

Core/Sources/ConversationTab/Chat.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public struct DisplayedChatMessage: Equatable {
2424
public var references: [ConversationReference] = []
2525
public var followUp: ConversationFollowUp? = nil
2626
public var suggestedTitle: String? = nil
27-
public var errorMessage: String? = nil
27+
public var errorMessages: [String] = []
2828
public var steps: [ConversationProgressStep] = []
2929
public var editAgentRounds: [AgentRound] = []
3030
public var panelMessages: [CopilotShowMessageParams] = []
@@ -36,7 +36,7 @@ public struct DisplayedChatMessage: Equatable {
3636
references: [ConversationReference] = [],
3737
followUp: ConversationFollowUp? = nil,
3838
suggestedTitle: String? = nil,
39-
errorMessage: String? = nil,
39+
errorMessages: [String] = [],
4040
steps: [ConversationProgressStep] = [],
4141
editAgentRounds: [AgentRound] = [],
4242
panelMessages: [CopilotShowMessageParams] = []
@@ -47,7 +47,7 @@ public struct DisplayedChatMessage: Equatable {
4747
self.references = references
4848
self.followUp = followUp
4949
self.suggestedTitle = suggestedTitle
50-
self.errorMessage = errorMessage
50+
self.errorMessages = errorMessages
5151
self.steps = steps
5252
self.editAgentRounds = editAgentRounds
5353
self.panelMessages = panelMessages
@@ -371,7 +371,7 @@ struct Chat {
371371
},
372372
followUp: message.followUp,
373373
suggestedTitle: message.suggestedTitle,
374-
errorMessage: message.errorMessage,
374+
errorMessages: message.errorMessages,
375375
steps: message.steps,
376376
editAgentRounds: message.editAgentRounds,
377377
panelMessages: message.panelMessages

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