feat(coding): add PI-105 product file host API
This commit is contained in:
138
electron/api/routes/coding-files.ts
Normal file
138
electron/api/routes/coding-files.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http';
|
||||
import type { HostApiContext } from '../context';
|
||||
import { CodingProductHostError } from '../coding-product-services';
|
||||
import { sendJson } from '../route-utils';
|
||||
|
||||
function unavailable(res: ServerResponse): void {
|
||||
sendJson(res, 503, {
|
||||
success: false,
|
||||
code: 'CODING_PRODUCT_TOOLS_UNAVAILABLE',
|
||||
error: 'Coding product tools are unavailable',
|
||||
});
|
||||
}
|
||||
|
||||
function serviceError(res: ServerResponse, error: unknown): void {
|
||||
if (error instanceof CodingProductHostError) {
|
||||
sendJson(res, error.status, {
|
||||
success: false,
|
||||
code: error.code,
|
||||
error: error.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const code = error && typeof error === 'object' && 'code' in error
|
||||
? String(error.code)
|
||||
: '';
|
||||
if (code === 'ENOENT') {
|
||||
sendJson(res, 404, {
|
||||
success: false,
|
||||
code: 'CODING_FILE_NOT_FOUND',
|
||||
error: 'Project file does not exist',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : '';
|
||||
const knownInputError = new Set([
|
||||
'File query is required',
|
||||
'File query is too long',
|
||||
'Search pattern is required',
|
||||
'Search pattern is too long',
|
||||
'Project file path must be relative',
|
||||
'Project file path escapes the active project',
|
||||
'Project file path is not a file',
|
||||
'Binary project files cannot be previewed',
|
||||
'Project file is not valid UTF-8 text',
|
||||
]);
|
||||
if (knownInputError.has(message)) {
|
||||
sendJson(res, 400, {
|
||||
success: false,
|
||||
code: 'CODING_FILE_REQUEST_INVALID',
|
||||
error: message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
sendJson(res, 500, {
|
||||
success: false,
|
||||
code: 'CODING_PRODUCT_TOOL_FAILED',
|
||||
error: 'Coding product request failed',
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleCodingFileRoutes(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
url: URL,
|
||||
ctx: HostApiContext,
|
||||
): Promise<boolean> {
|
||||
const host = ctx.codingProducts?.host;
|
||||
const fixedGetRoutes = new Set([
|
||||
'/api/coding/files/status',
|
||||
'/api/coding/files/find',
|
||||
'/api/coding/files/content',
|
||||
'/api/coding/search',
|
||||
'/api/coding/skills',
|
||||
]);
|
||||
const commandMatch = url.pathname.match(/^\/api\/coding\/conversations\/([^/]+)\/commands$/);
|
||||
const changesMatch = url.pathname.match(/^\/api\/coding\/conversations\/([^/]+)\/changes$/);
|
||||
if (!fixedGetRoutes.has(url.pathname) && !commandMatch && !changesMatch) return false;
|
||||
if (req.method !== 'GET') return false;
|
||||
if (!host) {
|
||||
unavailable(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (url.pathname === '/api/coding/files/status') {
|
||||
sendJson(res, 200, { files: await host.fileStatus() });
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/files/find') {
|
||||
const query = url.searchParams.get('query')?.trim() ?? '';
|
||||
const rawLimit = url.searchParams.get('limit');
|
||||
const parsedLimit = rawLimit ? Number(rawLimit) : undefined;
|
||||
sendJson(res, 200, {
|
||||
files: await host.findFiles(
|
||||
query,
|
||||
parsedLimit !== undefined && Number.isSafeInteger(parsedLimit) && parsedLimit > 0
|
||||
? parsedLimit
|
||||
: undefined,
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/files/content') {
|
||||
sendJson(res, 200, {
|
||||
file: await host.fileContent(url.searchParams.get('path')?.trim() ?? ''),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/search') {
|
||||
sendJson(res, 200, {
|
||||
matches: await host.searchText(url.searchParams.get('pattern')?.trim() ?? ''),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (url.pathname === '/api/coding/skills') {
|
||||
sendJson(res, 200, {
|
||||
skills: await host.listSkills(url.searchParams.get('agentId')?.trim() || undefined),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (commandMatch) {
|
||||
sendJson(res, 200, {
|
||||
commands: await host.listCommands(decodeURIComponent(commandMatch[1])),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (changesMatch) {
|
||||
sendJson(res, 200, {
|
||||
changes: await host.getChanges(decodeURIComponent(changesMatch[1])),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
serviceError(res, error);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user