220 lines
9.3 KiB
Swift
220 lines
9.3 KiB
Swift
import AppKit
|
|
import CodexBarCore
|
|
import SwiftUI
|
|
|
|
struct CopilotProviderImplementation: ProviderImplementation {
|
|
let id: UsageProvider = .copilot
|
|
let supportsLoginFlow: Bool = true
|
|
|
|
@MainActor
|
|
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
|
|
ProviderPresentation { _ in "github api" }
|
|
}
|
|
|
|
@MainActor
|
|
func observeSettings(_ settings: SettingsStore) {
|
|
_ = settings.copilotAPIToken
|
|
_ = settings.copilotEnterpriseHost
|
|
_ = settings.copilotBudgetExtrasEnabled
|
|
_ = settings.copilotBudgetCookieSource
|
|
_ = settings.copilotBudgetCookieHeader
|
|
}
|
|
|
|
@MainActor
|
|
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
|
|
.copilot(context.settings.copilotSettingsSnapshot(tokenOverride: context.tokenOverride))
|
|
}
|
|
|
|
@MainActor
|
|
func loginMenuAction(context _: ProviderMenuLoginContext)
|
|
-> (label: String, action: MenuDescriptor.MenuAction)?
|
|
{
|
|
("Add Account...", .addProviderAccount(.copilot))
|
|
}
|
|
|
|
@MainActor
|
|
func settingsToggles(context: ProviderSettingsContext) -> [ProviderSettingsToggleDescriptor] {
|
|
let budgetExtrasBinding = Binding(
|
|
get: { context.settings.copilotBudgetExtrasEnabled },
|
|
set: { enabled in
|
|
context.settings.copilotBudgetExtrasEnabled = enabled
|
|
})
|
|
let budgetExtrasStatus: () -> String? = {
|
|
if context.store.snapshot(for: .copilot)?.extraRateWindows?.isEmpty == false {
|
|
return nil
|
|
}
|
|
if context.settings.copilotBudgetCookieSource == .manual,
|
|
context.settings.copilotBudgetCookieHeader.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
{
|
|
return [
|
|
"Paste a github.com Cookie header, then refresh Copilot.",
|
|
"Copilot reauth does not provide the GitHub web cookie used for budgets.",
|
|
].joined(separator: " ")
|
|
}
|
|
return [
|
|
"Refresh Copilot to load budget bars.",
|
|
"Budget extras require a logged-in github.com browser session or a manual Cookie header.",
|
|
].joined(separator: " ")
|
|
}
|
|
|
|
return [
|
|
ProviderSettingsToggleDescriptor(
|
|
id: "copilot-budget-extras",
|
|
title: "Budget extras",
|
|
subtitle: [
|
|
"Optional.",
|
|
"Turn this on to fetch configured GitHub Copilot budget limits and show them as extra bars.",
|
|
].joined(separator: " "),
|
|
binding: budgetExtrasBinding,
|
|
statusText: budgetExtrasStatus,
|
|
actions: [],
|
|
isVisible: nil,
|
|
onChange: { enabled in
|
|
if enabled {
|
|
await context.store.refreshProvider(.copilot, allowDisabled: true)
|
|
} else {
|
|
context.store.clearCopilotBudgetExtras()
|
|
}
|
|
},
|
|
onAppDidBecomeActive: nil,
|
|
onAppearWhenEnabled: nil),
|
|
]
|
|
}
|
|
|
|
@MainActor
|
|
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
|
|
let extraWindows = context.store.snapshot(for: .copilot)?.extraRateWindows ?? []
|
|
let cookieBinding = Binding(
|
|
get: { context.settings.copilotBudgetCookieSource.rawValue },
|
|
set: { raw in
|
|
context.settings.copilotBudgetCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
|
|
})
|
|
let cookieOptions = ProviderCookieSourceUI.options(
|
|
allowsOff: false,
|
|
keychainDisabled: context.settings.debugDisableKeychainAccess)
|
|
let cookieSubtitle: () -> String? = {
|
|
ProviderCookieSourceUI.subtitle(
|
|
source: context.settings.copilotBudgetCookieSource,
|
|
keychainDisabled: context.settings.debugDisableKeychainAccess,
|
|
auto: "Automatically imports browser cookies for github.com budget extras.",
|
|
manual: "Paste a Cookie header from github.com.",
|
|
off: "GitHub cookies are disabled.")
|
|
}
|
|
let options = [
|
|
ProviderSettingsPickerOption(
|
|
id: CopilotIconSecondaryWindowSelection.chat,
|
|
title: "Chat"),
|
|
] + extraWindows.map { window in
|
|
ProviderSettingsPickerOption(id: window.id, title: window.title)
|
|
}
|
|
|
|
return [
|
|
ProviderSettingsPickerDescriptor(
|
|
id: "copilot-icon-secondary-window",
|
|
title: "Menu bar secondary metric",
|
|
subtitle: "Choose the second meter shown in the menu bar icon.",
|
|
placement: .menuBar,
|
|
dynamicSubtitle: {
|
|
extraWindows.isEmpty
|
|
? "Budget options appear after a refresh finds configured Copilot budgets."
|
|
: nil
|
|
},
|
|
binding: Binding(
|
|
get: {
|
|
let selected = context.settings.copilotIconSecondaryWindowID
|
|
if selected == CopilotIconSecondaryWindowSelection.chat {
|
|
return selected
|
|
}
|
|
return extraWindows.contains(where: { $0.id == selected })
|
|
? selected
|
|
: CopilotIconSecondaryWindowSelection.chat
|
|
},
|
|
set: { selection in
|
|
context.settings.copilotIconSecondaryWindowID = selection
|
|
}),
|
|
options: options,
|
|
isVisible: { context.settings.copilotBudgetExtrasEnabled },
|
|
onChange: nil),
|
|
ProviderSettingsPickerDescriptor(
|
|
id: "copilot-budget-cookie-source",
|
|
title: "GitHub cookies",
|
|
subtitle: "Automatically imports browser cookies for budget extras.",
|
|
dynamicSubtitle: cookieSubtitle,
|
|
binding: cookieBinding,
|
|
options: cookieOptions,
|
|
isVisible: { context.settings.copilotBudgetExtrasEnabled },
|
|
onChange: { _ in
|
|
await context.store.refreshProvider(.copilot, allowDisabled: true)
|
|
},
|
|
trailingText: {
|
|
guard context.settings.copilotBudgetCookieSource != .manual else { return nil }
|
|
return ProviderCookieSourceUI.cachedTrailingText(provider: .copilot)
|
|
}),
|
|
]
|
|
}
|
|
|
|
@MainActor
|
|
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
|
|
[
|
|
ProviderSettingsFieldDescriptor(
|
|
id: "copilot-budget-cookie-header",
|
|
title: "Manual GitHub Cookie header",
|
|
subtitle: "Paste a github.com Cookie header. Treat this value like a password.",
|
|
kind: .secure,
|
|
placeholder: "Cookie: ...",
|
|
binding: context.stringBinding(\.copilotBudgetCookieHeader),
|
|
actions: [
|
|
ProviderSettingsActionDescriptor(
|
|
id: "refresh-copilot-budget-cookie",
|
|
title: "Refresh budgets",
|
|
style: .bordered,
|
|
isVisible: nil,
|
|
perform: {
|
|
await context.store.refreshProvider(.copilot, allowDisabled: true)
|
|
}),
|
|
],
|
|
isVisible: {
|
|
context.settings.copilotBudgetExtrasEnabled &&
|
|
context.settings.copilotBudgetCookieSource == .manual
|
|
},
|
|
onActivate: nil),
|
|
ProviderSettingsFieldDescriptor(
|
|
id: "copilot-enterprise-host",
|
|
title: "Enterprise host",
|
|
subtitle: "Optional. Enter your GitHub Enterprise host, for example octocorp.ghe.com. " +
|
|
"Leave blank for github.com.",
|
|
kind: .plain,
|
|
placeholder: "github.com",
|
|
binding: context.stringBinding(\.copilotEnterpriseHost),
|
|
actions: [],
|
|
isVisible: nil,
|
|
onActivate: nil),
|
|
ProviderSettingsFieldDescriptor(
|
|
id: "copilot-add-account",
|
|
title: "GitHub Login",
|
|
subtitle: "Add accounts via GitHub OAuth Device Flow on the selected host.",
|
|
kind: .plain,
|
|
placeholder: nil,
|
|
binding: .constant(""),
|
|
actions: [
|
|
ProviderSettingsActionDescriptor(
|
|
id: "copilot-add-account-action",
|
|
title: "Add Account",
|
|
style: .bordered,
|
|
isVisible: { true },
|
|
perform: {
|
|
await CopilotLoginFlow.run(settings: context.settings)
|
|
}),
|
|
],
|
|
isVisible: nil,
|
|
onActivate: nil),
|
|
]
|
|
}
|
|
|
|
@MainActor
|
|
func runLoginFlow(context: ProviderLoginContext) async -> Bool {
|
|
await CopilotLoginFlow.run(settings: context.controller.settings)
|
|
return true
|
|
}
|
|
}
|