fix: add from __future__ import annotations to workspace_manager.py

Fixes NameError: ToolInstance not defined at runtime because
type annotations are evaluated at class definition time.
Deferring annotation evaluation with __future__ annotations
keeps TYPE_CHECKING imports from causing runtime crashes.

Also includes ruff formatting cleanup on workspace-related files.
This commit is contained in:
2026-05-31 23:41:45 +02:00
parent 5bba2bbd92
commit a5d64d1859
15 changed files with 831 additions and 699 deletions
+154 -140
View File
@@ -2,185 +2,199 @@ import { AxiosError } from "axios";
import { apiClient } from "./client";
export interface ToolInstance {
id: string;
name: string;
display_name: string;
tool_type_id: string;
tool_type_name: string;
tool_type_interfaces: string[];
status: string;
url: string | null;
port: number | null;
selected_config_profile_id: string | null;
ssh_key_ids: string[];
created_at: string;
id: string;
name: string;
display_name: string;
tool_type_id: string;
tool_type_name: string;
tool_type_interfaces: string[];
status: string;
url: string | null;
port: number | null;
selected_config_profile_id: string | null;
ssh_key_ids: string[];
created_at: string;
}
export interface Session {
id: string;
display_name: string;
tool_type_name: string;
tool_icon: string;
tool_type_interfaces: string[];
repository_name: string;
repository_id: string;
project_name: string;
project_id: string;
status: string;
url: string | null;
container_status?: string;
probe_status?: string;
clone_mode?: string;
branch?: string | null;
created_at?: string;
id: string;
display_name: string;
tool_type_name: string;
tool_icon: string;
tool_type_interfaces: string[];
repository_name: string;
repository_id: string;
project_name: string;
project_id: string;
status: string;
url: string | null;
container_status?: string;
probe_status?: string;
clone_mode?: string;
branch?: string | null;
created_at?: string;
}
export async function listInstances(
projectId: string,
repoId: string
projectId: string,
repoId: string,
): Promise<ToolInstance[]> {
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances`
);
return response.data.instances;
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances`,
);
return response.data.instances;
}
export async function createInstance(
projectId: string,
repoId: string,
toolTypeId: string,
displayName?: string,
cloneMode?: string,
branch?: string,
newBranch?: string,
configProfileId?: string,
sshKeyIds?: string[],
workspaceId?: string
projectId: string,
repoId: string,
toolTypeId: string,
displayName?: string,
cloneMode?: string,
branch?: string,
newBranch?: string,
configProfileId?: string,
sshKeyIds?: string[],
workspaceId?: string,
): Promise<ToolInstance> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances`,
{
tool_type_id: toolTypeId,
display_name: displayName,
workspace_id: workspaceId || undefined,
clone_mode: cloneMode || "mount",
branch: branch || undefined,
new_branch: newBranch || undefined,
config_profile_id: configProfileId,
ssh_key_ids: sshKeyIds || [],
}
);
return response.data;
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances`,
{
tool_type_id: toolTypeId,
display_name: displayName,
workspace_id: workspaceId || undefined,
clone_mode: cloneMode || "mount",
branch: branch || undefined,
new_branch: newBranch || undefined,
config_profile_id: configProfileId,
ssh_key_ids: sshKeyIds || [],
},
);
return response.data;
}
export async function startInstance(
projectId: string,
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2
projectId: string,
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2,
): Promise<{ status: string; url?: string }> {
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/start`,
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] }
);
return response.data;
} catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces)
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return startInstance(projectId, repoId, instanceId, configProfileId, sshKeyIds, retries - 1);
}
throw error;
}
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/start`,
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] },
);
return response.data;
} catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces)
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return startInstance(
projectId,
repoId,
instanceId,
configProfileId,
sshKeyIds,
retries - 1,
);
}
throw error;
}
}
export async function stopInstance(
projectId: string,
repoId: string,
instanceId: string
projectId: string,
repoId: string,
instanceId: string,
): Promise<{ status: string }> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/stop`
);
return response.data;
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/stop`,
);
return response.data;
}
export async function restartInstance(
projectId: string,
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2
projectId: string,
repoId: string,
instanceId: string,
configProfileId?: string,
sshKeyIds?: string[],
retries = 2,
): Promise<{ status: string; url?: string }> {
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/restart`,
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] }
);
return response.data;
} catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces)
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return restartInstance(projectId, repoId, instanceId, configProfileId, sshKeyIds, retries - 1);
}
throw error;
}
try {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/restart`,
{ config_profile_id: configProfileId, ssh_key_ids: sshKeyIds || [] },
);
return response.data;
} catch (error) {
// Retry on network errors (e.g. Docker creating network interfaces)
const axiosError = error as AxiosError;
if (retries > 0 && !axiosError.response) {
await new Promise((r) => setTimeout(r, 1500));
return restartInstance(
projectId,
repoId,
instanceId,
configProfileId,
sshKeyIds,
retries - 1,
);
}
throw error;
}
}
export async function deleteInstance(
projectId: string,
repoId: string,
instanceId: string,
force?: boolean
projectId: string,
repoId: string,
instanceId: string,
force?: boolean,
): Promise<void> {
await apiClient.delete(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`,
{ params: { force } }
);
await apiClient.delete(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}`,
{ params: { force } },
);
}
export async function getUserSessions(): Promise<Session[]> {
const response = await apiClient.get("/users/me/sessions");
return response.data.sessions;
const response = await apiClient.get("/users/me/sessions");
return response.data.sessions;
}
export interface InstanceHealth {
healthy: boolean;
container_status: string;
container_health: string | null;
container_exit_code: number | null;
tunnel_status: string;
tunnel_status_code: number | null;
probe_status: string;
last_probe_output: string | null;
error: string | null;
healthy: boolean;
container_status: string;
container_health: string | null;
container_exit_code: number | null;
tunnel_status: string;
tunnel_status_code: number | null;
probe_status: string;
last_probe_output: string | null;
error: string | null;
}
export async function checkInstanceHealth(
projectId: string,
repoId: string,
instanceId: string
projectId: string,
repoId: string,
instanceId: string,
): Promise<InstanceHealth> {
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/health`
);
return response.data;
const response = await apiClient.get(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/health`,
);
return response.data;
}
export async function recreateInstanceTunnel(
projectId: string,
repoId: string,
instanceId: string
projectId: string,
repoId: string,
instanceId: string,
): Promise<{ status: string; url?: string }> {
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/recreate-tunnel`
);
return response.data;
const response = await apiClient.post(
`/projects/${projectId}/repositories/${repoId}/instances/${instanceId}/recreate-tunnel`,
);
return response.data;
}