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

196 lines
5.9 KiB
TypeScript

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<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: 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<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 ${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 };
}