Skip to content

Commit b2189de

Browse files
committed
Pre-release 0.33.114
1 parent 8e83ed9 commit b2189de

File tree

9 files changed

+119
-48
lines changed

9 files changed

+119
-48
lines changed

Core/Sources/ConversationTab/Chat.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Preferences
66
import Terminal
77
import ConversationServiceProvider
88
import Persist
9+
import GitHubCopilotService
910

1011
public struct DisplayedChatMessage: Equatable {
1112
public enum Role: Equatable {
@@ -142,7 +143,7 @@ struct Chat {
142143
state.typedMessage = ""
143144

144145
let selectedFiles = state.selectedFiles
145-
let selectedModelFamily = AppState.shared.getSelectedModelFamily()
146+
let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatLLM()?.modelFamily
146147
return .run { _ in
147148
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily)
148149
}.cancellable(id: CancelID.sendMessage(self.id))
@@ -152,7 +153,7 @@ struct Chat {
152153
let skillSet = state.buildSkillSet()
153154

154155
let selectedFiles = state.selectedFiles
155-
let selectedModelFamily = AppState.shared.getSelectedModelFamily()
156+
let selectedModelFamily = AppState.shared.getSelectedModelFamily() ?? CopilotModelManager.getDefaultChatLLM()?.modelFamily
156157

157158
return .run { _ in
158159
try await service.send(id, content: message, skillSet: skillSet, references: selectedFiles, model: selectedModelFamily)

Core/Sources/ConversationTab/ModelPicker.swift

+73-32
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ChatService
33
import Persist
44
import ComposableArchitecture
55
import GitHubCopilotService
6+
import Combine
67

78
public let SELECTED_LLM_KEY = "selectedLLM"
89

@@ -28,6 +29,28 @@ extension AppState {
2829
}
2930
}
3031

32+
class CopilotModelManagerObservable: ObservableObject {
33+
static let shared = CopilotModelManagerObservable()
34+
35+
@Published var availableChatModels: [LLMModel] = []
36+
@Published var defaultModel: LLMModel = .init(modelName: "", modelFamily: "")
37+
private var cancellables = Set<AnyCancellable>()
38+
39+
private init() {
40+
// Initial load
41+
availableChatModels = CopilotModelManager.getAvailableChatLLMs()
42+
43+
// Setup notification to update when models change
44+
NotificationCenter.default.publisher(for: .gitHubCopilotModelsDidChange)
45+
.receive(on: DispatchQueue.main)
46+
.sink { [weak self] _ in
47+
self?.availableChatModels = CopilotModelManager.getAvailableChatLLMs()
48+
self?.defaultModel = CopilotModelManager.getDefaultChatModel()
49+
}
50+
.store(in: &cancellables)
51+
}
52+
}
53+
3154
extension CopilotModelManager {
3255
static func getAvailableChatLLMs() -> [LLMModel] {
3356
let LLMs = CopilotModelManager.getAvailableLLMs()
@@ -37,26 +60,40 @@ extension CopilotModelManager {
3760
LLMModel(modelName: $0.modelName, modelFamily: $0.modelFamily)
3861
}
3962
}
63+
64+
static func getDefaultChatModel() -> LLMModel {
65+
let defaultModel = CopilotModelManager.getDefaultChatLLM()
66+
if let defaultModel = defaultModel {
67+
return LLMModel(modelName: defaultModel.modelName, modelFamily: defaultModel.modelFamily)
68+
}
69+
// Fallback to a hardcoded default if no model has isChatDefault = true
70+
return LLMModel(modelName: "GPT-4.1 (Preview)", modelFamily: "gpt-4.1")
71+
}
4072
}
4173

4274
struct LLMModel: Codable, Hashable {
4375
let modelName: String
4476
let modelFamily: String
4577
}
4678

47-
let defaultModel = LLMModel(modelName: "GPT-4o", modelFamily: "gpt-4o")
4879
struct ModelPicker: View {
49-
@State private var selectedModel = defaultModel.modelName
80+
@State private var selectedModel = ""
5081
@State private var isHovered = false
5182
@State private var isPressed = false
83+
@ObservedObject private var modelManager = CopilotModelManagerObservable.shared
5284
static var lastRefreshModelsTime: Date = .init(timeIntervalSince1970: 0)
5385

5486
init() {
55-
self.updateCurrentModel()
87+
let initialModel = AppState.shared.getSelectedModelName() ?? CopilotModelManager.getDefaultChatModel().modelName
88+
self._selectedModel = State(initialValue: initialModel)
5689
}
5790

5891
var models: [LLMModel] {
59-
CopilotModelManager.getAvailableChatLLMs()
92+
modelManager.availableChatModels
93+
}
94+
95+
var defaultModel: LLMModel {
96+
modelManager.defaultModel
6097
}
6198

6299
func updateCurrentModel() {
@@ -65,44 +102,48 @@ struct ModelPicker: View {
65102

66103
var body: some View {
67104
WithPerceptionTracking {
68-
Menu(selectedModel) {
69-
if models.isEmpty {
70-
Button {
71-
// No action needed
72-
} label: {
73-
Text("Loading...")
74-
}
75-
} else {
76-
ForEach(models, id: \.self) { option in
77-
Button {
78-
selectedModel = option.modelName
79-
AppState.shared.setSelectedModel(option)
80-
} label: {
81-
if selectedModel == option.modelName {
82-
Text("\(option.modelName)")
83-
} else {
84-
Text(" \(option.modelName)")
105+
Group {
106+
if !models.isEmpty && !selectedModel.isEmpty {
107+
Menu(selectedModel) {
108+
ForEach(models, id: \.self) { option in
109+
Button {
110+
selectedModel = option.modelName
111+
AppState.shared.setSelectedModel(option)
112+
} label: {
113+
if selectedModel == option.modelName {
114+
Text("\(option.modelName)")
115+
} else {
116+
Text(" \(option.modelName)")
117+
}
85118
}
86119
}
87120
}
121+
.menuStyle(BorderlessButtonMenuStyle())
122+
.frame(maxWidth: labelWidth())
123+
.padding(4)
124+
.background(
125+
RoundedRectangle(cornerRadius: 5)
126+
.fill(isHovered ? Color.gray.opacity(0.1) : Color.clear)
127+
)
128+
.onHover { hovering in
129+
isHovered = hovering
130+
}
131+
} else {
132+
EmptyView()
88133
}
89134
}
90-
.menuStyle(BorderlessButtonMenuStyle())
91-
.frame(maxWidth: labelWidth())
92-
.padding(4)
93-
.background(
94-
RoundedRectangle(cornerRadius: 5)
95-
.fill(isHovered ? Color.gray.opacity(0.1) : Color.clear)
96-
)
97-
.onHover { hovering in
98-
isHovered = hovering
99-
}
100135
.onAppear() {
101-
updateCurrentModel()
102136
Task {
103137
await refreshModels()
138+
updateCurrentModel()
104139
}
105140
}
141+
.onChange(of: defaultModel) { _ in
142+
updateCurrentModel()
143+
}
144+
.onChange(of: models) { _ in
145+
updateCurrentModel()
146+
}
106147
.help("Pick Model")
107148
}
108149
}

ExtensionService/AppDelegate.swift

+1-3
Original file line numberDiff line numberDiff line change
@@ -258,10 +258,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate {
258258

259259
func forceAuthStatusCheck() async {
260260
do {
261-
let service = try GitHubCopilotService()
261+
let service = try await GitHubCopilotViewModel.shared.getGitHubCopilotAuthService()
262262
_ = try await service.checkStatus()
263-
try await service.shutdown()
264-
try await service.exit()
265263
} catch {
266264
Logger.service.error("Failed to read auth status: \(error)")
267265
}

Server/package-lock.json

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
"description": "Package for downloading @github/copilot-language-server",
55
"private": true,
66
"dependencies": {
7-
"@github/copilot-language-server": "^1.298.0"
7+
"@github/copilot-language-server": "^1.310.0"
88
}
99
}

Tool/Sources/ConversationServiceProvider/LSPTypes.swift

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public struct ChatTemplate: Codable, Equatable {
1717
public enum PromptTemplateScope: String, Codable, Equatable {
1818
case chatPanel = "chat-panel"
1919
case editPanel = "edit-panel"
20+
case agentPanel = "agent-panel"
2021
case editor = "editor"
2122
case inline = "inline"
2223
case completion = "completion"
@@ -37,13 +38,30 @@ public struct CopilotModel: Codable, Equatable {
3738
public let modelPolicy: CopilotModelPolicy?
3839
public let scopes: [PromptTemplateScope]
3940
public let preview: Bool
41+
public let isChatDefault: Bool
42+
public let isChatFallback: Bool
43+
public let capabilities: CopilotModelCapabilities
44+
public let billing: CopilotModelBilling?
4045
}
4146

4247
public struct CopilotModelPolicy: Codable, Equatable {
4348
public let state: String
4449
public let terms: String
4550
}
4651

52+
public struct CopilotModelCapabilities: Codable, Equatable {
53+
public let supports: CopilotModelCapabilitiesSupports
54+
}
55+
56+
public struct CopilotModelCapabilitiesSupports: Codable, Equatable {
57+
public let vision: Bool
58+
}
59+
60+
public struct CopilotModelBilling: Codable, Equatable {
61+
public let isPremium: Bool
62+
public let multiplier: Float
63+
}
64+
4765
// MARK: Conversation Agents
4866
public struct ChatAgent: Codable, Equatable {
4967
public let slug: String

Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift

+5-5
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public final class GitHubCopilotExtension: BuiltinExtension {
6464
guard let service = await serviceLocator.getService(from: workspace) else { return }
6565
try await service.notifyOpenTextDocument(fileURL: documentURL, content: content)
6666
} catch {
67-
Logger.gitHubCopilot.error(error.localizedDescription)
67+
Logger.gitHubCopilot.info(error.localizedDescription)
6868
}
6969
}
7070
}
@@ -76,7 +76,7 @@ public final class GitHubCopilotExtension: BuiltinExtension {
7676
guard let service = await serviceLocator.getService(from: workspace) else { return }
7777
try await service.notifySaveTextDocument(fileURL: documentURL)
7878
} catch {
79-
Logger.gitHubCopilot.error(error.localizedDescription)
79+
Logger.gitHubCopilot.info(error.localizedDescription)
8080
}
8181
}
8282
}
@@ -88,7 +88,7 @@ public final class GitHubCopilotExtension: BuiltinExtension {
8888
guard let service = await serviceLocator.getService(from: workspace) else { return }
8989
try await service.notifyCloseTextDocument(fileURL: documentURL)
9090
} catch {
91-
Logger.gitHubCopilot.error(error.localizedDescription)
91+
Logger.gitHubCopilot.info(error.localizedDescription)
9292
}
9393
}
9494
}
@@ -122,10 +122,10 @@ public final class GitHubCopilotExtension: BuiltinExtension {
122122
// Reopen document if it's not found in the language server
123123
self.workspace(workspace, didOpenDocumentAt: documentURL)
124124
default:
125-
Logger.gitHubCopilot.error(error.localizedDescription)
125+
Logger.gitHubCopilot.info(error.localizedDescription)
126126
}
127127
} catch {
128-
Logger.gitHubCopilot.error(error.localizedDescription)
128+
Logger.gitHubCopilot.info(error.localizedDescription)
129129
}
130130
}
131131
}
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
import ConversationServiceProvider
2+
import Foundation
3+
4+
public extension Notification.Name {
5+
static let gitHubCopilotModelsDidChange = Notification
6+
.Name("com.github.CopilotForXcode.CopilotModelsDidChange")
7+
}
28

39
public class CopilotModelManager {
410
private static var availableLLMs: [CopilotModel] = []
511

612
public static func updateLLMs(_ models: [CopilotModel]) {
7-
availableLLMs = models
13+
availableLLMs = models.sorted(by: { $0.modelName.lowercased() < $1.modelName.lowercased()})
14+
NotificationCenter.default.post(name: .gitHubCopilotModelsDidChange, object: nil)
815
}
916

1017
public static func getAvailableLLMs() -> [CopilotModel] {
1118
return availableLLMs
1219
}
1320

21+
public static func getDefaultChatLLM() -> CopilotModel? {
22+
return availableLLMs.first(where: { $0.isChatDefault })
23+
}
24+
1425
public static func hasLLMs() -> Bool {
1526
return !availableLLMs.isEmpty
1627
}
1728

1829
public static func clearLLMs() {
1930
availableLLMs = []
31+
NotificationCenter.default.post(name: .gitHubCopilotModelsDidChange, object: nil)
2032
}
2133
}

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift

+1
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ public final class GitHubCopilotService:
739739
if status.status == .ok || status.status == .maybeOk {
740740
await Status.shared.updateAuthStatus(.loggedIn, username: status.user)
741741
if !CopilotModelManager.hasLLMs() {
742+
Logger.gitHubCopilot.info("No models found, fetching models...")
742743
let models = try? await models()
743744
if let models = models, !models.isEmpty {
744745
CopilotModelManager.updateLLMs(models)

0 commit comments

Comments
 (0)