import { validateWorkspaceDescriptor } from "./schema.js"; import type { DwhTransport, VectorTransport, WorkspaceDescriptor } from "./schema.js"; export type InstallationRole = "DWH" | "VECTOR" | "VECTOR_WRITER" | "EMBEDDING"; export type InstallationSuffix = | "TRANSPORT" | "HOST" | "PORT" | "BASE_URL" | "USER" | "PASSWORD_FILE" | "API_KEY_FILE" | "TLS_CA_FILE" | "SSH_HOST" | "SSH_PORT" | "SSH_USER" | "SSH_PRIVATE_KEY_FILE" | "SSH_KNOWN_HOSTS_FILE" | "SSH_TARGET_HOST" | "SSH_TARGET_PORT"; type ConnectorTransport = DwhTransport | VectorTransport; export interface InstallationVariable { name: string; role: InstallationRole; suffix: InstallationSuffix; secret: boolean; transports?: readonly ConnectorTransport[]; } export interface InstallationContract { workspaceId: string; namespace: string; variables: InstallationVariable[]; } const DIRECT_SUFFIXES: readonly InstallationSuffix[] = [ "HOST", "PORT", "USER", "PASSWORD_FILE", "TLS_CA_FILE", ]; const REST_SUFFIXES: readonly InstallationSuffix[] = [ "BASE_URL", "API_KEY_FILE", "TLS_CA_FILE", ]; const SSH_SUFFIXES: readonly InstallationSuffix[] = [ "USER", "PASSWORD_FILE", "TLS_CA_FILE", "SSH_HOST", "SSH_PORT", "SSH_USER", "SSH_PRIVATE_KEY_FILE", "SSH_KNOWN_HOSTS_FILE", "SSH_TARGET_HOST", "SSH_TARGET_PORT", ]; const EMBEDDING_SUFFIXES: readonly InstallationSuffix[] = [ "BASE_URL", "API_KEY_FILE", "TLS_CA_FILE", ]; function namespaceFor(workspace: WorkspaceDescriptor): string { return workspace.workspace.id.replaceAll("-", "_").toUpperCase(); } function createVariable( namespace: string, role: InstallationRole, suffix: InstallationSuffix, transports?: readonly ConnectorTransport[], ): InstallationVariable { return { name: `THT_WS_${namespace}_${role}_${suffix}`, role, suffix, secret: suffix.endsWith("_FILE"), ...(transports ? { transports } : {}), }; } function connectorVariables( namespace: string, role: "DWH" | "VECTOR", transports: readonly ConnectorTransport[], ): InstallationVariable[] { const suffixTransports = new Map(); const add = (suffixes: readonly InstallationSuffix[], transport: ConnectorTransport) => { for (const suffix of suffixes) { const applicable = suffixTransports.get(suffix) ?? []; applicable.push(transport); suffixTransports.set(suffix, applicable); } }; for (const transport of transports) { if (transport === "postgres_direct" || transport === "pgvector_direct") { add(DIRECT_SUFFIXES, transport); } else if (transport === "rest_api") { add(REST_SUFFIXES, transport); } else { add(SSH_SUFFIXES, transport); } } return [ createVariable(namespace, role, "TRANSPORT", transports), ...[...suffixTransports.entries()].map(([suffix, applicable]) => ( createVariable(namespace, role, suffix, applicable) )), ]; } export function buildInstallationContract(workspace: WorkspaceDescriptor): InstallationContract { const descriptor = validateWorkspaceDescriptor(workspace); const namespace = namespaceFor(descriptor); return { workspaceId: descriptor.workspace.id, namespace, variables: [ ...connectorVariables(namespace, "DWH", descriptor.dwh.supported_transports), ...(descriptor.workspace.schema_version === 2 ? connectorVariables( namespace, "VECTOR", "supported_transports" in descriptor.semantic_index.vector_store ? descriptor.semantic_index.vector_store.supported_transports : [], ) : []), ...(descriptor.workspace.schema_version === 2 && descriptor.semantic_index.vector_writer ? [createVariable(namespace, "VECTOR_WRITER", "API_KEY_FILE")] : []), ...(descriptor.workspace.schema_version === 2 ? EMBEDDING_SUFFIXES.map((suffix) => createVariable(namespace, "EMBEDDING", suffix)) : []), ], }; } function localizedIntroduction(workspace: WorkspaceDescriptor): string { return workspace.workspace.language === "it" ? `Configurazione dell'installazione per ${workspace.workspace.name}. Imposta solo i binding supportati da questa installazione.` : `Installation setup for ${workspace.workspace.name}. Configure only the bindings supported by this installation.`; } export function renderWorkspaceDocs(workspace: WorkspaceDescriptor): { envExample: string; markdown: string } { const descriptor = validateWorkspaceDescriptor(workspace); const contract = buildInstallationContract(descriptor); const variablesByRole = new Map(); for (const variable of contract.variables) { const variables = variablesByRole.get(variable.role) ?? []; variables.push(variable); variablesByRole.set(variable.role, variables); } const envExample = [ `# Generated installation bindings for ${descriptor.workspace.id}`, "# Provide secret file paths only; never paste secret values here.", ...contract.variables.map((variable) => `${variable.name}=`), "", ].join("\n"); const markdown = [ "# Installation requirements", "", `**Workspace:** ${descriptor.workspace.name}`, "", localizedIntroduction(descriptor), "", "Use the following UI fields as installation bindings. Secret fields always contain file paths, never secret values.", "", ...(["DWH", "VECTOR", "VECTOR_WRITER", "EMBEDDING"] as const) .filter((role) => variablesByRole.has(role)) .flatMap((role) => [ `## ${role === "DWH" ? "Data warehouse" : role === "VECTOR" ? "Vector store" : role === "VECTOR_WRITER" ? "Vector writer" : "Embedding service"}`, "", ...(variablesByRole.get(role) ?? []).map((variable) => ( `- \`${variable.name}\`${variable.transports ? ` (for: ${variable.transports.join(", ")})` : ""}` )), "", ]), ].join("\n"); return { envExample, markdown }; }