Skip to content

Commit 5f07431

Browse files
committed
Support embedding with GitHub Copilot
1 parent 7c2c89b commit 5f07431

File tree

7 files changed

+149
-0
lines changed

7 files changed

+149
-0
lines changed

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelEdit.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ struct EmbeddingModelEdit {
5050
case openAI
5151
case azureOpenAI
5252
case ollama
53+
case gitHubCopilot
5354
case openAICompatible
5455
case mistralOpenAICompatible
5556
case voyageAIOpenAICompatible
@@ -64,6 +65,8 @@ struct EmbeddingModelEdit {
6465
self = .ollama
6566
case .openAICompatible:
6667
self = .openAICompatible
68+
case .gitHubCopilot:
69+
self = .gitHubCopilot
6770
}
6871
}
6972
}
@@ -189,6 +192,8 @@ struct EmbeddingModelEdit {
189192
state.format = .ollama
190193
case .openAICompatible:
191194
state.format = .openAICompatible
195+
case .gitHubCopilot:
196+
state.format = .gitHubCopilot
192197
case .mistralOpenAICompatible:
193198
state.format = .openAICompatible
194199
state.baseURLSelection.baseURL = "https://api.mistral.ai"

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelEditView.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ struct EmbeddingModelEditView: View {
2424
OpenAICompatibleForm(store: store)
2525
case .ollama:
2626
OllamaForm(store: store)
27+
case .gitHubCopilot:
28+
GitHubCopilotForm(store: store)
2729
}
2830
}
2931
.padding()
@@ -103,6 +105,8 @@ struct EmbeddingModelEditView: View {
103105
Text("Mistral (OpenAI Compatible)")
104106
case .voyageAIOpenAICompatible:
105107
Text("Voyage (OpenAI Compatible)")
108+
case .gitHubCopilot:
109+
Text("GitHub Copilot")
106110
}
107111
}
108112
},
@@ -383,6 +387,53 @@ struct EmbeddingModelEditView: View {
383387
}
384388
}
385389
}
390+
391+
struct GitHubCopilotForm: View {
392+
@Perception.Bindable var store: StoreOf<EmbeddingModelEdit>
393+
@State var isEditingCustomHeader = false
394+
395+
var body: some View {
396+
WithPerceptionTracking {
397+
TextField("Model Name", text: $store.modelName)
398+
.overlay(alignment: .trailing) {
399+
Picker(
400+
"",
401+
selection: $store.modelName,
402+
content: {
403+
if OpenAIEmbeddingModel(rawValue: store.modelName) == nil {
404+
Text("Custom Model").tag(store.modelName)
405+
}
406+
ForEach(OpenAIEmbeddingModel.allCases, id: \.self) { model in
407+
Text(model.rawValue).tag(model.rawValue)
408+
}
409+
}
410+
)
411+
.frame(width: 20)
412+
}
413+
414+
MaxTokensTextField(store: store)
415+
DimensionsTextField(store: store)
416+
417+
Button("Custom Headers") {
418+
isEditingCustomHeader.toggle()
419+
}
420+
421+
VStack(alignment: .leading, spacing: 8) {
422+
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
423+
" Please login in the GitHub Copilot settings to use the model."
424+
)
425+
426+
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
427+
" This will call the APIs directly, which may not be allowed by GitHub. But it's used in other popular apps like Zed."
428+
)
429+
}
430+
.dynamicHeightTextInFormWorkaround()
431+
.padding(.vertical)
432+
}.sheet(isPresented: $isEditingCustomHeader) {
433+
CustomHeaderSettingsView(headers: $store.customHeaders)
434+
}
435+
}
436+
}
386437
}
387438

388439
class EmbeddingModelManagementView_Editing_Previews: PreviewProvider {

Core/Sources/HostApp/AccountSettings/EmbeddingModelManagement/EmbeddingModelManagement.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extension EmbeddingModel: ManageableAIModel {
1111
case .azureOpenAI: return "Azure OpenAI"
1212
case .openAICompatible: return "OpenAI Compatible"
1313
case .ollama: return "Ollama"
14+
case .gitHubCopilot: return "GitHub Copilot"
1415
}
1516
}
1617

Tool/Sources/AIModel/EmbeddingModel.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct EmbeddingModel: Codable, Equatable, Identifiable {
2121
case azureOpenAI
2222
case openAICompatible
2323
case ollama
24+
case gitHubCopilot
2425
}
2526

2627
public struct Info: Codable, Equatable {
@@ -92,6 +93,8 @@ public struct EmbeddingModel: Codable, Equatable, Identifiable {
9293
let baseURL = info.baseURL
9394
if baseURL.isEmpty { return "http://localhost:11434/api/embeddings" }
9495
return "\(baseURL)/api/embeddings"
96+
case .gitHubCopilot:
97+
return "https://api.githubcopilot.com/embeddings"
9598
}
9699
}
97100
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import AIModel
2+
import AsyncAlgorithms
3+
import ChatBasic
4+
import Foundation
5+
import GitHubCopilotService
6+
import Logger
7+
import Preferences
8+
9+
/// Looks like it's used in many other popular repositories so maybe it's safe.
10+
actor GitHubCopilotEmbeddingService: EmbeddingAPI {
11+
let chatModel: EmbeddingModel
12+
13+
init(model: EmbeddingModel) {
14+
var model = model
15+
model.format = .openAICompatible
16+
chatModel = model
17+
}
18+
19+
func embed(text: String) async throws -> EmbeddingResponse {
20+
let service = try await buildService()
21+
return try await service.embed(text: text)
22+
}
23+
24+
func embed(texts: [String]) async throws -> EmbeddingResponse {
25+
let service = try await buildService()
26+
return try await service.embed(texts: texts)
27+
}
28+
29+
func embed(tokens: [[Int]]) async throws -> EmbeddingResponse {
30+
let service = try await buildService()
31+
return try await service.embed(tokens: tokens)
32+
}
33+
34+
private func buildService() async throws -> OpenAIEmbeddingService {
35+
let token = try await GitHubCopilotExtension.fetchToken()
36+
37+
return OpenAIEmbeddingService(
38+
apiKey: token.token,
39+
model: chatModel,
40+
endpoint: token.endpoints.api + "/embeddings"
41+
) { request in
42+
43+
// POST /chat/completions HTTP/2
44+
// :authority: api.individual.githubcopilot.com
45+
// authorization: Bearer *
46+
// x-request-id: *
47+
// openai-organization: github-copilot
48+
// vscode-sessionid: *
49+
// vscode-machineid: *
50+
// editor-version: vscode/1.89.1
51+
// editor-plugin-version: Copilot for Xcode/0.35.5
52+
// copilot-language-server-version: 1.236.0
53+
// x-github-api-version: 2023-07-07
54+
// openai-intent: conversation-panel
55+
// content-type: application/json
56+
// user-agent: GithubCopilot/1.236.0
57+
// content-length: 9061
58+
// accept: */*
59+
// accept-encoding: gzip,deflate,br
60+
61+
request.setValue(
62+
"Copilot for Xcode/\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown")",
63+
forHTTPHeaderField: "Editor-Version"
64+
)
65+
request.setValue("Bearer \(token.token)", forHTTPHeaderField: "Authorization")
66+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
67+
request.setValue("vscode-chat", forHTTPHeaderField: "Copilot-Integration-Id")
68+
request.setValue("2023-07-07", forHTTPHeaderField: "X-Github-Api-Version")
69+
}
70+
}
71+
}
72+

Tool/Sources/OpenAIService/APIs/OpenAIEmbeddingService.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
1616
let apiKey: String
1717
let model: EmbeddingModel
1818
let endpoint: String
19+
var requestModifier: ((inout URLRequest) -> Void)? = nil
1920

2021
public func embed(text: String) async throws -> EmbeddingResponse {
2122
return try await embed(texts: [text])
@@ -42,6 +43,7 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
4243
Self.setupAppInformation(&request)
4344
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
4445
await Self.setupExtraHeaderFields(&request, model: model, apiKey: apiKey)
46+
requestModifier?(&request)
4547

4648
let (result, response) = try await URLSession.shared.data(for: request)
4749
guard let response = response as? HTTPURLResponse else {
@@ -82,6 +84,7 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
8284
Self.setupAppInformation(&request)
8385
Self.setupAPIKey(&request, model: model, apiKey: apiKey)
8486
await Self.setupExtraHeaderFields(&request, model: model, apiKey: apiKey)
87+
requestModifier?(&request)
8588

8689
let (result, response) = try await URLSession.shared.data(for: request)
8790
guard let response = response as? HTTPURLResponse else {
@@ -136,6 +139,8 @@ struct OpenAIEmbeddingService: EmbeddingAPI {
136139
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
137140
case .azureOpenAI:
138141
request.setValue(apiKey, forHTTPHeaderField: "api-key")
142+
case .gitHubCopilot:
143+
break
139144
case .ollama:
140145
assertionFailure("Unsupported")
141146
}

Tool/Sources/OpenAIService/EmbeddingService.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public struct EmbeddingService {
2727
model: model,
2828
endpoint: configuration.endpoint
2929
).embed(text: text)
30+
case .gitHubCopilot:
31+
embeddingResponse = try await GitHubCopilotEmbeddingService(
32+
model: model
33+
).embed(text: text)
3034
}
3135

3236
#if DEBUG
@@ -59,6 +63,10 @@ public struct EmbeddingService {
5963
model: model,
6064
endpoint: configuration.endpoint
6165
).embed(texts: text)
66+
case .gitHubCopilot:
67+
embeddingResponse = try await GitHubCopilotEmbeddingService(
68+
model: model
69+
).embed(texts: text)
6270
}
6371

6472
#if DEBUG
@@ -91,6 +99,10 @@ public struct EmbeddingService {
9199
model: model,
92100
endpoint: configuration.endpoint
93101
).embed(tokens: tokens)
102+
case .gitHubCopilot:
103+
embeddingResponse = try await GitHubCopilotEmbeddingService(
104+
model: model
105+
).embed(tokens: tokens)
94106
}
95107

96108
#if DEBUG

0 commit comments

Comments
 (0)