Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8c1a9b1
add api token picker to ChatModelEditView
Feb 20, 2025
5d79140
add Bearer header field
Feb 20, 2025
6a90966
optionally add api key to header
Feb 20, 2025
2bed2b6
Merge pull request #625 from tmpit/ollama_api_token
intitni Feb 21, 2025
94bfdd5
Add API key field for Ollama embedding API
intitni Feb 21, 2025
be47c9a
Add custom header filed for Ollama APIs
intitni Feb 21, 2025
f1cb526
Update model format picker styles
intitni Feb 21, 2025
4298221
Add id to references
intitni Feb 23, 2025
8a0b121
Support reasoning content from DeepSeek
intitni Feb 23, 2025
316d91d
Update
intitni Feb 23, 2025
a7a34fa
Fix embedding empty array
intitni Feb 23, 2025
88ce86f
Update UI
intitni Feb 23, 2025
e4b2336
Add request modifier
intitni Feb 24, 2025
17514e3
Add GitHub Copilot auth token fetcher
intitni Feb 24, 2025
2710000
Update name
intitni Feb 24, 2025
d8f95d2
Add method to get GitHubCopilot tokens
intitni Feb 24, 2025
e6dee6b
Add header value parser
intitni Feb 24, 2025
dca1c0d
Support joining split documents
intitni Feb 24, 2025
50270bf
Add new models
intitni Feb 24, 2025
5e8d1ec
Adjust debug UI z index
intitni Feb 24, 2025
7c2c89b
Support github copilot chat direct call
intitni Feb 24, 2025
5f07431
Support embedding with GitHub Copilot
intitni Feb 24, 2025
dc2d2fc
Fix selected content
intitni Feb 24, 2025
0051a26
Add option requiresBeginWithUserMessage
intitni Feb 24, 2025
efeafb0
Fix selected content
intitni Feb 24, 2025
addd0d5
Bump version
intitni Feb 24, 2025
730669f
Fix document merging
intitni Feb 25, 2025
1acd818
Enable NSAllowsArbitraryLoads
intitni Feb 25, 2025
1b80c9d
Fix configuration override
intitni Feb 25, 2025
8110127
Fix header parsing
intitni Feb 25, 2025
b70c21e
Merge branch 'hotfix/0.35.5'
intitni Feb 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Copilot for Xcode.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Copilot-for-Xcode-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
Expand Down Expand Up @@ -991,6 +992,7 @@
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "Copilot-for-Xcode-Info.plist";
INFOPLIST_KEY_CFBundleDisplayName = "";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools";
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
5 changes: 5 additions & 0 deletions Copilot-for-Xcode-Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
<string>$(EXTENSION_BUNDLE_NAME)</string>
<key>HOST_APP_NAME</key>
<string>$(HOST_APP_NAME)</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>SUEnableJavaScript</key>
<string>YES</string>
<key>SUFeedURL</key>
Expand Down
2 changes: 1 addition & 1 deletion Core/Sources/ChatGPTChatTab/ChatContextMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ struct ChatContextMenu: View {
var chatModel: some View {
let allModels = chatModels + [.init(
id: "com.github.copilot",
name: "GitHub Copilot as chat model",
name: "GitHub Copilot Language Server",
format: .openAI,
info: .init()
)]
Expand Down
2 changes: 2 additions & 0 deletions Core/Sources/ChatService/AllPlugins.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ final class LegacyChatPluginWrapper<Plugin: ChatPlugin>: LegacyChatPlugin {
break
case .startNewMessage:
break
case .reasoning:
break
}

await chatGPTService.memory.mutateHistory { history in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct ChatModelEdit {
var openAIProjectID: String = ""
var customHeaders: [ChatModel.Info.CustomHeaderInfo.HeaderField] = []
var openAICompatibleSupportsMultipartMessageContent = true
var requiresBeginWithUserMessage = false
}

enum Action: Equatable, BindableAction {
Expand All @@ -45,10 +46,44 @@ struct ChatModelEdit {
case testSucceeded(String)
case testFailed(String)
case checkSuggestedMaxTokens
case selectModelFormat(ModelFormat)
case apiKeySelection(APIKeySelection.Action)
case baseURLSelection(BaseURLSelection.Action)
}

enum ModelFormat: CaseIterable {
case openAI
case azureOpenAI
case googleAI
case ollama
case claude
case gitHubCopilot
case openAICompatible
case deepSeekOpenAICompatible
case openRouterOpenAICompatible
case grokOpenAICompatible
case mistralOpenAICompatible

init(_ format: ChatModel.Format) {
switch format {
case .openAI:
self = .openAI
case .azureOpenAI:
self = .azureOpenAI
case .googleAI:
self = .googleAI
case .ollama:
self = .ollama
case .claude:
self = .claude
case .openAICompatible:
self = .openAICompatible
case .gitHubCopilot:
self = .gitHubCopilot
}
}
}

var toast: (String, ToastType) -> Void {
@Dependency(\.namespacedToast) var toast
return {
Expand Down Expand Up @@ -164,11 +199,53 @@ struct ChatModelEdit {
state.suggestedMaxTokens = nil
}
return .none
case .gitHubCopilot:
if let knownModel = AvailableGitHubCopilotModel(rawValue: state.modelName) {
state.suggestedMaxTokens = knownModel.contextWindow
} else {
state.suggestedMaxTokens = nil
}
return .none
default:
state.suggestedMaxTokens = nil
return .none
}

case let .selectModelFormat(format):
switch format {
case .openAI:
state.format = .openAI
case .azureOpenAI:
state.format = .azureOpenAI
case .googleAI:
state.format = .googleAI
case .ollama:
state.format = .ollama
case .claude:
state.format = .claude
case .gitHubCopilot:
state.format = .gitHubCopilot
case .openAICompatible:
state.format = .openAICompatible
case .deepSeekOpenAICompatible:
state.format = .openAICompatible
state.baseURLSelection.baseURL = "https://api.deepseek.com"
state.baseURLSelection.isFullURL = false
case .openRouterOpenAICompatible:
state.format = .openAICompatible
state.baseURLSelection.baseURL = "https://openrouter.ai"
state.baseURLSelection.isFullURL = false
case .grokOpenAICompatible:
state.format = .openAICompatible
state.baseURLSelection.baseURL = "https://api.x.ai"
state.baseURLSelection.isFullURL = false
case .mistralOpenAICompatible:
state.format = .openAICompatible
state.baseURLSelection.baseURL = "https://api.mistral.ai"
state.baseURLSelection.isFullURL = false
}
return .none

case .apiKeySelection:
return .none

Expand Down Expand Up @@ -208,7 +285,7 @@ extension ChatModel {
switch state.format {
case .googleAI, .ollama, .claude:
return false
case .azureOpenAI, .openAI, .openAICompatible:
case .azureOpenAI, .openAI, .openAICompatible, .gitHubCopilot:
return state.supportsFunctionCalling
}
}(),
Expand All @@ -222,7 +299,8 @@ extension ChatModel {
openAICompatibleInfo: .init(
enforceMessageOrder: state.enforceMessageOrder,
supportsMultipartMessageContent: state
.openAICompatibleSupportsMultipartMessageContent
.openAICompatibleSupportsMultipartMessageContent,
requiresBeginWithUserMessage: state.requiresBeginWithUserMessage
),
customHeaderInfo: .init(headers: state.customHeaders)
)
Expand All @@ -249,7 +327,8 @@ extension ChatModel {
openAIProjectID: info.openAIInfo.projectID,
customHeaders: info.customHeaderInfo.headers,
openAICompatibleSupportsMultipartMessageContent: info.openAICompatibleInfo
.supportsMultipartMessageContent
.supportsMultipartMessageContent,
requiresBeginWithUserMessage: info.openAICompatibleInfo.requiresBeginWithUserMessage
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ struct ChatModelEditView: View {
OllamaForm(store: store)
case .claude:
ClaudeForm(store: store)
case .gitHubCopilot:
GitHubCopilotForm(store: store)
}
}
.padding()
Expand Down Expand Up @@ -86,31 +88,44 @@ struct ChatModelEditView: View {
var body: some View {
WithPerceptionTracking {
Picker(
selection: $store.format,
selection: Binding(
get: { .init(store.format) },
set: { store.send(.selectModelFormat($0)) }
),
content: {
ForEach(
ChatModel.Format.allCases,
id: \.rawValue
ChatModelEdit.ModelFormat.allCases,
id: \.self
) { format in
switch format {
case .openAI:
Text("OpenAI").tag(format)
Text("OpenAI")
case .azureOpenAI:
Text("Azure OpenAI").tag(format)
Text("Azure OpenAI")
case .openAICompatible:
Text("OpenAI Compatible").tag(format)
Text("OpenAI Compatible")
case .googleAI:
Text("Google Generative AI").tag(format)
Text("Google AI")
case .ollama:
Text("Ollama").tag(format)
Text("Ollama")
case .claude:
Text("Claude").tag(format)
Text("Claude")
case .gitHubCopilot:
Text("GitHub Copilot")
case .deepSeekOpenAICompatible:
Text("DeepSeek (OpenAI Compatible)")
case .openRouterOpenAICompatible:
Text("OpenRouter (OpenAI Compatible)")
case .grokOpenAICompatible:
Text("Grok (OpenAI Compatible)")
case .mistralOpenAICompatible:
Text("Mistral (OpenAI Compatible)")
}
}
},
label: { Text("Format") }
)
.pickerStyle(.segmented)
.pickerStyle(.menu)
}
}
}
Expand Down Expand Up @@ -243,7 +258,7 @@ struct ChatModelEditView: View {

MaxTokensTextField(store: store)
SupportsFunctionCallingToggle(store: store)

TextField(text: $store.openAIOrganizationID, prompt: Text("Optional")) {
Text("Organization ID")
}
Expand Down Expand Up @@ -321,11 +336,15 @@ struct ChatModelEditView: View {
Toggle(isOn: $store.enforceMessageOrder) {
Text("Enforce message order to be user/assistant alternated")
}

Toggle(isOn: $store.openAICompatibleSupportsMultipartMessageContent) {
Text("Support multi-part message content")
}

Toggle(isOn: $store.requiresBeginWithUserMessage) {
Text("Requires the first message to be from the user")
}

Button("Custom Headers") {
isEditingCustomHeader.toggle()
}
Expand Down Expand Up @@ -375,12 +394,16 @@ struct ChatModelEditView: View {

struct OllamaForm: View {
@Perception.Bindable var store: StoreOf<ChatModelEdit>
@State var isEditingCustomHeader = false

var body: some View {
WithPerceptionTracking {
BaseURLTextField(store: store, prompt: Text("http://127.0.0.1:11434")) {
Text("/api/chat")
}

ApiKeyNamePicker(store: store)

TextField("Model Name", text: $store.modelName)

MaxTokensTextField(store: store)
Expand All @@ -389,12 +412,19 @@ struct ChatModelEditView: View {
Text("Keep Alive")
}

Button("Custom Headers") {
isEditingCustomHeader.toggle()
}

VStack(alignment: .leading, spacing: 8) {
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
" For more details, please visit [https://ollama.com](https://ollama.com)."
)
}
.padding(.vertical)

}.sheet(isPresented: $isEditingCustomHeader) {
CustomHeaderSettingsView(headers: $store.customHeaders)
}
}
}
Expand Down Expand Up @@ -442,6 +472,61 @@ struct ChatModelEditView: View {
}
}
}

struct GitHubCopilotForm: View {
@Perception.Bindable var store: StoreOf<ChatModelEdit>
@State var isEditingCustomHeader = false

var body: some View {
WithPerceptionTracking {
TextField("Model Name", text: $store.modelName)
.overlay(alignment: .trailing) {
Picker(
"",
selection: $store.modelName,
content: {
if AvailableGitHubCopilotModel(rawValue: store.modelName) == nil {
Text("Custom Model").tag(store.modelName)
}
ForEach(AvailableGitHubCopilotModel.allCases, id: \.self) { model in
Text(model.rawValue).tag(model.rawValue)
}
}
)
.frame(width: 20)
}

MaxTokensTextField(store: store)
SupportsFunctionCallingToggle(store: store)

Toggle(isOn: $store.enforceMessageOrder) {
Text("Enforce message order to be user/assistant alternated")
}

Toggle(isOn: $store.openAICompatibleSupportsMultipartMessageContent) {
Text("Support multi-part message content")
}

Button("Custom Headers") {
isEditingCustomHeader.toggle()
}

VStack(alignment: .leading, spacing: 8) {
Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
" Please login in the GitHub Copilot settings to use the model."
)

Text(Image(systemName: "exclamationmark.triangle.fill")) + Text(
" This will call the APIs directly, which may not be allowed by GitHub. But it's used in other popular apps like Zed."
)
}
.dynamicHeightTextInFormWorkaround()
.padding(.vertical)
}.sheet(isPresented: $isEditingCustomHeader) {
CustomHeaderSettingsView(headers: $store.customHeaders)
}
}
}
}

#Preview("OpenAI") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ extension ChatModel: ManageableAIModel {
case .googleAI: return "Google Generative AI"
case .ollama: return "Ollama"
case .claude: return "Claude"
case .gitHubCopilot: return "GitHub Copilot"
}
}

Expand Down
Loading