feat: complete ops ai workspace bridge

This commit is contained in:
DCCONSTRUCTIONS
2026-06-13 08:46:55 +03:00
parent 8b1e356494
commit 3806848b07
7 changed files with 2040 additions and 45 deletions
@@ -97,6 +97,66 @@ export type TAIWorkspaceThreadMessageResponse = {
message: TAIWorkspaceThreadMessage;
};
export type TAIWorkspaceBridgeDispatchResponse = {
ok: boolean;
threadId: string;
executor?: TAIWorkspaceExecutor;
message?: TAIWorkspaceThreadMessage;
assistantMessage?: TAIWorkspaceThreadMessage;
bridge?: {
ok?: boolean;
accepted?: boolean;
requestId?: string | null;
threadId?: string | null;
mode?: string;
error?: string;
response?: Record<string, any>;
};
};
export type TAIWorkspaceBridgeEvent = {
id?: number | string;
requestId?: string | null;
threadId?: string | null;
sessionId?: string | null;
threadTitle?: string | null;
userMessage?: string | null;
command?: string | null;
cwd?: string | null;
kind?: string | null;
message?: string | null;
text?: string | null;
at?: string | null;
status?: string | null;
[key: string]: any;
};
export type TAIWorkspaceBridgeEventsResponse = {
ok: boolean;
executorId: string;
online?: boolean;
latestEventId?: number;
events: TAIWorkspaceBridgeEvent[];
};
export type TAIWorkspaceOpsProject = {
id: string;
identifier?: string | null;
slug?: string | null;
name?: string | null;
title?: string | null;
projectIdentifier?: string | null;
[key: string]: any;
};
export type TAIWorkspaceOpsWorkspace = {
id: string;
slug: string;
name?: string | null;
title?: string | null;
[key: string]: any;
};
export type TAIWorkspaceThreadInput = {
title: string;
originSurface?: "engine" | "ops" | "global";
@@ -113,6 +173,65 @@ export class WorkspaceAIWorkspaceService extends APIService {
super(API_BASE_URL);
}
private arrayFromApiPayload<T = any>(payload: any): T[] {
if (Array.isArray(payload)) return payload;
if (Array.isArray(payload?.results)) return payload.results;
if (Array.isArray(payload?.data)) return payload.data;
if (Array.isArray(payload?.projects)) return payload.projects;
if (Array.isArray(payload?.workspaces)) return payload.workspaces;
return [];
}
async listProjects(workspaceSlug: string): Promise<TAIWorkspaceOpsProject[]> {
return this.get(`/api/workspaces/${workspaceSlug}/projects/`)
.then((response) =>
this.arrayFromApiPayload<TAIWorkspaceOpsProject>(response?.data)
.map((project) => ({
...project,
id: String(project.id || "").trim(),
identifier: String(project.identifier || project.projectIdentifier || "").trim(),
title: String(project.title || project.name || project.identifier || project.id || "Project").trim(),
}))
.filter((project) => project.id)
)
.catch((error) => {
throw error?.response?.data ?? error;
});
}
async listWorkspaces(): Promise<TAIWorkspaceOpsWorkspace[]> {
return this.get("/api/users/me/workspaces/")
.then((response) =>
this.arrayFromApiPayload<TAIWorkspaceOpsWorkspace>(response?.data)
.map((workspace) => ({
...workspace,
id: String(workspace.id || workspace.slug || "").trim(),
slug: String(workspace.slug || "").trim(),
title: String(workspace.title || workspace.name || workspace.slug || "Workspace").trim(),
}))
.filter((workspace) => workspace.slug)
)
.catch((error) => {
throw error?.response?.data ?? error;
});
}
async updateSettings(
workspaceSlug: string,
input: {
selectedExecutorId?: string | null;
activeContext?: Record<string, any>;
enabledToolPacks?: string[];
metadata?: Record<string, any>;
}
): Promise<Record<string, any>> {
return this.patch(`/api/workspaces/${workspaceSlug}/ai-workspace/settings/`, input)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data ?? error;
});
}
async listExecutors(workspaceSlug: string): Promise<TAIWorkspaceExecutorListResponse> {
return this.get(`/api/workspaces/${workspaceSlug}/ai-workspace/executors/`)
.then((response) => response?.data)
@@ -158,10 +277,25 @@ export class WorkspaceAIWorkspaceService extends APIService {
return this.listExecutors(workspaceSlug);
}
getWindowsAgentInstallerUrl(workspaceSlug: string, executorId: string, options: { port?: string | number } = {}) {
async checkExecutor(workspaceSlug: string, executorId: string): Promise<TAIWorkspaceExecutorListResponse> {
await this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/executors/${executorId}/check/`).catch((error) => {
throw error?.response?.data ?? error;
});
return this.listExecutors(workspaceSlug);
}
getWindowsAgentInstallerUrl(
workspaceSlug: string,
executorId: string,
options: { port?: string | number; opsWorkspaceSlug?: string; opsProjectId?: string } = {}
) {
const params = new URLSearchParams({ v: `${Date.now()}` });
const port = String(options.port || "").trim();
if (port) params.set("port", port);
const opsWorkspaceSlug = String(options.opsWorkspaceSlug || "").trim();
if (opsWorkspaceSlug) params.set("ops_workspace_slug", opsWorkspaceSlug);
const opsProjectId = String(options.opsProjectId || "").trim();
if (opsProjectId) params.set("ops_project_id", opsProjectId);
return `${API_BASE_URL}/api/workspaces/${workspaceSlug}/ai-workspace/executors/${executorId}/agent/windows.ps1?${params.toString()}`;
}
@@ -170,7 +304,7 @@ export class WorkspaceAIWorkspaceService extends APIService {
options: { surface?: "ops" | "engine" | "global"; limit?: number } = {}
): Promise<TAIWorkspaceThreadListResponse> {
const params = new URLSearchParams();
params.set("surface", options.surface || "ops");
if (options.surface) params.set("surface", options.surface);
params.set("limit", String(options.limit || 100));
return this.get(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/?${params.toString()}`)
.then((response) => response?.data)
@@ -182,7 +316,7 @@ export class WorkspaceAIWorkspaceService extends APIService {
async createThread(workspaceSlug: string, input: TAIWorkspaceThreadInput): Promise<TAIWorkspaceThreadResponse> {
return this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/`, {
originSurface: "ops",
enabledToolPacks: ["ops"],
enabledToolPacks: ["ops", "engine"],
...input,
})
.then((response) => response?.data)
@@ -228,4 +362,40 @@ export class WorkspaceAIWorkspaceService extends APIService {
throw error?.response?.data ?? error;
});
}
async dispatchThreadMessage(
workspaceSlug: string,
threadId: string,
input: {
messageId: string;
selectedExecutorId?: string | null;
context?: Record<string, any>;
modeId?: string;
modeTitle?: string;
remoteControlMode?: string;
}
): Promise<TAIWorkspaceBridgeDispatchResponse> {
return this.post(`/api/workspaces/${workspaceSlug}/ai-workspace/threads/${threadId}/dispatch/`, input)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data ?? error;
});
}
async listBridgeEvents(
workspaceSlug: string,
executorId: string,
options: { since?: number | "latest"; limit?: number } = {}
): Promise<TAIWorkspaceBridgeEventsResponse> {
const params = new URLSearchParams();
params.set("since", String(options.since ?? 0));
params.set("limit", String(options.limit || 100));
return this.get(
`/api/workspaces/${workspaceSlug}/ai-workspace/executors/${executorId}/events/?${params.toString()}`
)
.then((response) => response?.data)
.catch((error) => {
throw error?.response?.data ?? error;
});
}
}