@@ -3,6 +3,7 @@ package com.coder.toolbox.util
33import com.coder.toolbox.CoderToolboxContext
44import com.coder.toolbox.cli.CoderCLIManager
55import com.coder.toolbox.feed.IdeFeedManager
6+ import com.coder.toolbox.feed.IdeType
67import com.coder.toolbox.models.WorkspaceAndAgentStatus
78import com.coder.toolbox.sdk.CoderRestClient
89import com.coder.toolbox.sdk.v2.models.Workspace
@@ -27,7 +28,7 @@ private const val CAN_T_HANDLE_URI_TITLE = "Can't handle URI"
2728@Suppress(" UnstableApiUsage" )
2829open class CoderProtocolHandler (
2930 private val context : CoderToolboxContext ,
30- ideFeedManager : IdeFeedManager ,
31+ private val ideFeedManager : IdeFeedManager ,
3132) {
3233 private val settings = context.settingsStore.readOnly()
3334
@@ -236,72 +237,136 @@ open class CoderProtocolHandler(
236237 private fun launchIde (
237238 environmentId : String ,
238239 productCode : String ,
239- buildNumber : String ,
240+ buildNumberHint : String ,
240241 projectFolder : String?
241242 ) {
242243 context.cs.launch(CoroutineName (" Launch Remote IDE" )) {
243- val selectedIde = selectAndInstallRemoteIde(productCode, buildNumber, environmentId) ? : return @launch
244- context.logger.info(" $productCode -$buildNumber is already on $environmentId . Going to launch JBClient" )
244+ val selectedIde = selectAndInstallRemoteIde(productCode, buildNumberHint, environmentId) ? : return @launch
245+ context.logger.info(" Selected IDE $selectedIde for $productCode with hint $buildNumberHint " )
246+
247+ // Ensure JBClient is prepared (installed/downloaded locally)
245248 installJBClient(selectedIde, environmentId).join()
249+
250+ // Launch
246251 launchJBClient(selectedIde, environmentId, projectFolder)
247252 }
248253 }
249254
250255 private suspend fun selectAndInstallRemoteIde (
251256 productCode : String ,
252- buildNumber : String ,
257+ buildNumberHint : String ,
253258 environmentId : String
254259 ): String? {
255- val installedIdes = context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
260+ val selectedIdeVersion = resolveIdeIdentifier(environmentId, productCode, buildNumberHint) ? : return null
261+ val installedIdeVersions = context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
262+
263+ if (installedIdeVersions.contains(selectedIdeVersion)) {
264+ context.logger.info(" $selectedIdeVersion is already installed on $environmentId " )
265+ return selectedIdeVersion
266+ }
267+
268+ val selectedIde = " $productCode -$selectedIdeVersion "
269+ context.logger.info(" Installing $selectedIde on $environmentId ..." )
270+ context.remoteIdeOrchestrator.installRemoteTool(environmentId, selectedIde)
256271
257- var selectedIde = " $productCode -$buildNumber "
258- if (installedIdes.firstOrNull { it.contains(buildNumber) } != null ) {
259- context.logger.info(" $selectedIde is already installed on $environmentId " )
272+ if (context.remoteIdeOrchestrator.waitForIdeToBeInstalled(environmentId, selectedIde)) {
273+ context.logger.info(" Successfully installed $selectedIdeVersion on $environmentId ." )
260274 return selectedIde
275+ } else {
276+ context.ui.showSnackbar(
277+ UUID .randomUUID().toString(),
278+ context.i18n.pnotr(" $selectedIde could not be installed" ),
279+ context.i18n.pnotr(" $selectedIde could not be installed on time. Check the logs for more details" ),
280+ context.i18n.ptrl(" OK" )
281+ )
282+ return null
261283 }
284+ }
262285
263- selectedIde = resolveAvailableIde(environmentId, productCode, buildNumber) ? : return null
286+ /* *
287+ * Resolves the full IDE identifier (e.g., "RR-241.14494.240") based on the build hint.
288+ * Supports: latest_eap, latest_release, latest_installed, or specific build number.
289+ */
290+ internal suspend fun resolveIdeIdentifier (
291+ environmentId : String ,
292+ productCode : String ,
293+ buildNumberHint : String
294+ ): String? {
295+ val availableBuilds = context.remoteIdeOrchestrator.getAvailableRemoteTools(environmentId, productCode)
296+
297+ when (buildNumberHint) {
298+ " latest_eap" -> {
299+ // Use IdeFeedManager to find best EAP match from available builds
300+ val bestEap = ideFeedManager.findBestMatch(
301+ productCode,
302+ IdeType .EAP ,
303+ availableBuilds
304+ )
264305
265- // needed otherwise TBX will install it again
266- if (! installedIdes.contains(selectedIde)) {
267- context.logger.info(" Installing $selectedIde on $environmentId ..." )
268- context.remoteIdeOrchestrator.installRemoteTool(environmentId, selectedIde)
306+ return if (bestEap != null ) {
307+ bestEap.build
308+ } else {
309+ // Fallback to latest available if valid
310+ if (availableBuilds.isEmpty()) {
311+ context.logAndShowError(
312+ CAN_T_HANDLE_URI_TITLE ,
313+ " $productCode is not available on $environmentId "
314+ )
315+ return null
316+ }
317+ val fallback = availableBuilds.maxBy { it }
318+ context.logger.info(" No EAP found for $productCode , falling back to latest available: $fallback " )
319+ fallback
320+ }
321+ }
269322
270- if (context.remoteIdeOrchestrator.waitForIdeToBeInstalled(environmentId, selectedIde)) {
271- context.logger.info(" Successfully installed $selectedIde on $environmentId ..." )
272- return selectedIde
273- } else {
274- context.ui.showSnackbar(
275- UUID .randomUUID().toString(),
276- context.i18n.pnotr(" $selectedIde could not be installed" ),
277- context.i18n.pnotr(" $selectedIde could not be installed on time. Check the logs for more details" ),
278- context.i18n.ptrl(" OK" )
323+ " latest_release" -> {
324+ val bestRelease = ideFeedManager.findBestMatch(
325+ productCode,
326+ IdeType .RELEASE ,
327+ availableBuilds
279328 )
280- return null
329+
330+ return if (bestRelease != null ) {
331+ bestRelease.build
332+ } else {
333+ // Fallback to latest available if valid
334+ if (availableBuilds.isEmpty()) {
335+ context.logAndShowError(
336+ CAN_T_HANDLE_URI_TITLE ,
337+ " $productCode is not available on $environmentId "
338+ )
339+ return null
340+ }
341+ val fallback = availableBuilds.maxBy { it }
342+ context.logger.info(" No Release found for $productCode , falling back to latest available: $fallback " )
343+ fallback
344+ }
281345 }
282- } else {
283- context.logger.info(" $selectedIde is already present on $environmentId ..." )
284- return selectedIde
285- }
286- }
287346
288- private suspend fun resolveAvailableIde (environmentId : String , productCode : String , buildNumber : String ): String? {
289- val availableVersions = context
290- .remoteIdeOrchestrator
291- .getAvailableRemoteTools(environmentId, productCode)
347+ " latest_installed" -> {
348+ val installed = context.remoteIdeOrchestrator.getInstalledRemoteTools(environmentId, productCode)
349+ if (installed.isNotEmpty()) {
350+ return installed.maxBy { it }
351+ }
352+ // Fallback to latest available if valid
353+ if (availableBuilds.isEmpty()) {
354+ context.logAndShowError(CAN_T_HANDLE_URI_TITLE , " $productCode is not available on $environmentId " )
355+ return null
356+ }
357+ val fallback = availableBuilds.maxBy { it }
358+ context.logger.info(" No installed IDE found, falling back to latest available: $fallback " )
359+ return fallback
360+ }
292361
293- if (availableVersions.isEmpty()) {
294- context.logAndShowError( CAN_T_HANDLE_URI_TITLE , " $productCode is not available on $environmentId " )
295- return null
296- }
362+ else -> {
363+ // Specific build number
364+ // Check if exact match exists in available or installed (implicitly handled by install check later)
365+ // Often the input buildNumber might be just "241" or "241.1234", but full build version is in the form of 241.1234.234"
297366
298- val buildNumberIsNotAvailable = availableVersions.firstOrNull { it.contains(buildNumber) } == null
299- if (buildNumberIsNotAvailable) {
300- val selectedIde = availableVersions.maxOf { it }
301- context.logger.info(" $productCode -$buildNumber is not available, we've selected the latest $selectedIde " )
302- return selectedIde
367+ return availableBuilds.filter { it.contains(buildNumberHint) }.maxByOrNull { it }
368+ }
303369 }
304- return " $productCode -$buildNumber "
305370 }
306371
307372 private fun installJBClient (selectedIde : String , environmentId : String ): Job =
@@ -340,7 +405,9 @@ open class CoderProtocolHandler(
340405 withTimeout(waitTime.toJavaDuration()) {
341406 while (! isInstalled) {
342407 delay(5 .seconds)
343- isInstalled = getInstalledRemoteTools(environmentId, ideHint).isNotEmpty()
408+ val installed = getInstalledRemoteTools(environmentId, ideHint) // Hint matching
409+ // Check if *specific* IDE is installed now
410+ isInstalled = installed.contains(ideHint) || installed.any { it.contains(ideHint) }
344411 }
345412 }
346413 return true
0 commit comments