From 65c2587fdace44023337020c0689660245fe916a Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 3 Jul 2024 18:17:59 -0700 Subject: [PATCH] rough edges, but roundtrip works --- .../TerminalApp/AppActionHandlers.cpp | 11 ++- .../TerminalApp/AppCommandlineArgs.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 85 +++++++++++++++++++ src/cascadia/TerminalApp/TerminalPage.h | 3 + .../TerminalSettingsEditor/AISettings.cpp | 2 + .../TerminalSettingsEditor/AISettings.xaml | 7 ++ .../AISettingsViewModel.cpp | 7 ++ .../AISettingsViewModel.h | 3 + .../AISettingsViewModel.idl | 3 + .../TerminalSettingsEditor/MainPage.cpp | 10 ++- .../TerminalSettingsEditor/MainPage.h | 1 + .../TerminalSettingsEditor/MainPage.idl | 2 + .../Resources/en-US/Resources.resw | 4 + 13 files changed, 137 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d59bee36276..68bc1721d44 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -1496,9 +1496,16 @@ namespace winrt::TerminalApp::implementation { if (const auto& uriArgs{ args.ActionArgs().try_as() }) { - if (!uriArgs.Uri().empty()) + const auto uriString{ uriArgs.Uri() }; + if (!uriString.empty()) { - args.Handled(true); + Windows::Foundation::Uri uri{ uriString }; + // we only accept "github-auth" host names for now + if (uri.Host() == L"github-auth") + { + _CompleteGithubAuth(uri); + args.Handled(true); + } } } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 96da9a4892c..818227d3e31 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -548,7 +548,6 @@ void AppCommandlineArgs::_buildHandleUriParser() // that `this` will still be safe - this function just lets us know this // command was parsed. subcommand->callback([&, this]() { - wil::WaitForDebuggerPresent(false); // Build the action from the values we've parsed on the commandline. const auto cmdlineArgs = _currentCommandline->Args(); winrt::hstring uri; @@ -994,7 +993,8 @@ void AppCommandlineArgs::ValidateStartupCommands() // If we parsed no commands, or the first command we've parsed is not a new // tab action, prepend a new-tab command to the front of the list. if (_startupActions.empty() || - _startupActions.front().Action() != ShortcutAction::NewTab) + (_startupActions.front().Action() != ShortcutAction::NewTab && + _startupActions.front().Action() != ShortcutAction::HandleUri)) { // Build the NewTab action from the values we've parsed on the commandline. NewTerminalArgs newTerminalArgs{}; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index ea8bb977bac..0d756bd30ab 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -17,6 +17,12 @@ #include #include +#include +#include +#include + +#include + #include "../../types/inc/utils.hpp" #include "App.h" #include "ColorHelper.h" @@ -45,6 +51,9 @@ using namespace ::TerminalApp; using namespace ::Microsoft::Console; using namespace ::Microsoft::Terminal::Core; using namespace std::chrono_literals; +namespace WWH = ::winrt::Windows::Web::Http; +namespace WSS = ::winrt::Windows::Storage::Streams; +namespace WDJ = ::winrt::Windows::Data::Json; #define HOOKUP_ACTION(action) _actionDispatch->action({ this, &TerminalPage::_Handle##action }); @@ -4029,9 +4038,85 @@ namespace winrt::TerminalApp::implementation } }); + sui.GithubAuthRequested([weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) { + if (auto page{ weakThis.get() }) + { + page->_InitiateGithubAuth(); + } + }); + return *settingsContent; } + void TerminalPage::_InitiateGithubAuth() + { + // todo: we probably want a "state" parameter for protection against forgery attacks + ShellExecute(nullptr, L"open", L"https://github.com/login/oauth/authorize?client_id=Iv1.b0870d058e4473a1", nullptr, nullptr, SW_SHOWNORMAL); + } + + winrt::fire_and_forget TerminalPage::_CompleteGithubAuth(const Windows::Foundation::Uri uri) + { + winrt::Windows::Web::Http::HttpClient httpClient{}; + httpClient.DefaultRequestHeaders().Accept().TryParseAdd(L"application/json"); + + WWH::HttpRequestMessage request{ WWH::HttpMethod::Post(), Windows::Foundation::Uri{ L"https://github.com/login/oauth/access_token" } }; + request.Headers().Accept().TryParseAdd(L"application/json"); + + WDJ::JsonObject jsonContent; + jsonContent.SetNamedValue(L"client_id", WDJ::JsonValue::CreateStringValue(L"Iv1.b0870d058e4473a1")); + jsonContent.SetNamedValue(L"client_secret", WDJ::JsonValue::CreateStringValue(L"notShowingSecretForCommitForObviousReasons")); + jsonContent.SetNamedValue(L"code", WDJ::JsonValue::CreateStringValue(uri.QueryParsed().GetFirstValueByName(L"code"))); + const auto stringContent = jsonContent.ToString(); + WWH::HttpStringContent requestContent{ + stringContent, + WSS::UnicodeEncoding::Utf8, + L"application/json" + }; + + request.Content(requestContent); + + co_await winrt::resume_background(); + + try + { + const auto response = httpClient.SendRequestAsync(request).get(); + // Parse out the suggestion from the response + const auto string{ response.Content().ReadAsStringAsync().get() }; + const auto jsonResult{ WDJ::JsonObject::Parse(string) }; + const auto authToken{ jsonResult.GetNamedString(L"access_token") }; + const auto refreshToken{ jsonResult.GetNamedString(L"refresh_token") }; + if (!authToken.empty() && !refreshToken.empty()) + { + _settings.GlobalSettings().AIInfo().GithubCopilotAuthToken(authToken); + _settings.GlobalSettings().AIInfo().GithubCopilotRefreshToken(refreshToken); + + // todo: this _settingsTab check only works if new instance behavior is set to attach to this window, + // fix this to work with any new instance behavior + if (_settingsTab) + { + if (auto terminalTab{ _GetTerminalTabImpl(_settingsTab) }) + { + // refresh the settings UI now that we have the auth tokens stored + co_await winrt::resume_foreground(Dispatcher()); + terminalTab->UpdateSettings(_settings); + + // also reload the extension palette with the new settings + if (_extensionPalette) + { + _extensionPalette = nullptr; + _loadQueryExtension(); + } + } + } + } + } + catch (...) + { + } + + co_return; + } + // Method Description: // - Creates a settings UI tab and focuses it. If there's already a settings UI tab open, // just focus the existing one. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 0e1643adca1..ab9a24728de 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -425,6 +425,9 @@ namespace winrt::TerminalApp::implementation fire_and_forget _LaunchSettings(const Microsoft::Terminal::Settings::Model::SettingsTarget target); + void _InitiateGithubAuth(); + winrt::fire_and_forget _CompleteGithubAuth(const Windows::Foundation::Uri uri); + void _TabDragStarted(const IInspectable& sender, const IInspectable& eventArgs); void _TabDragCompleted(const IInspectable& sender, const IInspectable& eventArgs); diff --git a/src/cascadia/TerminalSettingsEditor/AISettings.cpp b/src/cascadia/TerminalSettingsEditor/AISettings.cpp index f1fb8fbc453..ba1e2e61332 100644 --- a/src/cascadia/TerminalSettingsEditor/AISettings.cpp +++ b/src/cascadia/TerminalSettingsEditor/AISettings.cpp @@ -123,5 +123,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void AISettings::ClearGithubCopilotTokens_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) { + _ViewModel.GithubCopilotAuthToken(L""); + _ViewModel.GithubCopilotRefreshToken(L""); } } diff --git a/src/cascadia/TerminalSettingsEditor/AISettings.xaml b/src/cascadia/TerminalSettingsEditor/AISettings.xaml index 3bc08cd91af..a86a3d03996 100644 --- a/src/cascadia/TerminalSettingsEditor/AISettings.xaml +++ b/src/cascadia/TerminalSettingsEditor/AISettings.xaml @@ -268,6 +268,13 @@ + diff --git a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.cpp b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.cpp index b2424f586fc..03135e15f70 100644 --- a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.cpp @@ -8,6 +8,7 @@ #include #include +#include using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -97,4 +98,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { return _Settings.GlobalSettings().AIInfo().ActiveProvider() == Model::LLMProvider::GithubCopilot; } + + void AISettingsViewModel::InitiateGithubAuth_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) + { + _awaitingGithubAuth = true; + GithubAuthRequested.raise(nullptr, nullptr); + } } diff --git a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.h b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.h index 3817bc5dbb9..f3cf0e8fc3b 100644 --- a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.h @@ -34,9 +34,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool AzureOpenAIIsActive(); bool OpenAIIsActive(); bool GithubCopilotIsActive(); + void InitiateGithubAuth_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); + til::typed_event GithubAuthRequested; private: Model::CascadiaSettings _Settings; + bool _awaitingGithubAuth{ false }; }; }; diff --git a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.idl b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.idl index 68ad4b80649..f9d2d441c8d 100644 --- a/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/AISettingsViewModel.idl @@ -21,5 +21,8 @@ namespace Microsoft.Terminal.Settings.Editor Boolean AreGithubCopilotTokensSet { get; }; void GithubCopilotAuthToken(String authToken); void GithubCopilotRefreshToken(String refreshToken); + + void InitiateGithubAuth_Click(IInspectable sender, Windows.UI.Xaml.RoutedEventArgs args); + event Windows.Foundation.TypedEventHandler GithubAuthRequested; } } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index ff6e0a1da02..396dbaf325e 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -422,7 +422,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (clickedItemTag == AISettingsTag) { - contentFrame().Navigate(xaml_typename(), winrt::make(_settingsClone)); + auto aiSettingsVM{ winrt::make(_settingsClone) }; + aiSettingsVM.GithubAuthRequested([weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) { + if (auto mainPage{ weakThis.get() }) + { + // propagate the event to TerminalPage + mainPage->GithubAuthRequested.raise(nullptr, nullptr); + } + }); + contentFrame().Navigate(xaml_typename(), aiSettingsVM); const auto crumb = winrt::make(box_value(clickedItemTag), RS_(L"Nav_AISettings/Content"), BreadcrumbSubPage::None); _breadcrumbs.Append(crumb); } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 1535c85a33c..8cde817a908 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -47,6 +47,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Windows::Foundation::Collections::IObservableVector Breadcrumbs() noexcept; til::typed_event OpenJson; + til::typed_event GithubAuthRequested; private: Windows::Foundation::Collections::IObservableVector _breadcrumbs; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.idl b/src/cascadia/TerminalSettingsEditor/MainPage.idl index 419981d94de..919cfdbe029 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.idl +++ b/src/cascadia/TerminalSettingsEditor/MainPage.idl @@ -42,5 +42,7 @@ namespace Microsoft.Terminal.Settings.Editor Windows.Foundation.Collections.IObservableVector Breadcrumbs { get; }; Windows.UI.Xaml.Media.Brush BackgroundBrush { get; }; + + event Windows.Foundation.TypedEventHandler GithubAuthRequested; } } diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index f31cde3fac8..282ff21d01e 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -625,6 +625,10 @@ Store Text on the button that allows the user to store their key and/or endpoint. + + Authenticate via Github + Text on the button that allows the user to authenticate to Github. + To use Azure OpenAI as a service provider, you need an Azure OpenAI service resource. Header of the description that informs the user about Azure OpenAI and the prerequisites for setting it up in Terminal.