Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: get notified on auth change #39

Merged
merged 1 commit into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 9 additions & 4 deletions src/Cody.Core/Agent/IAgentService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
using Cody.Core.Agent.Protocol;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Cody.Core.Agent
Expand Down Expand Up @@ -44,6 +40,15 @@ public interface IAgentService

[AgentMethod("textDocument/didClose")]
void DidClose(ProtocolTextDocument docState);

[AgentMethod("chat/new")]
Task<string> NewChat();

[AgentMethod("chat/sidebar/new")]
Task<ChatPanelInfo> NewSidebarChat();

[AgentMethod("chat/web/new")]
Task<ChatPanelInfo> NewEditorChat();
}
}

106 changes: 57 additions & 49 deletions src/Cody.Core/Agent/NotificationHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using EnvDTE80;
using Newtonsoft.Json.Linq;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

namespace Cody.Core.Agent
Expand All @@ -11,6 +12,7 @@ public class NotificationHandlers : INotificationHandler
public NotificationHandlers()
{
}

public delegate Task PostWebMessageAsJsonDelegate(string message);
public PostWebMessageAsJsonDelegate PostWebMessageAsJson { get; set; }

Expand All @@ -24,49 +26,32 @@ public NotificationHandlers()

public void SetAgentClient(IAgentService client)
{
this.agentClient = client;
agentClient = client;
agentClientReady.SetResult(true);
}

// Send a message to the host from webview.
public async Task SendWebviewMessage(string handle, string message)
{
// Turn message into a JSON object
var json = JObject.Parse(message);
var command = json["command"]?.ToString();

switch (command)
try
{
case "links":
var link = json["value"]?.ToString();
if (!string.IsNullOrEmpty(link))
{
// if the is links, open the link in the default browser
// string link = json["value"].ToString();
// System.Diagnostics.Process.Start(link);
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(link) { UseShellExecute = true });
}
return;

case "command":
var json = JObject.Parse(message);
var command = json["command"]?.ToString();
if (command == "command")
{
var id = json["id"]?.ToString();
// Open the extension options page for authentication related commands.
if (id == "cody.status-bar.interacted" || id.StartsWith("cody.auth.signin") || id.StartsWith("cody.auth.signout"))
if (id == "cody.status-bar.interacted" || id?.StartsWith("cody.auth.signin") == true)
{
try
{
var dte = (DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
dte.ExecuteCommand("Tools.Options", "Cody.General");
}
catch (System.Runtime.InteropServices.COMException)
{
// Handle the case where Visual Studio is not running or COM object is not accessible
}
var dte = (DTE2)Marshal.GetActiveObject("VisualStudio.DTE");
dte.ExecuteCommand("Tools.Options", "Cody.General");
return;
}
break;
}
}
catch
{
// Ignore
}

await agentClient.ReceiveMessageStringEncoded(new ReceiveMessageStringEncodedParams
{
Id = handle,
Expand All @@ -77,18 +62,19 @@ await agentClient.ReceiveMessageStringEncoded(new ReceiveMessageStringEncodedPar
[AgentNotification("debug/message")]
public void Debug(string channel, string message)
{
System.Diagnostics.Debug.WriteLine(message, "Agent Debug");
DebugLog(message, "Debug");
}

[AgentNotification("webview/registerWebview")]
public void RegisterWebview(string handle)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent registerWebview");
DebugLog(handle, "RegisterWebview");
}

[AgentNotification("webview/registerWebviewViewProvider")]
public async Task RegisterWebviewViewProvider(string viewId, bool retainContextWhenHidden)
{
DebugLog(viewId, "RegisterWebviewViewProvider");
agentClientReady.Task.Wait();
await agentClient.ResolveWebviewView(new ResolveWebviewViewParams
{
Expand All @@ -97,21 +83,20 @@ await agentClient.ResolveWebviewView(new ResolveWebviewViewParams
// TODO: Create dynmically when we support editor panel
WebviewHandle = "visual-studio-sidebar",
});
System.Diagnostics.Debug.WriteLine(viewId, retainContextWhenHidden, "Agent registerWebviewViewProvider");
}

[AgentNotification("webview/createWebviewPanel", deserializeToSingleObject: true)]
public void CreateWebviewPanel(CreateWebviewPanelParams panelParams)
{
System.Diagnostics.Debug.WriteLine(panelParams, "Agent createWebviewPanel");
DebugLog(panelParams.ToString(), "CreateWebviewPanel");
}

[AgentNotification("webview/setOptions")]
public void SetOptions(string handle, DefiniteWebviewOptions options)
{
if (options.EnableCommandUris is bool enableCmd)
{

DebugLog(handle, "SetOptions");
}
else if (options.EnableCommandUris is JArray jArray)
{
Expand All @@ -122,7 +107,7 @@ public void SetOptions(string handle, DefiniteWebviewOptions options)
[AgentNotification("webview/setHtml")]
public void SetHtml(string handle, string html)
{
System.Diagnostics.Debug.WriteLine(html, "Agent setHtml");
DebugLog(html, "SetHtml");
OnSetHtmlEvent?.Invoke(this, new SetHtmlEvent() { Handle = handle, Html = html });
}

Expand All @@ -135,45 +120,68 @@ public void PostMessage(string handle, string message)
[AgentNotification("webview/postMessageStringEncoded")]
public void PostMessageStringEncoded(string id, string stringEncodedMessage)
{
System.Diagnostics.Debug.WriteLine(stringEncodedMessage, "Agent postMessageStringEncoded");
DebugLog(stringEncodedMessage, "PostMessageStringEncoded");
PostWebMessageAsJson?.Invoke(stringEncodedMessage);
}

[AgentNotification("webview/didDisposeNative")]
public void DidDisposeNative(string handle)
{

}

[AgentNotification("extensionConfiguration/didChange", deserializeToSingleObject: true)]
public void ExtensionConfigDidChange(ExtensionConfiguration config)
{
System.Diagnostics.Debug.WriteLine(config, "Agent didChange");
DebugLog(handle, "DidDisposeNative");
}

[AgentNotification("webview/dispose")]
public void Dispose(string handle)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent dispose");
DebugLog(handle, "Dispose");
}

[AgentNotification("webview/reveal")]
public void Reveal(string handle, int viewColumn, bool preserveFocus)
{
System.Diagnostics.Debug.WriteLine(handle, "Agent reveal");
DebugLog(handle, "Reveal");
}

[AgentNotification("webview/setTitle")]
public void SetTitle(string handle, string title)
{
System.Diagnostics.Debug.WriteLine(title, "Agent setTitle");
DebugLog(title, "SetTitle");
}

[AgentNotification("webview/setIconPath")]
public void SetIconPath(string handle, string iconPathUri)
{
System.Diagnostics.Debug.WriteLine(iconPathUri, "Agent setIconPath");
DebugLog(iconPathUri, "SetIconPath");
}

[AgentNotification("window/didChangeContext")]
public void WindowDidChangeContext(string key, string value)
{
DebugLog(value, $@"WindowDidChangeContext Key - {key}");

// Check the value to see if Cody is activated or deactivated
// Deactivated: value = "false", meaning user is no longer authenticated.
// In this case, we can send Agent a request to get the latest user AuthStatus to
// confirm if the user is logged out or not.
if (key == "cody.activated")
{
var isAuthenticated = value == "true";
DebugLog(isAuthenticated.ToString(), "User is authenticated");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PiotrKarczmarz @tomaszgolebiowski here is where we can get notified when user auth status has changed so we don't need to poll AuthStatus constantly.

}
}

[AgentNotification("extensionConfiguration/didChange", deserializeToSingleObject: true)]
public void ExtensionConfigDidChange(ExtensionConfiguration config)
{
DebugLog(config.ToString(), "didChange");
}

public void DebugLog(string message, string origin)
{
// Log the message to the debug console when in debug mode.
#if DEBUG
System.Diagnostics.Debug.WriteLine(message, $@"Agent Notified {origin}");
#endif
}
}
}
8 changes: 8 additions & 0 deletions src/Cody.Core/Agent/Protocol/ChatPanelInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Cody.Core.Agent.Protocol
{
public class ChatPanelInfo
{
public string PanelId { get; set; }
public string ChatId { get; set; }
}
}
1 change: 1 addition & 0 deletions src/Cody.Core/Cody.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
<Compile Include="Agent\INotificationHandler.cs" />
<Compile Include="Agent\NotificationHandlers.cs" />
<Compile Include="Agent\Protocol\AuthStatus.cs" />
<Compile Include="Agent\Protocol\ChatPanelInfo.cs" />
<Compile Include="Agent\Protocol\ClientCapabilities.cs" />
<Compile Include="Agent\Protocol\ClientInfo.cs" />
<Compile Include="Agent\Protocol\ConfigOverwrites.cs" />
Expand Down
64 changes: 24 additions & 40 deletions src/Cody.UI/Controls/WebviewController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ public class WebviewController
{
private CoreWebView2 _webview;

public CoreWebView2 GetWebview => _webview;

public string colorThemeScript;

public event EventHandler<string> WebViewMessageReceived;
Expand All @@ -31,24 +29,9 @@ public async Task<CoreWebView2> InitializeWebView(CoreWebView2 webView)
ConfigureWebView();
SetupResourceHandling();

webView.OpenDevToolsWindow();

return webView;
}

private async Task<CoreWebView2Environment> CreateWebView2Environment()
{
var appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Cody");
var options = new CoreWebView2EnvironmentOptions
{
#if DEBUG
AdditionalBrowserArguments = "--remote-debugging-port=9222 --disable-web-security --allow-file-access-from-files",
AllowSingleSignOnUsingOSPrimaryAccount = true,
#endif
};
return await CoreWebView2Environment.CreateAsync(null, appData, options);
}

private void ConfigureWebView()
{
_webview.Settings.AreDefaultScriptDialogsEnabled = true;
Expand All @@ -57,14 +40,19 @@ private void ConfigureWebView()
_webview.Settings.AreHostObjectsAllowed = true;
_webview.Settings.IsScriptEnabled = true;
_webview.Settings.AreBrowserAcceleratorKeysEnabled = true;
_webview.Settings.AreDevToolsEnabled = true;
_webview.Settings.IsGeneralAutofillEnabled = true;
// Enable below settings only in DEBUG mode.
_webview.Settings.AreDefaultContextMenusEnabled = false;
_webview.Settings.AreDevToolsEnabled = false;
#if DEBUG
_webview.Settings.AreDefaultContextMenusEnabled = true;
_webview.Settings.AreDevToolsEnabled = true;
#endif
}

private void SetupEventHandlers()
{
_webview.DOMContentLoaded += CoreWebView2OnDOMContentLoaded;
_webview.NavigationCompleted += CoreWebView2OnNavigationCompleted;
_webview.WebMessageReceived += HandleWebViewMessage;
}

Expand Down Expand Up @@ -103,31 +91,28 @@ private string GetContentType(string filePath)
return "Content-Type: text/html";
}


private async void CoreWebView2OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
await ApplyThemingScript();
}

private async void CoreWebView2OnDOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
{
await ApplyThemingScript();
}

private void HandleWebViewMessage(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
// Handle message sent from webview to agent.
var message = e.TryGetWebMessageAsString();
System.Diagnostics.Debug.WriteLine(message, "Agent HandleWebViewMessage");
WebViewMessageReceived.Invoke(this, message);
// TODO: Get token from message if message has a token.
// IMPORTANT: Do not log the token to the console in production.
System.Diagnostics.Debug.WriteLine(message, "Agent HandleWebViewMessage");
}

public async Task PostWebMessageAsJson(string message)
{
await Application.Current.Dispatcher.InvokeAsync(async () =>
// From agent to webview.
await Application.Current.Dispatcher.InvokeAsync(() =>
{
System.Diagnostics.Debug.WriteLine(message, "Agent PostWebMessageAsJson");
_webview.PostWebMessageAsJson(message);
await _webview.ExecuteScriptWithResultAsync(GetPostMessageScript(message));
System.Diagnostics.Debug.WriteLine(message, "Agent PostWebMessageAsJson");
});
}

Expand All @@ -153,19 +138,27 @@ private static string GetVsCodeApiScript() => @"
globalThis.acquireVsCodeApi = (function() {
let acquired = false;
let state = undefined;

window.chrome.webview.addEventListener('message', e => {
console.log('Send to webview', e.data)
const event = new CustomEvent('message');
event.data = e.data;
window.dispatchEvent(event)
});

return () => {
if (acquired && !false) {
throw new Error('An instance of the VS Code API has already been acquired');
}
acquired = true;
return Object.freeze({
postMessage: function(message) {
console.log(`do-postMessage: ${JSON.stringify(message)}`);
console.log(`Send from webview: ${JSON.stringify(message)}`);
window.chrome.webview.postMessage(JSON.stringify(message));
},
setState: function(newState) {
state = newState;
console.log(`do-setState: ${JSON.stringify(newState)}`);
console.log(`Set State: ${JSON.stringify(newState)}`);
return newState;
},
getState: function() {
Expand All @@ -184,14 +177,5 @@ private static string GetThemeScript(string colorTheme) => $@"

{colorTheme}
";

private static string GetPostMessageScript(string message) => $@"
(() => {{
const event = new CustomEvent('message');
console.log('PostWebMessageAsJson', {message});
event.data = {message};
window.dispatchEvent(event);
}})()
";
}
}
Loading