@@ -14,6 +14,7 @@ import Logger
14
14
import Workspace
15
15
import XcodeInspector
16
16
import OrderedCollections
17
+ import SystemUtils
17
18
18
19
public protocol ChatServiceType {
19
20
var memory : ContextAwareAutoManagedChatMemory { get set }
@@ -330,22 +331,42 @@ public final class ChatService: ChatServiceType, ObservableObject {
330
331
let workDoneToken = UUID ( ) . uuidString
331
332
activeRequestId = workDoneToken
332
333
333
- let chatMessage = ChatMessage (
334
+ var chatMessage = ChatMessage (
334
335
id: id,
335
336
chatTabID: self . chatTabInfo. id,
336
337
role: . user,
337
338
content: content,
338
339
references: references. toConversationReferences ( )
339
340
)
340
341
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
341
349
// If turnId is provided, it is used to update the existing message, no need to append the user message
342
350
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
+ }
343
364
await memory. appendMessage ( chatMessage)
344
365
}
345
366
346
367
// reset file edits
347
368
self . resetFileEdits ( )
348
-
369
+
349
370
// persist
350
371
saveChatMessageToStorage ( chatMessage)
351
372
@@ -370,32 +391,68 @@ public final class ChatService: ChatServiceType, ObservableObject {
370
391
return
371
392
}
372
393
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]
374
435
let supportedSkills : [ String ] = skillSet. map { $0. id }
375
436
let ignoredSkills : [ String ] = skillCapabilities. filter {
376
437
!supportedSkills. contains ( $0)
377
438
}
378
- let currentEditorSkill = skillSet. first { $0. id == CurrentEditorSkill . ID }
379
- let activeDoc : Doc ? = ( currentEditorSkill as? CurrentEditorSkill ) . map { Doc ( uri: $0. currentFile. url. absoluteString) }
380
439
381
440
/// replace the `@workspace` to `@project`
382
441
let newContent = replaceFirstWord ( in: content, from: " @workspace " , to: " @project " )
383
442
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
395
455
)
396
- self . lastUserRequest = request
397
- self . skillSet = skillSet
398
- try await send ( request)
399
456
}
400
457
401
458
public func sendAndWait( _ id: String , content: String ) async throws -> String {
@@ -444,20 +501,16 @@ public final class ChatService: ChatServiceType, ObservableObject {
444
501
{
445
502
// TODO: clean up contents for resend message
446
503
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
+ )
461
514
}
462
515
}
463
516
@@ -569,6 +622,19 @@ public final class ChatService: ChatServiceType, ObservableObject {
569
622
570
623
Task {
571
624
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
+
572
638
lastUserMessage. clsTurnID = progress. turnId
573
639
saveChatMessageToStorage ( lastUserMessage)
574
640
}
@@ -653,14 +719,9 @@ public final class ChatService: ChatServiceType, ObservableObject {
653
719
Task {
654
720
await Status . shared
655
721
. 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) ] )
664
725
// will persist in resetongoingRequest()
665
726
await memory. appendMessage ( errorMessage)
666
727
@@ -683,27 +744,17 @@ public final class ChatService: ChatServiceType, ObservableObject {
683
744
}
684
745
} else if CLSError . code == 400 && CLSError . message. contains ( " model is not supported " ) {
685
746
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). " ]
692
750
)
693
751
await memory. appendMessage ( errorMessage)
694
752
resetOngoingRequest ( )
695
753
return
696
754
}
697
755
} else {
698
756
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] )
707
758
// will persist in resetOngoingRequest()
708
759
await memory. appendMessage ( errorMessage)
709
760
resetOngoingRequest ( )
@@ -728,6 +779,22 @@ public final class ChatService: ChatServiceType, ObservableObject {
728
779
}
729
780
}
730
781
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
+
731
798
private func resetOngoingRequest( ) {
732
799
activeRequestId = nil
733
800
isReceivingMessage = false
0 commit comments