Skip to content

Commit 415088e

Browse files
committed
Add model settings for GitHub Copilot but looks like it's not yet implemented
1 parent 233516d commit 415088e

File tree

9 files changed

+226
-27
lines changed

9 files changed

+226
-27
lines changed

Core/Sources/HostApp/AccountSettings/ChatModelManagement/ChatModelEditView.swift

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ struct ChatModelEditView: View {
4949
.controlSize(.small)
5050
}
5151
}
52-
52+
5353
CustomBodyEdit(store: store)
5454
.disabled({
5555
switch store.format {
@@ -495,7 +495,7 @@ struct ChatModelEditView: View {
495495
TextField(text: $store.ollamaKeepAlive, prompt: Text("Default Value")) {
496496
Text("Keep Alive")
497497
}
498-
498+
499499
VStack(alignment: .leading, spacing: 8) {
500500
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
501501
" For more details, please visit [https://ollama.com](https://ollama.com)."
@@ -555,22 +555,12 @@ struct ChatModelEditView: View {
555555

556556
var body: some View {
557557
WithPerceptionTracking {
558-
TextField("Model Name", text: $store.modelName)
559-
.overlay(alignment: .trailing) {
560-
Picker(
561-
"",
562-
selection: $store.modelName,
563-
content: {
564-
if AvailableGitHubCopilotModel(rawValue: store.modelName) == nil {
565-
Text("Custom Model").tag(store.modelName)
566-
}
567-
ForEach(AvailableGitHubCopilotModel.allCases, id: \.self) { model in
568-
Text(model.rawValue).tag(model.rawValue)
569-
}
570-
}
571-
)
572-
.frame(width: 20)
573-
}
558+
#warning("Todo: use the old picker and update the context window limit.")
559+
GitHubCopilotModelPicker(
560+
title: "Model Name",
561+
hasDefaultModel: false,
562+
gitHubCopilotModelId: $store.modelName
563+
)
574564

575565
MaxTokensTextField(store: store)
576566
SupportsFunctionCallingToggle(store: store)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import Dependencies
2+
import Foundation
3+
import GitHubCopilotService
4+
import Perception
5+
import SwiftUI
6+
import Toast
7+
8+
public struct GitHubCopilotModelPicker: View {
9+
@Perceptible
10+
final class ViewModel {
11+
var availableModels: [GitHubCopilotLLMModel] = []
12+
@PerceptionIgnored @Dependency(\.toast) var toast
13+
14+
init() {}
15+
16+
func appear() {
17+
reloadAvailableModels()
18+
}
19+
20+
func disappear() {}
21+
22+
func reloadAvailableModels() {
23+
Task { @MainActor in
24+
do {
25+
availableModels = try await GitHubCopilotExtension.fetchLLMModels()
26+
} catch {
27+
toast("Failed to fetch GitHub Copilot models: \(error)", .error)
28+
}
29+
}
30+
}
31+
}
32+
33+
let title: String
34+
let hasDefaultModel: Bool
35+
@Binding var gitHubCopilotModelId: String
36+
@State var viewModel: ViewModel
37+
38+
init(
39+
title: String,
40+
hasDefaultModel: Bool = true,
41+
gitHubCopilotModelId: Binding<String>
42+
) {
43+
self.title = title
44+
_gitHubCopilotModelId = gitHubCopilotModelId
45+
self.hasDefaultModel = hasDefaultModel
46+
viewModel = .init()
47+
}
48+
49+
public var body: some View {
50+
WithPerceptionTracking {
51+
TextField(title, text: $gitHubCopilotModelId)
52+
.overlay(alignment: .trailing) {
53+
Picker(
54+
"",
55+
selection: $gitHubCopilotModelId,
56+
content: {
57+
if hasDefaultModel {
58+
Text("Default").tag("")
59+
}
60+
61+
if !gitHubCopilotModelId.isEmpty,
62+
!viewModel.availableModels.contains(where: {
63+
$0.modelId == gitHubCopilotModelId
64+
})
65+
{
66+
Text(gitHubCopilotModelId).tag(gitHubCopilotModelId)
67+
}
68+
if viewModel.availableModels.isEmpty {
69+
Text({
70+
viewModel.reloadAvailableModels()
71+
return "Loading..."
72+
}()).tag("Loading...")
73+
}
74+
ForEach(viewModel.availableModels) { model in
75+
Text(model.modelId)
76+
.tag(model.modelId)
77+
}
78+
}
79+
)
80+
.frame(width: 20)
81+
}
82+
.onAppear {
83+
viewModel.appear()
84+
}
85+
.onDisappear {
86+
viewModel.disappear()
87+
}
88+
}
89+
}
90+
}
91+

Core/Sources/HostApp/AccountSettings/GitHubCopilotView.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ struct GitHubCopilotView: View {
2525
var disableGitHubCopilotSettingsAutoRefreshOnAppear
2626
@AppStorage(\.gitHubCopilotLoadKeyChainCertificates)
2727
var gitHubCopilotLoadKeyChainCertificates
28+
@AppStorage(\.gitHubCopilotModelId) var gitHubCopilotModelId
2829
init() {}
2930
}
3031

@@ -199,7 +200,7 @@ struct GitHubCopilotView: View {
199200
.foregroundColor(.secondary)
200201
.font(.callout)
201202
.dynamicHeightTextInFormWorkaround()
202-
203+
203204
Toggle(isOn: $settings.gitHubCopilotLoadKeyChainCertificates) {
204205
Text("Load certificates in keychain")
205206
}
@@ -267,6 +268,14 @@ struct GitHubCopilotView: View {
267268
Button("Refresh configurations") {
268269
refreshConfiguration()
269270
}
271+
272+
// Not available yet
273+
// Form {
274+
// GitHubCopilotModelPicker(
275+
// title: "Chat Model Name",
276+
// gitHubCopilotModelId: $settings.gitHubCopilotModelId
277+
// )
278+
// }
270279
}
271280

272281
SettingsDivider("Advanced")
@@ -349,7 +358,6 @@ struct GitHubCopilotView: View {
349358
if status != .ok, status != .notSignedIn {
350359
toast(
351360
"GitHub Copilot status is not \"ok\". Please check if you have a valid GitHub Copilot subscription.",
352-
353361
.error
354362
)
355363
}

Tool/Sources/GitHubCopilotService/GitHubCopilotExtension.swift

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,15 +222,15 @@ extension GitHubCopilotExtension {
222222
public static func fetchToken() async throws -> Token {
223223
guard let authToken = authInfo?.oauth_token
224224
else { throw GitHubCopilotError.notLoggedIn }
225-
225+
226226
let oldToken = await MainActor.run { cachedToken }
227227
if let oldToken {
228228
let expiresAt = Date(timeIntervalSince1970: TimeInterval(oldToken.expires_at))
229229
if expiresAt > Date() {
230230
return oldToken
231231
}
232232
}
233-
233+
234234
let url = URL(string: "https://api.github.com/copilot_internal/v2/token")!
235235
var request = URLRequest(url: url)
236236
request.httpMethod = "GET"
@@ -255,5 +255,64 @@ extension GitHubCopilotExtension {
255255
throw error
256256
}
257257
}
258+
259+
public static func fetchLLMModels() async throws -> [GitHubCopilotLLMModel] {
260+
let token = try await GitHubCopilotExtension.fetchToken()
261+
guard let endpoint = URL(string: token.endpoints.api + "/models") else {
262+
throw CancellationError()
263+
}
264+
var request = URLRequest(url: endpoint)
265+
request.setValue(
266+
"Copilot for Xcode/\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown")",
267+
forHTTPHeaderField: "Editor-Version"
268+
)
269+
request.setValue("Bearer \(token.token)", forHTTPHeaderField: "Authorization")
270+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
271+
request.setValue("vscode-chat", forHTTPHeaderField: "Copilot-Integration-Id")
272+
request.setValue("2023-07-07", forHTTPHeaderField: "X-Github-Api-Version")
273+
274+
let (data, response) = try await URLSession.shared.data(for: request)
275+
276+
guard let response = response as? HTTPURLResponse else {
277+
throw CancellationError()
278+
}
279+
280+
guard response.statusCode == 200 else {
281+
throw CancellationError()
282+
}
283+
284+
struct Model: Decodable {
285+
struct Limit: Decodable {
286+
var max_context_window_tokens: Int
287+
}
288+
289+
struct Capability: Decodable {
290+
var type: String?
291+
var family: String?
292+
var limit: Limit?
293+
}
294+
295+
var id: String
296+
var capabilities: Capability
297+
}
298+
299+
struct Body: Decodable {
300+
var data: [Model]
301+
}
302+
303+
let models = try JSONDecoder().decode(Body.self, from: data)
304+
.data
305+
.filter {
306+
$0.capabilities.type == "chat"
307+
}
308+
.map {
309+
GitHubCopilotLLMModel(
310+
modelId: $0.id,
311+
familyName: $0.capabilities.family ?? "",
312+
contextWindow: $0.capabilities.limit?.max_context_window_tokens ?? 0
313+
)
314+
}
315+
return models
316+
}
258317
}
259318

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotRequest.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ enum GitHubCopilotRequest {
364364
enum ConversationSource: String, Codable {
365365
case panel, inline
366366
}
367-
367+
368368
enum ConversationMode: String, Codable {
369369
case agent = "Agent"
370370
}
@@ -464,5 +464,42 @@ enum GitHubCopilotRequest {
464464
return .custom("conversation/destroy", dict)
465465
}
466466
}
467+
468+
struct CopilotModels: GitHubCopilotRequestType {
469+
typealias Response = [GitHubCopilotModel]
470+
471+
var request: ClientRequest {
472+
.custom("copilot/models", .hash([:]))
473+
}
474+
}
475+
}
476+
477+
public struct GitHubCopilotModel: Codable, Equatable {
478+
public let modelFamily: String
479+
public let modelName: String
480+
public let id: String
481+
// public let modelPolicy: CopilotModelPolicy?
482+
public let scopes: [GitHubCopilotPromptTemplateScope]
483+
public let preview: Bool
484+
public let isChatDefault: Bool
485+
public let isChatFallback: Bool
486+
// public let capabilities: CopilotModelCapabilities
487+
// public let billing: CopilotModelBilling?
488+
}
489+
490+
public struct GitHubCopilotLLMModel: Equatable, Decodable, Identifiable {
491+
public var id: String { modelId }
492+
public var modelId: String
493+
public var familyName: String
494+
public var contextWindow: Int
495+
}
496+
497+
public enum GitHubCopilotPromptTemplateScope: String, Codable, Equatable {
498+
case chatPanel = "chat-panel"
499+
case editPanel = "edit-panel"
500+
case agentPanel = "agent-panel"
501+
case editor
502+
case inline
503+
case completion
467504
}
468505

Tool/Sources/GitHubCopilotService/LanguageServer/GitHubCopilotService.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,6 @@ public class GitHubCopilotBaseService {
273273
let notifications = NotificationCenter.default
274274
.notifications(named: .gitHubCopilotShouldRefreshEditorInformation)
275275
Task { [weak self] in
276-
print(await xcodeVersion())
277276
_ = try? await server.sendRequest(
278277
GitHubCopilotRequest.SetEditorInfo(xcodeVersion: xcodeVersion() ?? "16.0")
279278
)

Tool/Sources/GitHubCopilotService/Services/GitHubCopilotChatService.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ public final class GitHubCopilotChatService: BuiltinExtensionChatServiceType {
4444
textDocument: doc,
4545
source: .panel,
4646
workspaceFolder: workspace.projectURL.path,
47+
model: {
48+
let selectedModel = UserDefaults.shared.value(for: \.gitHubCopilotModelId)
49+
if selectedModel.isEmpty {
50+
return nil
51+
}
52+
return selectedModel
53+
}(),
4754
userLanguage: {
4855
let language = UserDefaults.shared.value(for: \.chatGPTLanguage)
4956
if language.isEmpty {
@@ -220,7 +227,7 @@ extension GitHubCopilotChatService {
220227
var responseIsIncomplete: Bool?
221228
var message: String?
222229
}
223-
230+
224231
struct Annotation: Decodable {
225232
var id: Int
226233
}

Tool/Sources/OpenAIService/APIs/GitHubCopilotChatCompletionsService.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ actor GitHubCopilotChatCompletionsService: ChatCompletionsStreamAPI, ChatComplet
7979
endpoint: endpoint,
8080
requestBody: requestBody
8181
) { request in
82-
82+
8383
// POST /chat/completions HTTP/2
8484
// :authority: api.individual.githubcopilot.com
8585
// authorization: Bearer *
@@ -97,7 +97,7 @@ actor GitHubCopilotChatCompletionsService: ChatCompletionsStreamAPI, ChatComplet
9797
// content-length: 9061
9898
// accept: */*
9999
// accept-encoding: gzip,deflate,br
100-
100+
101101
request.setValue(
102102
"Copilot for Xcode/\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown")",
103103
forHTTPHeaderField: "Editor-Version"

Tool/Sources/Preferences/Keys.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ public extension UserDefaultPreferenceKeys {
195195
var gitHubCopilotPretendIDEToBeVSCode: PreferenceKey<Bool> {
196196
.init(defaultValue: false, key: "GitHubCopilotPretendIDEToBeVSCode")
197197
}
198+
199+
var gitHubCopilotModelId: PreferenceKey<String> {
200+
.init(defaultValue: "", key: "GitHubCopilotModelId")
201+
}
202+
203+
var gitHubCopilotModelFamily: PreferenceKey<String> {
204+
.init(defaultValue: "", key: "GitHubCopilotModelFamily")
205+
}
198206
}
199207

200208
// MARK: - Codeium Settings

0 commit comments

Comments
 (0)