Files
ST-Bionic-Memory-Ecology/vector/vector-space.js
2026-05-15 15:09:57 +00:00

130 lines
4.4 KiB
JavaScript

import { stableHashString } from "../runtime/runtime-state.js";
export const VECTOR_MANIFEST_VERSION = 1;
function normalizeString(value) {
return String(value || "").trim();
}
function normalizeLower(value) {
return normalizeString(value).toLowerCase();
}
export function normalizeVectorApiUrl(value) {
const raw = normalizeString(value);
if (!raw) return "";
try {
const url = new URL(raw, raw.startsWith("/") ? "http://st-bme.local" : undefined);
url.hash = "";
url.search = "";
let pathname = url.pathname.replace(/\/+$/, "");
pathname = pathname.replace(/\/embeddings$/i, "").replace(/\/v1$/i, "/v1");
const normalized = `${url.protocol}//${url.host}${pathname}`.replace(/\/+$/, "");
return raw.startsWith("/") ? normalized.replace(/^http:\/\/st-bme\.local/i, "") : normalized;
} catch {
return raw.replace(/[?#].*$/, "").replace(/\/+$/, "").replace(/\/embeddings$/i, "");
}
}
export function getVectorProviderKind(config = {}) {
if (config?.mode === "authority" || config?.source === "authority-trivium") {
return "authority-client";
}
if (config?.mode === "backend") {
return "st-backend";
}
return "direct-openai-compatible";
}
export function getVectorEmbeddingMode(config = {}) {
if (config?.mode === "backend") return "st-backend";
if (config?.embeddingMode === "server") return "server";
return "client";
}
export function deriveVectorSpace(config = {}, observedDim = 0, extra = {}) {
const dim = Math.max(0, Math.floor(Number(observedDim) || 0));
const providerKind = normalizeLower(extra.providerKind || getVectorProviderKind(config));
const embeddingMode = normalizeLower(extra.embeddingMode || getVectorEmbeddingMode(config));
const source = normalizeLower(config.embeddingSource || config.source || "");
const normalizedApiUrl = normalizeVectorApiUrl(
config.apiUrl || config.baseUrl || extra.apiUrl || "",
);
const model = normalizeString(config.model || extra.model || "");
const material = {
providerKind,
embeddingMode,
source,
normalizedApiUrl,
model,
observedDim: dim,
};
const vectorSpaceId = dim > 0
? `vs_${stableHashString(JSON.stringify(material))}`
: "";
return {
vectorSpaceId,
providerKind,
embeddingMode,
source,
normalizedApiUrl,
model,
observedDim: dim,
settingsFingerprint: stableHashString(JSON.stringify({ ...material, observedDim: undefined })),
probedAt: Number(extra.probedAt || Date.now()),
};
}
export function createVectorManifest({
backend = "local",
chatId = "",
collectionId = "",
graphRevision = 0,
vectorSpace = null,
status = "missing",
nodeCount = 0,
embeddedNodeCount = 0,
failedNodeCount = 0,
lastError = "",
} = {}) {
const observedDim = Math.max(0, Math.floor(Number(vectorSpace?.observedDim) || 0));
const now = Date.now();
return {
manifestVersion: VECTOR_MANIFEST_VERSION,
backend,
chatId,
collectionId,
graphRevision: Math.max(0, Math.floor(Number(graphRevision) || 0)),
vectorSpaceId: vectorSpace?.vectorSpaceId || "",
observedDim,
model: vectorSpace?.model || "",
normalizedApiUrl: vectorSpace?.normalizedApiUrl || "",
status,
nodeCount: Math.max(0, Math.floor(Number(nodeCount) || 0)),
embeddedNodeCount: Math.max(0, Math.floor(Number(embeddedNodeCount) || 0)),
failedNodeCount: Math.max(0, Math.floor(Number(failedNodeCount) || 0)),
createdAt: now,
completedAt: status === "clean" ? now : 0,
lastError: lastError || "",
};
}
export function isVectorManifestCompatible(manifest, vectorSpace) {
if (!manifest || !vectorSpace) return false;
if (manifest.status !== "clean") return false;
if (!manifest.vectorSpaceId || !vectorSpace.vectorSpaceId) return false;
if (manifest.vectorSpaceId !== vectorSpace.vectorSpaceId) return false;
return Number(manifest.observedDim || 0) === Number(vectorSpace.observedDim || 0);
}
export function summarizeVectorSpaceChange(previous, current) {
if (!previous?.vectorSpaceId || !current?.vectorSpaceId) return "vector-space-missing";
if (previous.vectorSpaceId === current.vectorSpaceId) return "unchanged";
if (Number(previous.observedDim || 0) !== Number(current.observedDim || 0)) {
return "dimension-changed";
}
if (previous.model !== current.model) return "model-changed";
if (previous.normalizedApiUrl !== current.normalizedApiUrl) return "endpoint-changed";
return "vector-space-changed";
}