Skip to content

Commit 6bb0599

Browse files
authored
impl: support for Toolbox 2.6.3 (#124)
Toolbox 2.6.3 comes with a couple of new additions in the API which need the following changes: - finish support for URI handling. The available API up to TBX 2.6.3 was buggy in terms of URI handling. It didn't allow plugins to programmatically install remote ides and launch them. The launch operation only worked when the IDE was already installed and a project was already opened with the IDE. TBX 2.6.3 adds a new API, _RemoteToolboxHelp_ which provides routines for listing the available IDEs on the remote, what is already installed and a command to install specific versions of the IDE. Additionally, there were fixes provided to the existing _ClientHelper_ which now launches the JBClient if a project was not specified. An additional quirk I've discovered is that if we provide a project, and that project was not already opened (present in the Projects tab) the IDE still won't open. And there is no API available to query the available projects. This PR uses the new API to: - query the installed ides - check if the provided ide is in the list of already installed IDEs. - if that's not the case we query the available list of IDEs and the available versions - if the provided ide and build no., is in the available list we will schedule it for install - if not, we select the latest available build number for the provided product code. - wait for the remote IDE to be installed - and then download and launch the JBClient with a project path if it was provided. - update the minimum API requirement. Toolbox API is upgraded to 1.1.41749 which comes with new API additions and some deprecations. Kotlin stdlib was also increased to a newer patch version - use new environment state API. The _CustomRemoteEnvironmentState_ is deprecated, and replaced by a new class _CustomRemoteEnvironmentStateV2_ which now supports i18n state labels - use the new ssh disconnect callback. Toolbox provides two callbacks, one before an SSH connection is established and another one which executes when the ssh connection is stopped. The latter was deprecated in the favor of a new callback that also provides hints on whether the user requested the disconnect. - use the new delete callback API. Toolbox provides a callback for scenarios that involve the env. deletion. This allows plugins to react and clean the internal state. With the new TBX API, the delete callback API is deprecated in the favor of a mutable state flow, a reactive approach that allows consumers to observe and react to state changes over time.
1 parent 09ecfcf commit 6bb0599

File tree

16 files changed

+449
-342
lines changed

16 files changed

+449
-342
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
### Added
6+
7+
- support for Toolbox 2.6.3 with improved URI handling
8+
59
## 0.2.3 - 2025-05-26
610

711
### Changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,11 @@ If `ide_product_code` and `ide_build_number` is missing, Toolbox will only open
101101
page. Coder Toolbox will attempt to start the workspace if it’s not already running; however, for the most reliable
102102
experience, it’s recommended to ensure the workspace is running prior to initiating the connection.
103103

104+
> ⚠️ Note: `folder` should point to a remote IDEA project that has already been opened and appears in the `Projects` tab.
105+
> If the path refers to a project that doesn't exist, the remote IDE won’t start or load it.
106+
107+
> Until [TBX-14952](https://youtrack.jetbrains.com/issue/TBX-14952/) is fixed, it's best to either use a path to a previously opened project or leave it empty.
108+
104109
## Configuring and Testing workspace polling with HTTP & SOCKS5 Proxy
105110

106111
This section explains how to set up a local proxy (without authentication which is not yet supported) and verify that

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=0.2.3
1+
version=0.3.0
22
group=com.coder.toolbox
33
name=coder-toolbox

gradle/libs.versions.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[versions]
2-
toolbox-plugin-api = "1.0.38881"
3-
kotlin = "2.1.0"
2+
toolbox-plugin-api = "1.1.41749"
3+
kotlin = "2.1.10"
44
coroutines = "1.10.1"
55
serialization = "1.8.0"
66
okhttp = "4.12.0"
@@ -9,7 +9,7 @@ marketplace-client = "2.0.46"
99
gradle-wrapper = "0.14.0"
1010
exec = "1.12"
1111
moshi = "1.15.2"
12-
ksp = "2.1.0-1.0.29"
12+
ksp = "2.1.10-1.0.31"
1313
retrofit = "2.11.0"
1414
changelog = "2.2.1"
1515
gettext = "0.7.0"

src/main/kotlin/com/coder/toolbox/CoderRemoteEnvironment.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import com.squareup.moshi.Moshi
2727
import kotlinx.coroutines.Job
2828
import kotlinx.coroutines.delay
2929
import kotlinx.coroutines.flow.MutableStateFlow
30+
import kotlinx.coroutines.flow.StateFlow
3031
import kotlinx.coroutines.flow.update
3132
import kotlinx.coroutines.isActive
3233
import kotlinx.coroutines.launch
@@ -203,7 +204,7 @@ class CoderRemoteEnvironment(
203204

204205
private fun File.doesNotExists(): Boolean = !this.exists()
205206

206-
override fun afterDisconnect() {
207+
override fun afterDisconnect(isManual: Boolean) {
207208
context.logger.info("Stopping the network metrics poll job for $id")
208209
pollJob?.cancel()
209210
this.connectionRequest.update { false }
@@ -269,7 +270,7 @@ class CoderRemoteEnvironment(
269270
}
270271
}
271272

272-
override fun onDelete() {
273+
override val deleteActionFlow: StateFlow<(() -> Unit)?> = MutableStateFlow {
273274
context.cs.launch {
274275
try {
275276
client.removeWorkspace(workspace)

src/main/kotlin/com/coder/toolbox/CoderToolboxContext.kt

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,22 @@ import com.jetbrains.toolbox.api.core.diagnostics.Logger
77
import com.jetbrains.toolbox.api.core.os.LocalDesktopManager
88
import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
99
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
10+
import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper
1011
import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1112
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1213
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
1314
import com.jetbrains.toolbox.api.ui.ToolboxUi
1415
import kotlinx.coroutines.CoroutineScope
1516
import java.net.URL
17+
import java.util.UUID
1618

19+
@Suppress("UnstableApiUsage")
1720
data class CoderToolboxContext(
1821
val ui: ToolboxUi,
1922
val envPageManager: EnvironmentUiPageManager,
2023
val envStateColorPalette: EnvironmentStateColorPalette,
21-
val ideOrchestrator: ClientHelper,
24+
val remoteIdeOrchestrator: RemoteToolsHelper,
25+
val jbClientOrchestrator: ClientHelper,
2226
val desktop: LocalDesktopManager,
2327
val cs: CoroutineScope,
2428
val logger: Logger,
@@ -44,4 +48,44 @@ data class CoderToolboxContext(
4448
}
4549
return this.settingsStore.defaultURL.toURL()
4650
}
51+
52+
suspend fun logAndShowError(title: String, error: String) {
53+
logger.error(error)
54+
ui.showSnackbar(
55+
UUID.randomUUID().toString(),
56+
i18n.pnotr(title),
57+
i18n.pnotr(error),
58+
i18n.ptrl("OK")
59+
)
60+
}
61+
62+
suspend fun logAndShowError(title: String, error: String, exception: Exception) {
63+
logger.error(exception, error)
64+
ui.showSnackbar(
65+
UUID.randomUUID().toString(),
66+
i18n.pnotr(title),
67+
i18n.pnotr(error),
68+
i18n.ptrl("OK")
69+
)
70+
}
71+
72+
suspend fun logAndShowWarning(title: String, warning: String) {
73+
logger.warn(warning)
74+
ui.showSnackbar(
75+
UUID.randomUUID().toString(),
76+
i18n.pnotr(title),
77+
i18n.pnotr(warning),
78+
i18n.ptrl("OK")
79+
)
80+
}
81+
82+
suspend fun logAndShowInfo(title: String, info: String) {
83+
logger.info(info)
84+
ui.showSnackbar(
85+
UUID.randomUUID().toString(),
86+
i18n.pnotr(title),
87+
i18n.pnotr(info),
88+
i18n.ptrl("OK")
89+
)
90+
}
4791
}

src/main/kotlin/com/coder/toolbox/CoderToolboxExtension.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.jetbrains.toolbox.api.localization.LocalizableStringFactory
1313
import com.jetbrains.toolbox.api.remoteDev.RemoteDevExtension
1414
import com.jetbrains.toolbox.api.remoteDev.RemoteProvider
1515
import com.jetbrains.toolbox.api.remoteDev.connection.ClientHelper
16+
import com.jetbrains.toolbox.api.remoteDev.connection.RemoteToolsHelper
1617
import com.jetbrains.toolbox.api.remoteDev.connection.ToolboxProxySettings
1718
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateColorPalette
1819
import com.jetbrains.toolbox.api.remoteDev.ui.EnvironmentUiPageManager
@@ -31,6 +32,7 @@ class CoderToolboxExtension : RemoteDevExtension {
3132
serviceLocator.getService<ToolboxUi>(),
3233
serviceLocator.getService<EnvironmentUiPageManager>(),
3334
serviceLocator.getService<EnvironmentStateColorPalette>(),
35+
serviceLocator.getService<RemoteToolsHelper>(),
3436
serviceLocator.getService<ClientHelper>(),
3537
serviceLocator.getService<LocalDesktopManager>(),
3638
serviceLocator.getService<CoroutineScope>(),

src/main/kotlin/com/coder/toolbox/models/WorkspaceAndAgentStatus.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import com.coder.toolbox.sdk.v2.models.WorkspaceAgentLifecycleState
77
import com.coder.toolbox.sdk.v2.models.WorkspaceAgentStatus
88
import com.coder.toolbox.sdk.v2.models.WorkspaceStatus
99
import com.jetbrains.toolbox.api.core.ui.color.StateColor
10-
import com.jetbrains.toolbox.api.remoteDev.states.CustomRemoteEnvironmentState
10+
import com.jetbrains.toolbox.api.remoteDev.states.CustomRemoteEnvironmentStateV2
1111
import com.jetbrains.toolbox.api.remoteDev.states.EnvironmentStateIcons
1212
import com.jetbrains.toolbox.api.remoteDev.states.StandardRemoteEnvironmentState
1313

@@ -61,9 +61,9 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) {
6161
* Note that a reachable environment will always display "connected" or
6262
* "disconnected" regardless of the label we give that status.
6363
*/
64-
fun toRemoteEnvironmentState(context: CoderToolboxContext): CustomRemoteEnvironmentState {
65-
return CustomRemoteEnvironmentState(
66-
label,
64+
fun toRemoteEnvironmentState(context: CoderToolboxContext): CustomRemoteEnvironmentStateV2 {
65+
return CustomRemoteEnvironmentStateV2(
66+
context.i18n.pnotr(label),
6767
color = getStateColor(context),
6868
reachable = ready() || unhealthy(),
6969
// TODO@JB: How does this work? Would like a spinner for pending states.
@@ -90,10 +90,10 @@ enum class WorkspaceAndAgentStatus(val label: String, val description: String) {
9090
else EnvironmentStateIcons.NoIcon
9191
}
9292

93-
fun toSshConnectingEnvState(context: CoderToolboxContext): CustomRemoteEnvironmentState {
93+
fun toSshConnectingEnvState(context: CoderToolboxContext): CustomRemoteEnvironmentStateV2 {
9494
val existingState = toRemoteEnvironmentState(context)
95-
return CustomRemoteEnvironmentState(
96-
"SSHing",
95+
return CustomRemoteEnvironmentStateV2(
96+
context.i18n.pnotr("SSHing"),
9797
existingState.color,
9898
existingState.isReachable,
9999
EnvironmentStateIcons.Connecting

src/main/kotlin/com/coder/toolbox/sdk/CoderRestClient.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,12 +192,12 @@ open class CoderRestClient(
192192
}
193193

194194
/**
195-
* Maps the list of workspaces to the associated agents.
195+
* Maps the available workspaces to the associated agents.
196196
*/
197-
suspend fun groupByAgents(workspaces: List<Workspace>): Set<Pair<Workspace, WorkspaceAgent>> {
197+
suspend fun workspacesByAgents(): Set<Pair<Workspace, WorkspaceAgent>> {
198198
// It is possible for there to be resources with duplicate names so we
199199
// need to use a set.
200-
return workspaces.flatMap { ws ->
200+
return workspaces().flatMap { ws ->
201201
when (ws.latestBuild.status) {
202202
WorkspaceStatus.RUNNING -> ws.latestBuild.resources
203203
else -> resources(ws)

0 commit comments

Comments
 (0)