Files
kandev/apps/web/lib/ws/workspace-files.ts
T
2026-08-05 10:57:24 +01:00

220 lines
5.5 KiB
TypeScript

import type { WebSocketClient } from "./client";
import type {
FileTreeResponse,
FileContentResponse,
FileSearchResponse,
WorkspaceContentSearchResponse,
} from "@/lib/types/backend";
/**
* Request file tree from the backend
*/
export async function requestFileTree(
client: WebSocketClient,
sessionId: string,
path: string = "",
depth: number = 1,
): Promise<FileTreeResponse> {
return client.request<FileTreeResponse>("workspace.tree.get", {
session_id: sessionId,
path,
depth,
});
}
/**
* Request file content from the backend.
* `repo` is the multi-repo subpath (e.g. "kandev"); omit for single-repo
* workspaces. When set, `path` is interpreted relative to that repo.
*/
export async function requestFileContent(
client: WebSocketClient,
sessionId: string,
path: string,
repo?: string,
): Promise<FileContentResponse & { is_binary: boolean }> {
const response = await client.request<FileContentResponse>("workspace.file.get", {
session_id: sessionId,
path,
...(repo ? { repo } : {}),
});
return normalizeFileContentResponse(response);
}
/**
* Request file content at a specific git ref (branch, commit, HEAD, etc.)
* Used for diff expansion to fetch old/base version of a file.
* `repo` scopes to a per-repo subdirectory for multi-repo workspaces.
*/
export async function requestFileContentAtRef(
client: WebSocketClient,
sessionId: string,
path: string,
ref: string,
repo?: string,
): Promise<FileContentResponse & { is_binary: boolean }> {
const response = await client.request<FileContentResponse>("workspace.file.get_at_ref", {
session_id: sessionId,
path,
ref,
...(repo ? { repo } : {}),
});
return normalizeFileContentResponse(response);
}
function normalizeFileContentResponse(
response: FileContentResponse,
): FileContentResponse & { is_binary: boolean } {
return { ...response, is_binary: response.is_binary === true };
}
/**
* Search for files in the workspace matching a query
*/
export async function searchWorkspaceFiles(
client: WebSocketClient,
sessionId: string,
query: string,
limit: number = 20,
): Promise<FileSearchResponse> {
return client.request<FileSearchResponse>("workspace.files.search", {
session_id: sessionId,
query,
limit,
});
}
/** Search text content across every repository in the active task workspace. */
export async function searchWorkspaceContent(
client: WebSocketClient,
sessionId: string,
query: string,
limitPerRepo: number = 50,
): Promise<WorkspaceContentSearchResponse> {
return client.request<WorkspaceContentSearchResponse>("workspace.content.search", {
session_id: sessionId,
query,
limit_per_repo: limitPerRepo,
});
}
/**
* File update response from backend
*/
export type FileUpdateResponse = {
path: string;
success: boolean;
new_hash?: string;
resolution?: "applied" | "overwritten";
error?: string;
};
/**
* Update file content using a diff. `repo` scopes to a per-repo subdirectory
* for multi-repo workspaces; omit for single-repo.
*/
export async function updateFileContent(
client: WebSocketClient,
sessionId: string,
params: {
path: string;
diff: string;
originalHash: string;
desiredContent?: string;
repo?: string;
},
): Promise<FileUpdateResponse> {
return client.request<FileUpdateResponse>("workspace.file.update", {
session_id: sessionId,
path: params.path,
diff: params.diff,
original_hash: params.originalHash,
...(params.desiredContent !== undefined && {
desired_content: params.desiredContent,
}),
...(params.repo ? { repo: params.repo } : {}),
});
}
/**
* File create response from backend
*/
export type FileCreateResponse = {
path: string;
success: boolean;
error?: string;
};
/**
* Create a new file in the workspace. `repo` scopes the create to a per-repo
* subdirectory for multi-repo task workspaces; omit for single-repo.
*/
export async function createFile(
client: WebSocketClient,
sessionId: string,
path: string,
repo?: string,
): Promise<FileCreateResponse> {
return client.request<FileCreateResponse>("workspace.file.create", {
session_id: sessionId,
path,
...(repo ? { repo } : {}),
});
}
/**
* File delete response from backend
*/
export type FileDeleteResponse = {
path: string;
success: boolean;
error?: string;
};
/**
* Delete a file from the workspace. `repo` scopes the delete to a per-repo
* subdirectory for multi-repo task workspaces; omit for single-repo.
*/
export async function deleteFile(
client: WebSocketClient,
sessionId: string,
path: string,
repo?: string,
): Promise<FileDeleteResponse> {
return client.request<FileDeleteResponse>("workspace.file.delete", {
session_id: sessionId,
path,
...(repo ? { repo } : {}),
});
}
/**
* File rename response from backend
*/
export type FileRenameResponse = {
old_path: string;
new_path: string;
success: boolean;
error?: string;
};
/**
* Rename a file or directory in the workspace. `repo` scopes BOTH oldPath and
* newPath to a per-repo subdirectory for multi-repo task workspaces; omit
* for single-repo. Cross-repo moves aren't supported.
*/
export async function renameFile(
client: WebSocketClient,
sessionId: string,
oldPath: string,
newPath: string,
repo?: string,
): Promise<FileRenameResponse> {
return client.request<FileRenameResponse>("workspace.file.rename", {
session_id: sessionId,
old_path: oldPath,
new_path: newPath,
...(repo ? { repo } : {}),
});
}