Skip to content

Commit 1339ef7

Browse files
committed
Pre-release 0.41.135
1 parent 3a67130 commit 1339ef7

File tree

68 files changed

+4228
-694
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+4228
-694
lines changed

BYOK.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Adding your API Keys with GitHub Copilot - Bring Your Own Key(BYOK)
2+
3+
4+
Copilot for Xcode supports **Bring Your Own Key (BYOK)** integration with multiple model providers. You can bring your own API keys to integrate with your preferred model provider, giving you full control and flexibility.
5+
6+
Supported providers include:
7+
- Anthropic
8+
- Azure
9+
- Gemini
10+
- Groq
11+
- OpenAI
12+
- OpenRouter
13+
14+
15+
## Configuration Steps
16+
17+
18+
To configure BYOK in Copilot for Xcode:
19+
20+
- Open the Copilot chat and select “Manage Models” from the Model picker.
21+
- Choose your preferred AI provider (e.g., Anthropic, OpenAI, and Azure).
22+
- Enter the required provider-specific details, such as the API key and endpoint URL (if applicable).
23+
24+
25+
| Model Provider | How to get the API Keys |
26+
|-------------------|------------------------------------------------------------------------------------------------------------|
27+
| Anthropic | Sign in to the [Anthropic Console](https://console.anthropic.com/dashboard) to generate and retrieve your API key. |
28+
| Gemini (Google) | Sign in to the [Google Cloud Console](https://aistudio.google.com/app/apikey) to generate and retrieve your API key. |
29+
| Groq | Sign in to the [Groq Console](https://console.groq.com/keys) to generate and retrieve your API key. |
30+
| OpenAI | Sign in to the [OpenAI’s Platform](https://platform.openai.com/api-keys) to generate and retrieve your API key. |
31+
| OpenRouter | Sign in to the [OpenRouter’s API Key Settings](https://openrouter.ai/settings/keys) to generate your API key. |
32+
| Azure | Sign in to the [Azure AI Foundry](https://ai.azure.com/), go to your [Deployments](https://ai.azure.com/resource/deployments/), and retrieve your API key and Endpoint after the deployment is complete. Ensure the model name you enter matches the one you deployed, as shown on the Details page.|
33+
34+
35+
- Click "Add" button to continue.
36+
- Once saved, it will list available AI models in the Models setting page. You can enable the models you intend to use with GitHub Copilot.
37+
38+
> [!NOTE]
39+
> Please keep your API key confidential and never share it publicly for safety.
40+

Copilot for Xcode/App.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
2121
case chat
2222
case settings
2323
case mcp
24+
case byok
2425
}
2526

2627
func applicationDidFinishLaunching(_ notification: Notification) {
@@ -50,6 +51,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
5051
return .settings
5152
} else if launchArgs.contains("--mcp") {
5253
return .mcp
54+
} else if launchArgs.contains("--byok") {
55+
return .byok
5356
} else {
5457
return .chat
5558
}
@@ -61,6 +64,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
6164
openSettings()
6265
case .mcp:
6366
openMCPSettings()
67+
case .byok:
68+
openBYOKSettings()
6469
case .chat:
6570
openChat()
6671
}
@@ -84,7 +89,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
8489
private func openMCPSettings() {
8590
DispatchQueue.main.async {
8691
activateAndOpenSettings()
87-
hostAppStore.send(.setActiveTab(2))
92+
hostAppStore.send(.setActiveTab(.mcp))
93+
}
94+
}
95+
96+
private func openBYOKSettings() {
97+
DispatchQueue.main.async {
98+
activateAndOpenSettings()
99+
hostAppStore.send(.setActiveTab(.byok))
88100
}
89101
}
90102

@@ -187,7 +199,18 @@ struct CopilotForXcodeApp: App {
187199
) { _ in
188200
DispatchQueue.main.async {
189201
activateAndOpenSettings()
190-
hostAppStore.send(.setActiveTab(2))
202+
hostAppStore.send(.setActiveTab(.mcp))
203+
}
204+
}
205+
206+
DistributedNotificationCenter.default().addObserver(
207+
forName: .openBYOKSettingsWindowRequest,
208+
object: nil,
209+
queue: .main
210+
) { _ in
211+
DispatchQueue.main.async {
212+
activateAndOpenSettings()
213+
hostAppStore.send(.setActiveTab(.byok))
191214
}
192215
}
193216
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0xF7",
9+
"green" : "0xF7",
10+
"red" : "0xF7"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x09",
27+
"green" : "0x09",
28+
"red" : "0x09"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0xFB",
9+
"green" : "0xFB",
10+
"red" : "0xFB"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x07",
27+
"green" : "0x07",
28+
"red" : "0x07"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0xE6",
9+
"green" : "0xE6",
10+
"red" : "0xE6"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x14",
27+
"green" : "0x14",
28+
"red" : "0x14"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"colors" : [
3+
{
4+
"color" : {
5+
"color-space" : "srgb",
6+
"components" : {
7+
"alpha" : "1.000",
8+
"blue" : "0xF2",
9+
"green" : "0xF2",
10+
"red" : "0xF2"
11+
}
12+
},
13+
"idiom" : "universal"
14+
},
15+
{
16+
"appearances" : [
17+
{
18+
"appearance" : "luminosity",
19+
"value" : "dark"
20+
}
21+
],
22+
"color" : {
23+
"color-space" : "srgb",
24+
"components" : {
25+
"alpha" : "1.000",
26+
"blue" : "0x0D",
27+
"green" : "0x0D",
28+
"red" : "0x0D"
29+
}
30+
},
31+
"idiom" : "universal"
32+
}
33+
],
34+
"info" : {
35+
"author" : "xcode",
36+
"version" : 1
37+
}
38+
}

Core/Sources/ChatService/ChatService.swift

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,19 @@ import SuggestionBasic
2121

2222
public protocol ChatServiceType {
2323
var memory: ContextAwareAutoManagedChatMemory { get set }
24-
func send(_ id: String, content: String, contentImages: [ChatCompletionContentPartImage], contentImageReferences: [ImageReference], skillSet: [ConversationSkill], references: [FileReference], model: String?, agentMode: Bool, userLanguage: String?, turnId: String?) async throws
24+
func send(
25+
_ id: String,
26+
content: String,
27+
contentImages: [ChatCompletionContentPartImage],
28+
contentImageReferences: [ImageReference],
29+
skillSet: [ConversationSkill],
30+
references: [ConversationAttachedReference],
31+
model: String?,
32+
modelProviderName: String?,
33+
agentMode: Bool,
34+
userLanguage: String?,
35+
turnId: String?
36+
) async throws
2537
func stopReceivingMessage() async
2638
func upvote(_ id: String, _ rating: ConversationRating) async
2739
func downvote(_ id: String, _ rating: ConversationRating) async
@@ -333,8 +345,9 @@ public final class ChatService: ChatServiceType, ObservableObject {
333345
contentImages: Array<ChatCompletionContentPartImage> = [],
334346
contentImageReferences: Array<ImageReference> = [],
335347
skillSet: Array<ConversationSkill>,
336-
references: Array<FileReference>,
348+
references: [ConversationAttachedReference],
337349
model: String? = nil,
350+
modelProviderName: String? = nil,
338351
agentMode: Bool = false,
339352
userLanguage: String? = nil,
340353
turnId: String? = nil
@@ -439,6 +452,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
439452
activeDoc: activeDoc,
440453
references: references,
441454
model: model,
455+
modelProviderName: modelProviderName,
442456
agentMode: agentMode,
443457
userLanguage: userLanguage,
444458
turnId: currentTurnId,
@@ -455,8 +469,9 @@ public final class ChatService: ChatServiceType, ObservableObject {
455469
content: String,
456470
contentImages: [ChatCompletionContentPartImage] = [],
457471
activeDoc: Doc?,
458-
references: [FileReference],
472+
references: [ConversationAttachedReference],
459473
model: String? = nil,
474+
modelProviderName: String? = nil,
460475
agentMode: Bool = false,
461476
userLanguage: String? = nil,
462477
turnId: String? = nil,
@@ -481,6 +496,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
481496
ignoredSkills: ignoredSkills,
482497
references: references,
483498
model: model,
499+
modelProviderName: modelProviderName,
484500
agentMode: agentMode,
485501
userLanguage: userLanguage,
486502
turnId: turnId
@@ -527,7 +543,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
527543
deleteChatMessageFromStorage(id)
528544
}
529545

530-
public func resendMessage(id: String, model: String? = nil) async throws {
546+
public func resendMessage(id: String, model: String? = nil, modelProviderName: String? = nil) async throws {
531547
if let _ = (await memory.history).first(where: { $0.id == id }),
532548
let lastUserRequest
533549
{
@@ -540,6 +556,7 @@ public final class ChatService: ChatServiceType, ObservableObject {
540556
skillSet: skillSet,
541557
references: lastUserRequest.references ?? [],
542558
model: model != nil ? model : lastUserRequest.model,
559+
modelProviderName: modelProviderName != nil ? modelProviderName : lastUserRequest.modelProviderName,
543560
agentMode: lastUserRequest.agentMode,
544561
userLanguage: lastUserRequest.userLanguage,
545562
turnId: id
@@ -1050,21 +1067,35 @@ func replaceFirstWord(in content: String, from oldWord: String, to newWord: Stri
10501067
return content
10511068
}
10521069

1053-
extension Array where Element == Reference {
1070+
extension Array where Element == FileReference {
10541071
func toConversationReferences() -> [ConversationReference] {
10551072
return self.map {
1056-
.init(uri: $0.uri, status: .included, kind: .reference($0))
1073+
.init(uri: $0.uri, status: .included, kind: .reference($0), referenceType: .file)
10571074
}
10581075
}
10591076
}
10601077

1061-
extension Array where Element == FileReference {
1078+
extension Array where Element == ConversationAttachedReference {
10621079
func toConversationReferences() -> [ConversationReference] {
10631080
return self.map {
1064-
.init(uri: $0.url.path, status: .included, kind: .fileReference($0))
1081+
switch $0 {
1082+
case .file(let fileRef):
1083+
.init(
1084+
uri: fileRef.url.path,
1085+
status: .included,
1086+
kind: .fileReference($0),
1087+
referenceType: .file)
1088+
case .directory(let directoryRef):
1089+
.init(
1090+
uri: directoryRef.url.path,
1091+
status: .included,
1092+
kind: .fileReference($0),
1093+
referenceType: .directory)
1094+
}
10651095
}
10661096
}
10671097
}
1098+
10681099
extension [ChatMessage] {
10691100
// transfer chat messages to turns
10701101
// used to restore chat history for CLS

0 commit comments

Comments
 (0)