From 50049be71acb09f7f31b97775e6e1e2e3cd41c9d Mon Sep 17 00:00:00 2001 From: Piotr Kukielka Date: Wed, 8 Jan 2025 14:46:30 +0100 Subject: [PATCH 1/2] Simplify jetbrains account management --- .../cody/util/CodyIntegrationTextFixture.kt | 10 +- .../com/sourcegraph/cody/CodyActionGroup.java | 10 +- .../config/CodyAuthNotificationActivity.java | 4 +- .../browser/JSToJavaBridgeRequestHandler.java | 2 +- .../sourcegraph/website/FileActionBase.java | 21 ++-- .../website/OpenRevisionAction.java | 6 +- .../sourcegraph/website/SearchActionBase.java | 7 +- .../com/sourcegraph/website/URLBuilder.java | 24 ++-- .../com/sourcegraph/cody/agent/CodyAgent.kt | 23 ++-- .../sourcegraph/cody/agent/CodyAgentClient.kt | 19 ++-- .../cody/agent/CodyAgentService.kt | 15 ++- .../com/sourcegraph/cody/auth/CodyAccount.kt | 50 --------- .../sourcegraph/cody/auth/CodyAuthService.kt | 37 +++++++ .../sourcegraph/cody/auth/CodySecureStore.kt | 19 ++++ .../cody/auth/SourcegraphServerPath.kt | 3 + .../autocomplete/CodyAutocompleteManager.kt | 4 +- .../cody/chat/actions/BaseCommandAction.kt | 6 +- .../cody/chat/actions/NewChatAction.kt | 6 +- .../sourcegraph/cody/chat/ui/LlmDropdown.kt | 8 +- .../config/CodySettingsFileChangeListener.kt | 5 +- .../config/migration/AccountsMigration.kt | 6 +- .../config/migration/SettingsMigration.kt | 5 +- .../notification/CodySettingChangeListener.kt | 2 +- .../cody/edit/actions/BaseEditCodeAction.kt | 4 +- .../edit/lenses/actions/LensEditAction.kt | 6 +- .../ignore/ActionInIgnoredFileNotification.kt | 14 +-- .../EndOfTrialNotificationScheduler.kt | 103 ------------------ .../initialization/PostStartupActivity.kt | 6 +- .../listeners/CodySelectionInlayManager.kt | 4 +- .../CodyDisableAutocompleteAction.kt | 6 +- ...odyDisableLanguageForAutocompleteAction.kt | 6 +- .../cody/statusbar/CodyStatusService.kt | 4 +- .../com/sourcegraph/common/BrowserOpener.kt | 5 +- .../com/sourcegraph/config/ConfigUtil.kt | 31 +++--- 34 files changed, 210 insertions(+), 271 deletions(-) delete mode 100644 jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt create mode 100644 jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAuthService.kt create mode 100644 jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodySecureStore.kt delete mode 100644 jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt diff --git a/jetbrains/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt b/jetbrains/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt index 974ef2b12805..1d772d1b7ffe 100644 --- a/jetbrains/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt +++ b/jetbrains/src/integrationTest/kotlin/com/sourcegraph/cody/util/CodyIntegrationTextFixture.kt @@ -22,7 +22,6 @@ import com.intellij.testFramework.runInEdtAndWait import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_generated.ProtocolAuthenticatedAuthStatus import com.sourcegraph.cody.agent.protocol_generated.ProtocolCodeLens -import com.sourcegraph.cody.auth.CodyAccount import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.edit.lenses.LensListener import com.sourcegraph.cody.edit.lenses.LensesService @@ -102,16 +101,13 @@ open class CodyIntegrationTextFixture : BasePlatformTestCase(), LensListener { // change anything. private fun initCredentialsAndAgent() { val credentials = TestingCredentials.dotcom - val account = CodyAccount(SourcegraphServerPath.from(credentials.serverEndpoint, "")) - account.storeToken( - credentials.token ?: credentials.redactedToken, - ) - CodyAccount.setActiveAccount(account) + val endpoint = SourcegraphServerPath.from(credentials.serverEndpoint, "") + val token = credentials.token ?: credentials.redactedToken assertNotNull( "Unable to start agent in a timely fashion!", CodyAgentService.getInstance(project) - .startAgent(project) + .startAgent(project, endpoint, token) .completeOnTimeout(null, ASYNC_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) .get()) } diff --git a/jetbrains/src/main/java/com/sourcegraph/cody/CodyActionGroup.java b/jetbrains/src/main/java/com/sourcegraph/cody/CodyActionGroup.java index f5d6f7c2ee98..f71fd5ae72f7 100644 --- a/jetbrains/src/main/java/com/sourcegraph/cody/CodyActionGroup.java +++ b/jetbrains/src/main/java/com/sourcegraph/cody/CodyActionGroup.java @@ -3,7 +3,8 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.sourcegraph.cody.auth.CodyAccount; +import com.intellij.openapi.project.Project; +import com.sourcegraph.cody.auth.CodyAuthService; import com.sourcegraph.config.ConfigUtil; import org.jetbrains.annotations.NotNull; @@ -22,7 +23,12 @@ public boolean isDumbAware() { @Override public void update(@NotNull AnActionEvent e) { super.update(e); + + Project project = e.getProject(); e.getPresentation() - .setVisible(ConfigUtil.isCodyEnabled() && CodyAccount.Companion.hasActiveAccount()); + .setVisible( + ConfigUtil.isCodyEnabled() + && project != null + && CodyAuthService.getInstance(project).isActivated()); } } diff --git a/jetbrains/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java b/jetbrains/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java index b29b9cd99ac7..50af6c30d57a 100644 --- a/jetbrains/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java +++ b/jetbrains/src/main/java/com/sourcegraph/config/CodyAuthNotificationActivity.java @@ -10,7 +10,7 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.sourcegraph.Icons; import com.sourcegraph.cody.CodyToolWindowFactory; -import com.sourcegraph.cody.auth.CodyAccount; +import com.sourcegraph.cody.auth.CodyAuthService; import com.sourcegraph.cody.config.CodyApplicationSettings; import com.sourcegraph.cody.initialization.Activity; import com.sourcegraph.common.NotificationGroups; @@ -22,7 +22,7 @@ public class CodyAuthNotificationActivity implements Activity { @Override public void runActivity(@NotNull Project project) { if (!CodyApplicationSettings.getInstance().isGetStartedNotificationDismissed() - && !CodyAccount.Companion.hasActiveAccount()) { + && !CodyAuthService.getInstance(project).isActivated()) { showOpenCodySidebarNotification(project); } } diff --git a/jetbrains/src/main/java/com/sourcegraph/find/browser/JSToJavaBridgeRequestHandler.java b/jetbrains/src/main/java/com/sourcegraph/find/browser/JSToJavaBridgeRequestHandler.java index a0c9e16afce2..0983b93436d4 100644 --- a/jetbrains/src/main/java/com/sourcegraph/find/browser/JSToJavaBridgeRequestHandler.java +++ b/jetbrains/src/main/java/com/sourcegraph/find/browser/JSToJavaBridgeRequestHandler.java @@ -42,7 +42,7 @@ public JBCefJSQuery.Response handle(@NotNull JsonObject request) { try { switch (action) { case "getConfig": - return createSuccessResponse(ConfigUtil.getConfigAsJson()); + return createSuccessResponse(ConfigUtil.getConfigAsJson(project)); case "getTheme": JsonObject currentThemeAsJson = ThemeUtil.getCurrentThemeAsJson(); return createSuccessResponse(currentThemeAsJson); diff --git a/jetbrains/src/main/java/com/sourcegraph/website/FileActionBase.java b/jetbrains/src/main/java/com/sourcegraph/website/FileActionBase.java index 3ae5ce50ccfd..90511b0daf02 100644 --- a/jetbrains/src/main/java/com/sourcegraph/website/FileActionBase.java +++ b/jetbrains/src/main/java/com/sourcegraph/website/FileActionBase.java @@ -40,11 +40,13 @@ public void actionPerformed(@NotNull AnActionEvent event) { LogicalPosition selectionStartPosition = getSelectionStartPosition(editor); LogicalPosition selectionEndPosition = getSelectionEndPosition(editor); + URLBuilder urlBuilder = new URLBuilder(project); + if (currentFile instanceof SourcegraphVirtualFile) { SourcegraphVirtualFile sourcegraphFile = (SourcegraphVirtualFile) currentFile; handleFileUri( project, - URLBuilder.buildSourcegraphBlobUrl( + urlBuilder.buildSourcegraphBlobUrl( sourcegraphFile.getRepoUrl(), sourcegraphFile.getCommit(), sourcegraphFile.getRelativePath(), @@ -68,7 +70,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { // Our "editor" backend doesn't support Perforce, but we have all the info we // need, so we'll go to the final URL directly. url = - URLBuilder.buildSourcegraphBlobUrl( + urlBuilder.buildSourcegraphBlobUrl( repoInfo.getCodeHostUrl() + "/" + repoInfo.getRepoName(), null, repoInfo.relativePath, @@ -76,7 +78,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { selectionEndPosition); } else { url = - URLBuilder.buildEditorFileUrl( + urlBuilder.buildEditorFileUrl( repoInfo.remoteUrl, repoInfo.remoteBranchName, repoInfo.relativePath, @@ -102,12 +104,13 @@ public void actionPerformedFromPreviewContent( handleFileUri( project, - URLBuilder.buildSourcegraphBlobUrl( - previewContent.getRepoUrl(), - previewContent.getCommit(), - previewContent.getPath(), - start, - end)); + new URLBuilder(project) + .buildSourcegraphBlobUrl( + previewContent.getRepoUrl(), + previewContent.getCommit(), + previewContent.getPath(), + start, + end)); } @Nullable diff --git a/jetbrains/src/main/java/com/sourcegraph/website/OpenRevisionAction.java b/jetbrains/src/main/java/com/sourcegraph/website/OpenRevisionAction.java index e2f080842b86..54a07ea2f189 100644 --- a/jetbrains/src/main/java/com/sourcegraph/website/OpenRevisionAction.java +++ b/jetbrains/src/main/java/com/sourcegraph/website/OpenRevisionAction.java @@ -12,10 +12,10 @@ import com.intellij.vcs.log.VcsLogCommitSelection; import com.intellij.vcs.log.VcsLogDataKeys; import com.intellij.vcsUtil.VcsUtil; +import com.sourcegraph.cody.auth.CodyAuthService; import com.sourcegraph.common.BrowserOpener; import com.sourcegraph.common.ErrorNotification; import com.sourcegraph.common.ui.DumbAwareEDTAction; -import com.sourcegraph.config.ConfigUtil; import com.sourcegraph.vcs.RepoUtil; import com.sourcegraph.vcs.RevisionContext; import com.sourcegraph.vcs.VCSType; @@ -89,7 +89,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { try { url = URLBuilder.buildCommitUrl( - ConfigUtil.getServerPath().getUrl(), + CodyAuthService.getInstance(project).getEndpoint().getUrl(), context.getRevisionNumber(), remoteUrl, productName, @@ -97,7 +97,7 @@ public void actionPerformed(@NotNull AnActionEvent event) { } catch (IllegalArgumentException e) { logger.warn( "Unable to build commit view URI for url " - + ConfigUtil.getServerPath().getUrl() + + CodyAuthService.getInstance(project).getEndpoint().getUrl() + ", revision " + context.getRevisionNumber() + ", product " diff --git a/jetbrains/src/main/java/com/sourcegraph/website/SearchActionBase.java b/jetbrains/src/main/java/com/sourcegraph/website/SearchActionBase.java index c802913ead34..e5ecb598a7cb 100644 --- a/jetbrains/src/main/java/com/sourcegraph/website/SearchActionBase.java +++ b/jetbrains/src/main/java/com/sourcegraph/website/SearchActionBase.java @@ -40,11 +40,12 @@ public void actionPerformedMode(@NotNull AnActionEvent event, @NotNull Scope sco VirtualFile currentFile = FileDocumentManager.getInstance().getFile(editor.getDocument()); assert currentFile != null; // selectedText != null, so this can't be null. + URLBuilder urlBuilder = new URLBuilder(project); if (currentFile instanceof SourcegraphVirtualFile) { String url; SourcegraphVirtualFile sourcegraphFile = (SourcegraphVirtualFile) currentFile; String repoUrl = (scope == Scope.REPOSITORY) ? sourcegraphFile.getRepoUrl() : null; - url = URLBuilder.buildEditorSearchUrl(selectedText, repoUrl, null); + url = urlBuilder.buildEditorSearchUrl(selectedText, repoUrl, null); BrowserOpener.INSTANCE.openInBrowser(project, url); } else { // This cannot run on EDT (Event Dispatch Thread) because it may block for a long time. @@ -62,9 +63,9 @@ public void actionPerformedMode(@NotNull AnActionEvent event, @NotNull Scope sco String codeHostUrl = (scope == Scope.REPOSITORY) ? repoInfo.getCodeHostUrl() : null; String repoName = (scope == Scope.REPOSITORY) ? repoInfo.getRepoName() : null; - url = URLBuilder.buildDirectSearchUrl(selectedText, codeHostUrl, repoName); + url = urlBuilder.buildDirectSearchUrl(selectedText, codeHostUrl, repoName); } else { - url = URLBuilder.buildEditorSearchUrl(selectedText, remoteUrl, remoteBranchName); + url = urlBuilder.buildEditorSearchUrl(selectedText, remoteUrl, remoteBranchName); } BrowserOpener.INSTANCE.openInBrowser(project, url); }); diff --git a/jetbrains/src/main/java/com/sourcegraph/website/URLBuilder.java b/jetbrains/src/main/java/com/sourcegraph/website/URLBuilder.java index bafadc384c9c..ae95288705f6 100644 --- a/jetbrains/src/main/java/com/sourcegraph/website/URLBuilder.java +++ b/jetbrains/src/main/java/com/sourcegraph/website/URLBuilder.java @@ -1,6 +1,8 @@ package com.sourcegraph.website; import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.project.Project; +import com.sourcegraph.cody.auth.CodyAuthService; import com.sourcegraph.common.RegexEscaper; import com.sourcegraph.config.ConfigUtil; import java.net.URI; @@ -10,14 +12,20 @@ import org.jetbrains.annotations.Nullable; public class URLBuilder { + Project project; + + public URLBuilder(Project project) { + this.project = project; + } + @NotNull - public static String buildEditorFileUrl( + public String buildEditorFileUrl( @NotNull String remoteUrl, @NotNull String branchName, @NotNull String relativePath, @Nullable LogicalPosition start, @Nullable LogicalPosition end) { - return ConfigUtil.getServerPath().getUrl() + return CodyAuthService.getInstance(project).getEndpoint().getUrl() + "-/editor" + "?remote_url=" + URLEncoder.encode(remoteUrl, StandardCharsets.UTF_8) @@ -42,10 +50,10 @@ public static String buildEditorFileUrl( } @NotNull - public static String buildEditorSearchUrl( + public String buildEditorSearchUrl( @NotNull String search, @Nullable String remoteUrl, @Nullable String remoteBranchName) { String url = - ConfigUtil.getServerPath().getUrl() + CodyAuthService.getInstance(project).getEndpoint().getUrl() + "-/editor" + "?" + buildVersionParams() @@ -63,13 +71,13 @@ public static String buildEditorSearchUrl( } @NotNull - public static String buildDirectSearchUrl( + public String buildDirectSearchUrl( @NotNull String search, @Nullable String codeHost, @Nullable String repoName) { String repoFilter = (codeHost != null && repoName != null) ? "repo:^" + RegexEscaper.INSTANCE.escapeRegexChars(codeHost + "/" + repoName) + "$" : null; - return ConfigUtil.getServerPath().getUrl() + return CodyAuthService.getInstance(project).getEndpoint().getUrl() + "/search" + "?patternType=literal" + "&q=" @@ -117,13 +125,13 @@ public static String buildCommitUrl( @NotNull // repoUrl should be like "github.com/sourcegraph/sourcegraph" - public static String buildSourcegraphBlobUrl( + public String buildSourcegraphBlobUrl( @NotNull String repoUrl, @Nullable String commit, @NotNull String path, @Nullable LogicalPosition start, @Nullable LogicalPosition end) { - return ConfigUtil.getServerPath().getUrl() + return CodyAuthService.getInstance(project).getEndpoint().getUrl() + repoUrl + (commit != null ? "@" + commit : "") + "/-/blob/" diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt index 3cc39bf7fd44..cc01901972d3 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgent.kt @@ -15,6 +15,7 @@ import com.sourcegraph.cody.agent.protocol_generated.ClientInfo import com.sourcegraph.cody.agent.protocol_generated.CodyAgentServer import com.sourcegraph.cody.agent.protocol_generated.ProtocolTypeAdapters import com.sourcegraph.cody.agent.protocol_generated.WebviewNativeConfig +import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.ui.web.WebUIServiceWebviewProvider import com.sourcegraph.cody.vscode.CancellationToken import com.sourcegraph.config.ConfigUtil @@ -114,7 +115,11 @@ private constructor( else -> Debuggability.NotDebuggable } - fun create(project: Project): CompletableFuture { + fun create( + project: Project, + endpoint: SourcegraphServerPath?, + token: String?, + ): CompletableFuture { try { val conn = startAgentProcess() val client = CodyAgentClient(project, WebUIServiceWebviewProvider(project)) @@ -131,7 +136,8 @@ private constructor( workspaceRootUri = ProtocolTextDocumentExt.normalizeUriOrPath( ConfigUtil.getWorkspaceRootPath(project).toUri().toString()), - extensionConfiguration = ConfigUtil.getAgentConfiguration(project), + extensionConfiguration = + ConfigUtil.getAgentConfiguration(project, endpoint, token), capabilities = ClientCapabilities( authentication = ClientCapabilities.AuthenticationEnum.Enabled, @@ -314,15 +320,13 @@ private constructor( // undefined fields. .serializeNulls() // TODO: Once all protocols have migrated we can remove these - // legacy enum - // conversions + // legacy enum conversions .registerTypeAdapter(URI::class.java, uriDeserializer) .registerTypeAdapter(URI::class.java, uriSerializer) ProtocolTypeAdapters.register(gsonBuilder) // This ensures that by default all enums are always serialized to their - // string - // equivalents + // string equivalents string equivalents gsonBuilder.registerTypeAdapterFactory(EnumTypeAdapterFactory()) } } @@ -385,10 +389,9 @@ private constructor( return try { binaryTarget?.toFile()?.deleteOnExit() token.onFinished { - // Important: delete the file from disk after the process exists - // Ideally, we should eventually replace this temporary file with a permanent - // location - // in the plugin directory. + // Important: delete the file from disk after the process exists Ideally, we + // should eventually replace this temporary file with a permanent location in + // the plugin directory. Files.deleteIfExists(binaryTarget) } logger.info("Extracting Node binary to " + binaryTarget.toAbsolutePath()) diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt index cfc2c937a2c8..fc2005bbd9c9 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt @@ -30,13 +30,13 @@ import com.sourcegraph.cody.agent.protocol_generated.TextDocument_ShowParams import com.sourcegraph.cody.agent.protocol_generated.UntitledTextDocument import com.sourcegraph.cody.agent.protocol_generated.Window_DidChangeContextParams import com.sourcegraph.cody.agent.protocol_generated.WorkspaceEditParams -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService +import com.sourcegraph.cody.auth.CodySecureStore import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.edit.EditService import com.sourcegraph.cody.edit.lenses.LensesService import com.sourcegraph.cody.error.CodyConsole import com.sourcegraph.cody.ignore.IgnoreOracle -import com.sourcegraph.cody.statusbar.CodyStatus import com.sourcegraph.cody.statusbar.CodyStatusService import com.sourcegraph.cody.ui.web.NativeWebviewProvider import com.sourcegraph.common.BrowserOpener @@ -142,23 +142,22 @@ class CodyAgentClient(private val project: Project, private val webview: NativeW @JsonRequest("secrets/get") fun secrets_get(params: Secrets_GetParams): CompletableFuture { - return CompletableFuture.completedFuture( - CodyAccount(SourcegraphServerPath(params.key)).getToken()) + return CompletableFuture.completedFuture(CodySecureStore.getFromSecureStore(params.key)) } @JsonRequest("secrets/store") fun secrets_store(params: Secrets_StoreParams): CompletableFuture { - CodyAccount(SourcegraphServerPath(params.key)).storeToken(params.value) + CodySecureStore.writeToSecureStore(params.key, params.value) return CompletableFuture.completedFuture(null) } @JsonRequest("secrets/delete") fun secrets_delete(params: Secrets_DeleteParams): CompletableFuture { - CodyAccount(SourcegraphServerPath(params.key)).storeToken(null) + CodySecureStore.writeToSecureStore(params.key, null) return CompletableFuture.completedFuture(null) } - @JsonRequest("window/showSaveDialog") + @JsonRequest("window/showSaveDialog") fun window_showSaveDialog(params: SaveDialogOptionsParams): CompletableFuture { // Let's use the first possible extension as default. val ext = params.filters?.firstOrNull()?.value?.firstOrNull() ?: "" @@ -192,12 +191,12 @@ class CodyAgentClient(private val project: Project, private val webview: NativeW @JsonNotification("window/didChangeContext") fun window_didChangeContext(params: Window_DidChangeContextParams) { if (params.key == "cody.activated") { - CodyAccount.setActivated(params.value?.toBoolean() ?: false) - CodyStatusService.notifyApplication(project, CodyStatus.CodyNotSignedIn) + CodyAuthService.getInstance(project).setActivated(params.value?.toBoolean() ?: false) + CodyStatusService.resetApplication(project) } if (params.key == "cody.serverEndpoint") { val endpoint = params.value ?: return - CodyAccount.setActiveAccount(CodyAccount(SourcegraphServerPath(endpoint))) + CodyAuthService.getInstance(project).setEndpoint(SourcegraphServerPath(endpoint)) CodyStatusService.resetApplication(project) } } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentService.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentService.kt index c75911e4641c..44b0fd1362d9 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentService.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentService.kt @@ -10,6 +10,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.Project import com.intellij.util.net.HttpConfigurable +import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.config.CodyApplicationSettings import com.sourcegraph.cody.listeners.CodyFileEditorListener import com.sourcegraph.cody.statusbar.CodyStatusService @@ -77,10 +78,22 @@ class CodyAgentService(private val project: Project) : Disposable { } fun startAgent(project: Project, secondsTimeout: Long = 45): CompletableFuture { + // Normally we do not need to specify endpoint or token used for starting an agent. + // Agent will automatically pick up the last used one or the default. + // Custom endpoint and token are used in tests. + return startAgent(project, endpoint = null, token = null, secondsTimeout) + } + + fun startAgent( + project: Project, + endpoint: SourcegraphServerPath?, + token: String?, + secondsTimeout: Long = 45 + ): CompletableFuture { ApplicationManager.getApplication().executeOnPooledThread { try { val future = - CodyAgent.create(project).exceptionally { err -> + CodyAgent.create(project, endpoint, token).exceptionally { err -> val msg = "Creating agent unsuccessful: ${err.localizedMessage}" logger.error(msg) throw (CodyAgentException(msg)) diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt deleted file mode 100644 index 81e206cf98e4..000000000000 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAccount.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.sourcegraph.cody.auth - -import com.intellij.credentialStore.CredentialAttributes -import com.intellij.credentialStore.Credentials -import com.intellij.credentialStore.generateServiceName -import com.intellij.ide.passwordSafe.PasswordSafe -import com.sourcegraph.config.ConfigUtil - -data class CodyAccount(val server: SourcegraphServerPath) { - - fun isDotcomAccount(): Boolean = server.url.lowercase().startsWith(ConfigUtil.DOTCOM_URL) - - fun getToken(): String? { - return PasswordSafe.instance.get(credentialAttributes(server.url))?.getPasswordAsString() - } - - fun storeToken(token: String?) { - PasswordSafe.instance.set(credentialAttributes(server.url), Credentials(user = "", token)) - } - - companion object { - private const val ACTIVE_ACCOUNT_MARKER = "active_cody_account" - - @Volatile private var isActivated: Boolean = false - - private fun credentialAttributes(key: String): CredentialAttributes = - CredentialAttributes(generateServiceName("Sourcegraph", key)) - - fun hasActiveAccount(): Boolean { - return isActivated && getActiveAccount() != null - } - - fun setActivated(isActivated: Boolean) { - this.isActivated = isActivated - } - - fun getActiveAccount(): CodyAccount? { - val serverUrl = - PasswordSafe.instance - .get(credentialAttributes(ACTIVE_ACCOUNT_MARKER)) - ?.getPasswordAsString() - return if (serverUrl == null) null else CodyAccount(SourcegraphServerPath(serverUrl)) - } - - fun setActiveAccount(account: CodyAccount?) { - PasswordSafe.instance.set( - credentialAttributes(ACTIVE_ACCOUNT_MARKER), Credentials(user = "", account?.server?.url)) - } - } -} diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAuthService.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAuthService.kt new file mode 100644 index 000000000000..bb72a641a20f --- /dev/null +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodyAuthService.kt @@ -0,0 +1,37 @@ +package com.sourcegraph.cody.auth + +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project +import com.sourcegraph.config.ConfigUtil + +@Service(Service.Level.PROJECT) +class CodyAuthService(val project: Project) { + + @Volatile private var isActivated: Boolean = false + @Volatile + private var endpoint: SourcegraphServerPath = SourcegraphServerPath(ConfigUtil.DOTCOM_URL) + + fun isActivated(): Boolean { + return isActivated + } + + fun setActivated(isActivated: Boolean) { + this.isActivated = isActivated + } + + fun getEndpoint(): SourcegraphServerPath { + return endpoint + } + + fun setEndpoint(endpoint: SourcegraphServerPath) { + this.endpoint = endpoint + } + + companion object { + @JvmStatic + fun getInstance(project: Project): CodyAuthService { + return project.service() + } + } +} diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodySecureStore.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodySecureStore.kt new file mode 100644 index 000000000000..ee62b2d00bfd --- /dev/null +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/CodySecureStore.kt @@ -0,0 +1,19 @@ +package com.sourcegraph.cody.auth + +import com.intellij.credentialStore.CredentialAttributes +import com.intellij.credentialStore.Credentials +import com.intellij.credentialStore.generateServiceName +import com.intellij.ide.passwordSafe.PasswordSafe + +object CodySecureStore { + private fun credentialAttributes(key: String): CredentialAttributes = + CredentialAttributes(generateServiceName("Sourcegraph", key)) + + fun getFromSecureStore(key: String): String? { + return PasswordSafe.instance.get(credentialAttributes(key))?.getPasswordAsString() + } + + fun writeToSecureStore(key: String, value: String?) { + PasswordSafe.instance.set(credentialAttributes(key), Credentials(user = "", value)) + } +} diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt index 1e3c6f2b80f1..673939b20347 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/auth/SourcegraphServerPath.kt @@ -3,6 +3,7 @@ package com.sourcegraph.cody.auth import com.intellij.util.xmlb.annotations.Attribute import com.intellij.util.xmlb.annotations.Tag import com.sourcegraph.cody.config.SourcegraphParseException +import com.sourcegraph.config.ConfigUtil import java.net.URI import java.util.regex.Pattern @@ -25,6 +26,8 @@ data class SourcegraphServerPath( val displayName: String get() = URI.create(url).host + fun isDotcom(): Boolean = url.lowercase().startsWith(ConfigUtil.DOTCOM_URL) + companion object { // 1 - schema, 2 - host, 4 - port, 5 - path diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt index 6498893e4521..6b47e110b970 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/autocomplete/CodyAutocompleteManager.kt @@ -28,7 +28,7 @@ import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_generated.AutocompleteItem import com.sourcegraph.cody.agent.protocol_generated.AutocompleteResult import com.sourcegraph.cody.agent.protocol_generated.CompletionItemParams -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.autocomplete.Utils.triggerAutocompleteAsync import com.sourcegraph.cody.autocomplete.render.AutocompleteRendererType import com.sourcegraph.cody.autocomplete.render.CodyAutocompleteBlockElementRenderer @@ -156,7 +156,7 @@ class CodyAutocompleteManager { isCommandExcluded(currentCommand)) { return } - if (!CodyAccount.hasActiveAccount()) { + if (!CodyAuthService.getInstance(project).isActivated()) { if (isTriggeredExplicitly) { HintManager.getInstance().showErrorHint(editor, "Cody: Sign in to use autocomplete") CodyToolWindowContent.show(project) diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt index ea3efe267ea0..6cd1705485ca 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/BaseCommandAction.kt @@ -11,7 +11,7 @@ import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_extensions.ProtocolTextDocumentExt import com.sourcegraph.cody.agent.protocol_generated.ExecuteCommandParams import com.sourcegraph.cody.agent.protocol_generated.Ignore_TestResult -import com.sourcegraph.cody.auth.CodyAccount.Companion.hasActiveAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.commands.CommandId import com.sourcegraph.cody.ignore.ActionInIgnoredFileNotification import com.sourcegraph.cody.ignore.IgnoreOracle @@ -30,7 +30,9 @@ abstract class BaseCommandAction : DumbAwareEDTAction() { override fun update(e: AnActionEvent) { super.update(e) - e.presentation.isVisible = isCodyEnabled() && hasActiveAccount() + val project = e.project + e.presentation.isVisible = + isCodyEnabled() && project != null && CodyAuthService.getInstance(project).isActivated() } open fun doAction(project: Project) { diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt index dc2de17179ef..30c793cb7b9d 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/actions/NewChatAction.kt @@ -2,7 +2,7 @@ package com.sourcegraph.cody.chat.actions import com.intellij.openapi.actionSystem.AnActionEvent import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.common.CodyBundle import com.sourcegraph.common.ui.DumbAwareEDTAction @@ -12,7 +12,9 @@ class NewChatAction : DumbAwareEDTAction() { } override fun update(event: AnActionEvent) { - event.presentation.isEnabled = CodyAccount.hasActiveAccount() + val project = event.project + event.presentation.isEnabled = + project != null && CodyAuthService.getInstance(project).isActivated() if (!event.presentation.isEnabled) { event.presentation.description = CodyBundle.getString("action.sourcegraph.disabled.description") diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/ui/LlmDropdown.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/ui/LlmDropdown.kt index 3e5bb254974a..ae5b58524753 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/ui/LlmDropdown.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/chat/ui/LlmDropdown.kt @@ -14,7 +14,7 @@ import com.sourcegraph.cody.agent.protocol_extensions.isDeprecated import com.sourcegraph.cody.agent.protocol_generated.Chat_ModelsParams import com.sourcegraph.cody.agent.protocol_generated.Model import com.sourcegraph.cody.agent.protocol_generated.ModelAvailabilityStatus -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.auth.SourcegraphServerPath import com.sourcegraph.cody.edit.EditCommandPrompt import com.sourcegraph.cody.ui.LlmComboBoxRenderer @@ -69,7 +69,8 @@ class LlmDropdown( val availableModels = models.map { it }.filterNot { it.model.isDeprecated() } availableModels.sortedBy { it.model.isCodyProOnly() }.forEach { addItem(it) } - val defaultLlm = serverToRecentModel[CodyAccount.getActiveAccount()?.server] + val endpoint = CodyAuthService.getInstance(project).getEndpoint() + val defaultLlm = serverToRecentModel[endpoint] selectedItem = availableModels.find { it.model.id == fixedModel || it.model.id == defaultLlm?.id } @@ -96,7 +97,8 @@ class LlmDropdown( return } - CodyAccount.getActiveAccount()?.server?.also { serverToRecentModel[it] = modelProvider.model } + val endpoint = CodyAuthService.getInstance(project).getEndpoint() + serverToRecentModel[endpoint] = modelProvider.model super.setSelectedItem(anObject) onSetSelectedItem(modelProvider.model) diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/CodySettingsFileChangeListener.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/CodySettingsFileChangeListener.kt index 99adf606fecc..91a78c37bd8c 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/CodySettingsFileChangeListener.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/CodySettingsFileChangeListener.kt @@ -20,11 +20,12 @@ class CodySettingsFileChangeListener(private val project: Project) : FileDocumen LocalFileSystem.getInstance() .refreshAndFindFileByNioFile(ConfigUtil.getSettingsFile(project)) if (currentFile == configFile) { - // TODO: it seams that some of the settings changes (like enabling/disabling autocomplete) + // TODO: it seams that some of the settings changes (like enabling/disabling + // autocomplete) // requires agent restart to take effect. CodyAgentService.withAgentRestartIfNeeded(project) { it.server.extensionConfiguration_change( - ConfigUtil.getAgentConfiguration(project, document.text)) + ConfigUtil.getAgentConfiguration(project, customConfigContent = document.text)) } } } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt index 67fa13ca8555..9426f728b995 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/AccountsMigration.kt @@ -1,6 +1,6 @@ package com.sourcegraph.cody.config.migration -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodySecureStore import com.sourcegraph.cody.auth.deprecated.DeprecatedCodyAccountManager object AccountsMigration { @@ -9,10 +9,8 @@ object AccountsMigration { codyAccountManager.getAccounts().forEach { oldAccount -> val token = codyAccountManager.getTokenForAccount(oldAccount) if (token != null) { - CodyAccount(oldAccount.server).storeToken(token) + CodySecureStore.writeToSecureStore(oldAccount.server.url, token) } } - - codyAccountManager.account?.server?.let { CodyAccount.setActiveAccount(CodyAccount(it)) } } } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt index 2c259a82f869..35e467ad76ab 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/migration/SettingsMigration.kt @@ -69,9 +69,8 @@ class SettingsMigration : Activity { RunOnceUtil.runOnceForProject(project, "CodyMigrateChatHistory-v2") { ChatHistoryMigration.migrate(project) } - RunOnceUtil.runOnceForProject(project, "AccountsToCodyMigration") { - AccountsMigration.migrate() - } + + RunOnceUtil.runOnceForApp("AccountsToCodyMigration") { AccountsMigration.migrate() } } private fun migrateOrphanedChatsToActiveAccount(project: Project) { diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/notification/CodySettingChangeListener.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/notification/CodySettingChangeListener.kt index 3582f9d09858..ad13b5ca7c1e 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/notification/CodySettingChangeListener.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/config/notification/CodySettingChangeListener.kt @@ -20,7 +20,7 @@ class CodySettingChangeListener(project: Project) : ChangeListener(project) { object : CodySettingChangeActionNotifier { override fun afterAction(context: CodySettingChangeContext) { // Notify JCEF about the config changes - javaToJSBridge?.callJS("pluginSettingsChanged", ConfigUtil.getConfigAsJson()) + javaToJSBridge?.callJS("pluginSettingsChanged", ConfigUtil.getConfigAsJson(project)) if (context.oldCodyEnabled != context.newCodyEnabled) { if (context.newCodyEnabled) { diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt index 60e4dbcd5c9c..cc7aec939551 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/actions/BaseEditCodeAction.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.project.Project import com.sourcegraph.cody.CodyToolWindowContent import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_generated.Ignore_TestResult -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.autocomplete.action.CodyAction import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.common.CodyBundle @@ -39,7 +39,7 @@ open class BaseEditCodeAction(runAction: (Editor) -> Unit) : false to CodyBundle.getString("action.cody.not-working") } else if (isBlockedByPolicy(project, event)) { false to CodyBundle.getString("filter.action-in-ignored-file.detail") - } else if (!CodyAccount.hasActiveAccount()) { + } else if (!CodyAuthService.getInstance(project).isActivated()) { false to CodyBundle.getString("action.sourcegraph.disabled.description") } else { true to "" diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt index 215f5f24bce4..0e8eb8d2ceb1 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/edit/lenses/actions/LensEditAction.kt @@ -9,7 +9,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.Project -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.edit.lenses.LensesService import com.sourcegraph.common.CodyBundle @@ -22,7 +22,9 @@ abstract class LensEditAction(val editAction: (Project, AnActionEvent, Editor, S } override fun update(event: AnActionEvent) { - event.presentation.isEnabled = CodyAccount.hasActiveAccount() + val project = event.project + event.presentation.isEnabled = + project != null && CodyAuthService.getInstance(project).isActivated() if (!event.presentation.isEnabled) { event.presentation.description = CodyBundle.getString("action.sourcegraph.disabled.description") diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt index 23100a09cb5c..279345a78f0c 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/ignore/ActionInIgnoredFileNotification.kt @@ -10,7 +10,7 @@ import com.intellij.openapi.application.runInEdt import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.sourcegraph.Icons -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.statusbar.CodyStatus import com.sourcegraph.cody.statusbar.CodyStatusService import com.sourcegraph.common.CodyBundle @@ -31,7 +31,7 @@ class ActionInIgnoredFileNotification : fun maybeNotify(project: Project) { val status = CodyStatusService.getCurrentStatus(project) - val account = CodyAccount.getActiveAccount() + val endpoint = CodyAuthService.getInstance(project).getEndpoint() when { status == CodyStatus.CodyUninit || status == CodyStatus.CodyDisabled || @@ -40,12 +40,12 @@ class ActionInIgnoredFileNotification : status == CodyStatus.CodyAgentNotRunning || status == CodyStatus.AgentError || status == CodyStatus.RateLimitError -> { - // Do nothing. These errors are not related to context filters; displaying them is handled - // by the status - // bar widget. + // Do nothing. These errors are not related to context filters; displaying them + // is handled by the status bar widget. } - account?.isDotcomAccount() == true -> { - // Show nothing. We do not use context filters on sourcegraph.com; should be unreachable. + endpoint.isDotcom() == true -> { + // Show nothing. We do not use context filters on sourcegraph.com; should be + // unreachable. log.warn( "got 'action in ignored file' notification with a dotcom account, which should be unreachable") } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt deleted file mode 100644 index da19bd3d23dd..000000000000 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/EndOfTrialNotificationScheduler.kt +++ /dev/null @@ -1,103 +0,0 @@ -package com.sourcegraph.cody.initialization - -import com.intellij.ide.util.PropertiesComponent -import com.intellij.openapi.Disposable -import com.intellij.openapi.diagnostic.Logger -import com.intellij.openapi.project.Project -import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.agent.protocol.GetFeatureFlag -import com.sourcegraph.cody.agent.protocol_extensions.isPendingStatus -import com.sourcegraph.cody.agent.protocol_extensions.isProPlan -import com.sourcegraph.cody.agent.protocol_generated.CurrentUserCodySubscription -import com.sourcegraph.cody.auth.CodyAccount -import com.sourcegraph.config.ConfigUtil -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit - -class EndOfTrialNotificationScheduler private constructor(val project: Project) : Disposable { - - private val logger = Logger.getInstance(EndOfTrialNotificationScheduler::class.java) - - private val scheduler = Executors.newScheduledThreadPool(1) - - init { - scheduler.scheduleAtFixedRate( - /* command = */ { - if (!ConfigUtil.isCodyEnabled()) { - return@scheduleAtFixedRate - } - - if (project.isDisposed) { - this.dispose() - } - - if (CodyAccount.getActiveAccount()?.isDotcomAccount() != true) { - return@scheduleAtFixedRate - } - - CodyAgentService.withAgentRestartIfNeeded(project) { agent -> - val currentUserCodySubscription = - agent.server - .graphql_getCurrentUserCodySubscription(null) - .completeOnTimeout(null, 4, TimeUnit.SECONDS) - .exceptionally { e -> - logger.warn("Error while getting currentUserCodySubscription ", e) - null - } - .get() - - if (currentUserCodySubscription == null) { - logger.debug("currentUserCodySubscription is null") - return@withAgentRestartIfNeeded - } - - val codyProTrialEnded = - agent.server - .featureFlags_getFeatureFlag(GetFeatureFlag.CodyProTrialEnded) - .completeOnTimeout(false, 4, TimeUnit.SECONDS) - .get() == true - - val useSscForCodySubscription = - agent.server - .featureFlags_getFeatureFlag(GetFeatureFlag.UseSscForCodySubscription) - .orTimeout(4, TimeUnit.SECONDS) - .completeOnTimeout(false, 4, TimeUnit.SECONDS) - .get() == true - - showProperNotificationIfApplicable( - currentUserCodySubscription, codyProTrialEnded, useSscForCodySubscription) - } - }, - /* initialDelay = */ 0, - /* period = */ 2, - /* unit = */ TimeUnit.HOURS) - } - - private fun showProperNotificationIfApplicable( - currentUserCodySubscription: CurrentUserCodySubscription, - codyProTrialEnded: Boolean, - useSscForCodySubscription: Boolean - ) { - if (currentUserCodySubscription.isProPlan() && - currentUserCodySubscription.isPendingStatus() && - useSscForCodySubscription) { - if (codyProTrialEnded) { - if (PropertiesComponent.getInstance().getBoolean(TrialEndedNotification.ignore)) { - dispose() - return - } - TrialEndedNotification(disposable = this).notify(project) - } - } - } - - override fun dispose() { - scheduler.shutdown() - } - - companion object { - fun createAndStart(project: Project): EndOfTrialNotificationScheduler { - return EndOfTrialNotificationScheduler(project) - } - } -} diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt index 530ce5dce902..c0710d39fcf0 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/initialization/PostStartupActivity.kt @@ -36,12 +36,10 @@ class PostStartupActivity : ProjectActivity { // Scheduling because this task takes ~2s to run CheckUpdatesTask(project).queue() } - // For integration tests we do not want to start agent immediately as we would like to first do - // some setup. Also, we do not start EndOfTrialNotificationScheduler as its timing is hard to - // control and can introduce unnecessary noise in the recordings + // For integration tests we do not want to start agent immediately as we would like to first + // do some setup. if (ConfigUtil.isCodyEnabled() && !ConfigUtil.isIntegrationTestModeEnabled()) { CodyAgentService.getInstance(project).startAgent(project) - EndOfTrialNotificationScheduler.createAndStart(project) } CodyStatusService.resetApplication(project) diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt index 07a4f89e246d..cf40f979fa79 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/listeners/CodySelectionInlayManager.kt @@ -13,7 +13,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer import com.sourcegraph.cody.agent.CodyAgentService import com.sourcegraph.cody.agent.protocol_generated.Ignore_TestResult -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.config.ConfigUtil import com.sourcegraph.utils.CodyEditorUtil @@ -37,7 +37,7 @@ class CodySelectionInlayManager(val project: Project) { !CodyAgentService.isConnected(project) || !ConfigUtil.isCodyUIHintsEnabled() || !CodyEditorUtil.isEditorValidForAutocomplete(editor) || - !CodyAccount.hasActiveAccount() || + !CodyAuthService.getInstance(project).isActivated() || IgnoreOracle.getInstance(project).policyForEditor(editor) != Ignore_TestResult.PolicyEnum.Use) { return diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt index 2843a0be462f..29c62e04380d 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableAutocompleteAction.kt @@ -1,7 +1,7 @@ package com.sourcegraph.cody.statusbar import com.intellij.openapi.actionSystem.AnActionEvent -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.autocomplete.CodyAutocompleteManager import com.sourcegraph.cody.config.CodyApplicationSettings import com.sourcegraph.common.ui.DumbAwareEDTAction @@ -15,9 +15,11 @@ class CodyDisableAutocompleteAction : DumbAwareEDTAction("Disable Cody Autocompl override fun update(e: AnActionEvent) { super.update(e) + val project = e.project e.presentation.isEnabledAndVisible = ConfigUtil.isCodyEnabled() && + project != null && ConfigUtil.isCodyAutocompleteEnabled() && - CodyAccount.hasActiveAccount() + CodyAuthService.getInstance(project).isActivated() } } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt index 89b592832495..a97820d1637d 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyDisableLanguageForAutocompleteAction.kt @@ -1,7 +1,7 @@ package com.sourcegraph.cody.statusbar import com.intellij.openapi.actionSystem.AnActionEvent -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.autocomplete.CodyAutocompleteManager import com.sourcegraph.cody.config.CodyApplicationSettings import com.sourcegraph.common.ui.DumbAwareEDTAction @@ -25,12 +25,14 @@ class CodyDisableLanguageForAutocompleteAction : DumbAwareEDTAction() { val isLanguageBlacklisted = languageForFocusedEditor?.let { CodyLanguageUtil.isLanguageBlacklisted(it) } ?: false val languageName = languageForFocusedEditor?.displayName ?: "" + val project = e.project e.presentation.isEnabledAndVisible = languageForFocusedEditor != null && ConfigUtil.isCodyEnabled() && ConfigUtil.isCodyAutocompleteEnabled() && + project != null && !isLanguageBlacklisted && - CodyAccount.hasActiveAccount() + CodyAuthService.getInstance(project).isActivated() e.presentation.text = "Disable Cody Autocomplete for $languageName" } } diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt index eac2dd2a5595..0aabd88159a3 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/statusbar/CodyStatusService.kt @@ -8,7 +8,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.util.ui.UIUtil import com.sourcegraph.cody.agent.CodyAgentService -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.cody.ignore.IgnoreOracle import com.sourcegraph.common.UpgradeToCodyProNotification import com.sourcegraph.config.ConfigUtil @@ -55,7 +55,7 @@ class CodyStatusService(val project: Project) : Disposable { CodyStatus.AgentError } else if (!CodyAgentService.isConnected(project)) { CodyStatus.CodyAgentNotRunning - } else if (!CodyAccount.hasActiveAccount()) { + } else if (!CodyAuthService.getInstance(project).isActivated()) { CodyStatus.CodyNotSignedIn } else if (UpgradeToCodyProNotification.autocompleteRateLimitError.get() != null || UpgradeToCodyProNotification.chatRateLimitError.get() != null) { diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/common/BrowserOpener.kt b/jetbrains/src/main/kotlin/com/sourcegraph/common/BrowserOpener.kt index 1ddb7c4c8e91..2b50d7e79515 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/common/BrowserOpener.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/common/BrowserOpener.kt @@ -3,8 +3,8 @@ package com.sourcegraph.common import com.intellij.ide.BrowserUtil import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.project.Project +import com.sourcegraph.cody.auth.CodyAuthService import com.sourcegraph.common.BrowserErrorNotification.show -import com.sourcegraph.config.ConfigUtil.getServerPath import java.awt.Desktop import java.io.IOException import java.net.URI @@ -12,7 +12,8 @@ import java.net.URISyntaxException object BrowserOpener { fun openRelativeUrlInBrowser(project: Project, relativeUrl: String) { - openInBrowser(project, getServerPath().url + "/" + relativeUrl) + openInBrowser( + project, CodyAuthService.getInstance(project).getEndpoint().url + "/" + relativeUrl) } fun openInBrowser(project: Project?, absoluteUrl: String) { diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt b/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt index 077d310908c3..96820252a0de 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt @@ -12,9 +12,9 @@ import com.intellij.openapi.fileEditor.TextEditor import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.sourcegraph.cody.agent.protocol_generated.ExtensionConfiguration -import com.sourcegraph.cody.auth.CodyAccount +import com.sourcegraph.cody.auth.CodyAuthService +import com.sourcegraph.cody.auth.CodySecureStore import com.sourcegraph.cody.auth.SourcegraphServerPath -import com.sourcegraph.cody.auth.SourcegraphServerPath.Companion.from import com.sourcegraph.cody.config.CodyApplicationSettings import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigRenderOptions @@ -90,14 +90,15 @@ object ConfigUtil { @JvmStatic fun getAgentConfiguration( project: Project, + endpoint: SourcegraphServerPath? = null, + token: String? = null, customConfigContent: String? = null ): ExtensionConfiguration { - val account = CodyAccount.getActiveAccount() return ExtensionConfiguration( anonymousUserID = CodyApplicationSettings.instance.anonymousUserId, - serverEndpoint = account?.server?.url ?: "", - accessToken = account?.getToken() ?: "", + serverEndpoint = endpoint?.url ?: "", + accessToken = token, customHeaders = emptyMap(), proxy = UserLevelConfig.getProxy(), autocompleteAdvancedProvider = @@ -109,23 +110,17 @@ object ConfigUtil { } @JvmStatic - fun getConfigAsJson(): JsonObject { - val account = CodyAccount.getActiveAccount() + fun getConfigAsJson(project: Project): JsonObject { + val endpoint = CodyAuthService.getInstance(project).getEndpoint() return JsonObject().apply { - addProperty("instanceURL", account?.server?.url) - addProperty("accessToken", account?.getToken()) + addProperty("instanceURL", endpoint.url) + addProperty("accessToken", CodySecureStore.getFromSecureStore(endpoint.url.toString())) addProperty("customRequestHeadersAsString", "") addProperty("pluginVersion", getPluginVersion()) addProperty("anonymousUserId", CodyApplicationSettings.instance.anonymousUserId) } } - @JvmStatic - fun getServerPath(): SourcegraphServerPath { - val activeAccount = CodyAccount.getActiveAccount() - return activeAccount?.server ?: from(DOTCOM_URL, "") - } - @JvmStatic fun shouldConnectToDebugAgent() = System.getenv("CODY_AGENT_DEBUG_REMOTE") == "true" @JvmStatic @@ -206,9 +201,9 @@ object ConfigUtil { @JvmStatic fun getWorkspaceRoot(project: Project): String { - // The base path should only be null for the default project. The agent server assumes that the - // workspace root is not null, so we have to provide some default value. Feel free to change to - // something else than the home directory if this is causing problems. + // The base path should only be null for the default project. The agent server assumes that + // the workspace root is not null, so we have to provide some default value. Feel free to + // change to something else than the home directory if this is causing problems. return project.basePath ?: System.getProperty("user.home") } From fc591a29a7a6fae8a418541fcfe24ae11f34aaec Mon Sep 17 00:00:00 2001 From: Piotr Kukielka Date: Fri, 10 Jan 2025 12:37:32 +0100 Subject: [PATCH 2/2] Add agent test --- .../ExtensionConfiguration.kt | 2 +- agent/src/TestClient.ts | 2 +- agent/src/agent.ts | 2 +- agent/src/unauthed.test.ts | 19 +++++++++++++++++++ .../sourcegraph/cody/agent/CodyAgentClient.kt | 2 +- .../com/sourcegraph/config/ConfigUtil.kt | 2 +- vscode/src/jsonrpc/agent-protocol.ts | 2 +- 7 files changed, 25 insertions(+), 6 deletions(-) diff --git a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ExtensionConfiguration.kt b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ExtensionConfiguration.kt index 648aa71d6e7f..89ee43e2cb03 100644 --- a/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ExtensionConfiguration.kt +++ b/agent/bindings/kotlin/lib/src/main/kotlin/com/sourcegraph/cody/agent/protocol_generated/ExtensionConfiguration.kt @@ -2,7 +2,7 @@ package com.sourcegraph.cody.agent.protocol_generated; data class ExtensionConfiguration( - val serverEndpoint: String, + val serverEndpoint: String? = null, val proxy: String? = null, val accessToken: String? = null, val customHeaders: Map, diff --git a/agent/src/TestClient.ts b/agent/src/TestClient.ts index 60afa27f7d2f..ff961c161916 100644 --- a/agent/src/TestClient.ts +++ b/agent/src/TestClient.ts @@ -116,7 +116,6 @@ export function setupRecording(): void { } export class TestClient extends MessageHandler { - private secrets = new AgentStatelessSecretStorage() private extensionConfigurationDuringInitialization: ExtensionConfiguration | undefined public static create({ bin = 'node', ...params }: TestClientParams): TestClient { setupRecording() @@ -180,6 +179,7 @@ export class TestClient extends MessageHandler { public workspaceEditParams: WorkspaceEditParams[] = [] public textDocumentEditParams: TextDocumentEditParams[] = [] public expectedEvents: string[] = [] + public secrets = new AgentStatelessSecretStorage() get serverEndpoint(): string { return this.params.credentials.serverEndpoint diff --git a/agent/src/agent.ts b/agent/src/agent.ts index 9e48557a2eff..1bad13eda865 100644 --- a/agent/src/agent.ts +++ b/agent/src/agent.ts @@ -1492,7 +1492,7 @@ export class Agent extends MessageHandler implements ExtensionClient { // If this is an authentication change we need to reauthenticate prior to firing events // that update the clients try { - if (isAuthChange || params?.forceAuthentication) { + if ((isAuthChange || params?.forceAuthentication) && config.serverEndpoint) { await authProvider.validateAndStoreCredentials( { configuration: { diff --git a/agent/src/unauthed.test.ts b/agent/src/unauthed.test.ts index e739360d73a9..0c4a688bdc67 100644 --- a/agent/src/unauthed.test.ts +++ b/agent/src/unauthed.test.ts @@ -35,6 +35,25 @@ describe.skip( expect(authStatus?.endpoint).toBe(TESTING_CREDENTIALS.dotcomUnauthed.serverEndpoint) }) + it('starts up with default andpoint and credentials if they are present in the secure store', async () => { + const newClient = TestClient.create({ + workspaceRootUri: workspace.rootUri, + name: 'unauthed', + credentials: TESTING_CREDENTIALS.dotcomUnauthed, + }) + + newClient.secrets.store( + TESTING_CREDENTIALS.dotcom.serverEndpoint, + TESTING_CREDENTIALS.dotcom.token ?? 'invalid' + ) + + await newClient.beforeAll({ serverEndpoint: undefined }, { expectAuthenticated: true }) + const authStatus = await newClient.request('extensionConfiguration/status', null) + expect(authStatus?.authenticated).toBe(true) + expect(authStatus?.endpoint).toBe(TESTING_CREDENTIALS.dotcom.serverEndpoint) + newClient.afterAll() + }) + it('authenticates to same endpoint using valid credentials', async () => { const authStatus = await client.request('extensionConfiguration/change', { ...client.info.extensionConfiguration, diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt index fc2005bbd9c9..89dcaa972362 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/cody/agent/CodyAgentClient.kt @@ -157,7 +157,7 @@ class CodyAgentClient(private val project: Project, private val webview: NativeW return CompletableFuture.completedFuture(null) } - @JsonRequest("window/showSaveDialog") + @JsonRequest("window/showSaveDialog") fun window_showSaveDialog(params: SaveDialogOptionsParams): CompletableFuture { // Let's use the first possible extension as default. val ext = params.filters?.firstOrNull()?.value?.firstOrNull() ?: "" diff --git a/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt b/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt index 96820252a0de..66792d315f48 100644 --- a/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt +++ b/jetbrains/src/main/kotlin/com/sourcegraph/config/ConfigUtil.kt @@ -97,7 +97,7 @@ object ConfigUtil { return ExtensionConfiguration( anonymousUserID = CodyApplicationSettings.instance.anonymousUserId, - serverEndpoint = endpoint?.url ?: "", + serverEndpoint = endpoint?.url, accessToken = token, customHeaders = emptyMap(), proxy = UserLevelConfig.getProxy(), diff --git a/vscode/src/jsonrpc/agent-protocol.ts b/vscode/src/jsonrpc/agent-protocol.ts index c912cc74b149..4cfff43ef980 100644 --- a/vscode/src/jsonrpc/agent-protocol.ts +++ b/vscode/src/jsonrpc/agent-protocol.ts @@ -558,7 +558,7 @@ export interface ServerInfo { } export interface ExtensionConfiguration { - serverEndpoint: string + serverEndpoint?: string | undefined | null proxy?: string | undefined | null accessToken?: string | undefined | null customHeaders: Record