Files
ThothII/backend/src/workspaces/contracts.ts
T

185 lines
5.3 KiB
TypeScript

import { validateCanonicalWorkspace } from "./schema.js";
import type { CanonicalWorkspace, DwhTransport, VectorTransport } from "./schema.js";
export type InstallationRole = "DWH" | "VECTOR" | "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: CanonicalWorkspace): 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<InstallationSuffix, ConnectorTransport[]>();
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: CanonicalWorkspace): InstallationContract {
const canonical = validateCanonicalWorkspace(workspace);
const namespace = namespaceFor(canonical);
return {
workspaceId: canonical.workspace.id,
namespace,
variables: [
...connectorVariables(namespace, "DWH", canonical.dwh.supported_transports),
...connectorVariables(
namespace,
"VECTOR",
canonical.semantic_index.vector_store.supported_transports,
),
...EMBEDDING_SUFFIXES.map((suffix) => createVariable(namespace, "EMBEDDING", suffix)),
],
};
}
function localizedIntroduction(workspace: CanonicalWorkspace): 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: CanonicalWorkspace): { envExample: string; markdown: string } {
const canonical = validateCanonicalWorkspace(workspace);
const contract = buildInstallationContract(canonical);
const variablesByRole = new Map<InstallationRole, InstallationVariable[]>();
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 ${canonical.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:** ${canonical.workspace.name}`,
"",
localizedIntroduction(canonical),
"",
"Use the following UI fields as installation bindings. Secret fields always contain file paths, never secret values.",
"",
...(["DWH", "VECTOR", "EMBEDDING"] as const).flatMap((role) => [
`## ${role === "DWH" ? "Data warehouse" : role === "VECTOR" ? "Vector store" : "Embedding service"}`,
"",
...(variablesByRole.get(role) ?? []).map((variable) => (
`- \`${variable.name}\`${variable.transports ? ` (for: ${variable.transports.join(", ")})` : ""}`
)),
"",
]),
].join("\n");
return { envExample, markdown };
}