Advance TS port Effect workbench

This commit is contained in:
elpresidank 2026-06-01 16:22:25 -05:00
parent 92dae8c374
commit 3515106670
116 changed files with 12286 additions and 9584 deletions

View file

@ -0,0 +1,51 @@
import type * as Atom from "effect/unstable/reactivity/Atom";
import {
apiFactoryAtom,
DEFAULT_SETTINGS,
flowIdAtom,
settingsAtom,
type FeatureSwitches,
type Settings,
type WorkbenchApiFactory,
} from "@/atoms/workbench";
import { makeMockBaseApi, qaSettingsFromFixture, type MockWorkbenchFixture } from "@/qa/mock-api";
export interface WorkbenchQaWindowConfig {
readonly enabled?: boolean;
readonly fixture?: MockWorkbenchFixture;
readonly flowId?: string;
}
declare global {
interface Window {
__TRUSTGRAPH_WORKBENCH_QA__?: WorkbenchQaWindowConfig;
}
}
function qaSettings(fixture: MockWorkbenchFixture | undefined): Settings {
const fixtureSettings = qaSettingsFromFixture(fixture);
return {
...DEFAULT_SETTINGS,
...fixtureSettings,
featureSwitches: {
...DEFAULT_SETTINGS.featureSwitches,
...fixtureSettings.featureSwitches,
} as FeatureSwitches,
};
}
export function getWorkbenchQaInitialValues(): Iterable<readonly [Atom.Atom<unknown>, unknown]> | undefined {
if (typeof window === "undefined") return undefined;
const config = window.__TRUSTGRAPH_WORKBENCH_QA__;
if (config?.enabled !== true) return undefined;
const fixture = config.fixture ?? {};
const api = makeMockBaseApi(fixture);
const apiFactory: WorkbenchApiFactory = {
create: () => api,
};
return [
[apiFactoryAtom as Atom.Atom<unknown>, apiFactory],
[settingsAtom as Atom.Atom<unknown>, qaSettings(fixture)],
[flowIdAtom as Atom.Atom<unknown>, config.flowId ?? "default"],
];
}

View file

@ -0,0 +1,578 @@
import { BaseApi, type ConnectionState, type DocumentMetadata, type ProcessingMetadata, type StreamingMetadata, type Triple } from "@trustgraph/client";
import { Option, Schema as S } from "effect";
type ConfigValues = Record<string, Record<string, unknown>>;
export interface MockWorkbenchFixture {
readonly settings?: {
readonly user?: string;
readonly apiKey?: string;
readonly gatewayUrl?: string;
readonly collection?: string;
readonly featureSwitches?: Record<string, boolean>;
};
readonly flows?: {
readonly activeIds?: string[];
readonly definitions?: Record<string, Record<string, unknown>>;
readonly blueprints?: Record<string, Record<string, unknown>>;
};
readonly config?: {
readonly prompt?: Record<string, unknown>;
readonly valuesByType?: ConfigValues;
};
readonly library?: {
readonly documents?: DocumentMetadata[];
readonly processing?: ProcessingMetadata[];
readonly metadataById?: Record<string, DocumentMetadata>;
};
readonly knowledge?: {
readonly kgCores?: string[];
readonly deCores?: string[];
readonly loadedKgCores?: string[];
};
readonly collections?: Array<Record<string, unknown>>;
readonly graph?: {
readonly triplesByFlowCollection?: Record<string, Triple[]>;
readonly explainTriplesByGraph?: Record<string, Triple[]>;
};
readonly chat?: {
readonly delayFrames?: number;
};
}
interface UploadSession {
readonly metadata: DocumentMetadata;
readonly chunks: string[];
readonly totalSize: number;
readonly chunkSize: number;
readonly totalChunks: number;
}
interface MockState {
readonly settings: Required<NonNullable<MockWorkbenchFixture["settings"]>>;
readonly flows: {
readonly activeIds: string[];
readonly definitions: Record<string, Record<string, unknown>>;
readonly blueprints: Record<string, Record<string, unknown>>;
};
readonly config: {
readonly prompt: Record<string, unknown>;
readonly valuesByType: ConfigValues;
};
readonly library: {
readonly documents: DocumentMetadata[];
readonly processing: ProcessingMetadata[];
readonly metadataById: Record<string, DocumentMetadata>;
readonly uploads: Record<string, UploadSession>;
};
readonly knowledge: {
readonly kgCores: string[];
readonly deCores: string[];
readonly loadedKgCores: string[];
};
readonly collections: Array<Record<string, unknown>>;
readonly graph: {
readonly triplesByFlowCollection: Record<string, Triple[]>;
readonly explainTriplesByGraph: Record<string, Triple[]>;
};
readonly chat: {
readonly delayFrames: number;
};
}
interface MockBaseApi extends BaseApi {
makeRequest<RequestType extends object, ResponseType>(
service: string,
request: RequestType,
timeout?: number,
retries?: number,
flow?: string,
): Promise<ResponseType>;
makeRequestMulti<RequestType extends object, ResponseType>(
service: string,
request: RequestType,
receiver: (resp: unknown) => boolean,
timeout?: number,
retries?: number,
flow?: string,
): Promise<ResponseType>;
}
const encodeJsonUnknown = S.encodeUnknownOption(S.fromJsonString(S.Unknown));
const decodeJsonUnknown = S.decodeUnknownOption(S.UnknownFromJsonString);
const iri = (value: string) => ({ t: "i" as const, i: value });
const literal = (value: string) => ({ t: "l" as const, v: value });
function encodeJson(value: unknown): string {
return Option.getOrElse(encodeJsonUnknown(value), () => "{}");
}
function decodeJson(value: unknown): unknown {
if (typeof value !== "string") return value;
return Option.getOrElse(decodeJsonUnknown(value), () => value);
}
function asRecord(value: unknown): Record<string, unknown> {
return value !== null && typeof value === "object" ? value as Record<string, unknown> : {};
}
function stringValue(value: unknown, fallback = ""): string {
return typeof value === "string" && value.length > 0 ? value : fallback;
}
function numberValue(value: unknown, fallback: number): number {
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
}
function withDefaultCollection(value: unknown): string {
return stringValue(value, "default");
}
function graphKey(flow: string | undefined, collection: unknown): string {
return `${flow ?? "default"}:${withDefaultCollection(collection)}`;
}
function cloneArray<A>(items: readonly A[] | undefined): A[] {
return items === undefined ? [] : [...items];
}
function defaultConfigValues(): ConfigValues {
return {
mcp: {
"qa-search": encodeJson({
url: "http://localhost:8383/mcp",
"remote-name": "qa-search",
"auth-token": "qa-token",
}),
},
tool: {
"qa-search-tool": encodeJson({
type: "mcp-tool",
name: "QA Search",
description: "Search tool used by browser QA",
"mcp-tool": "qa-search",
group: ["default"],
arguments: [{ name: "query", type: "string", description: "Search query" }],
}),
},
"token-cost": {
"qa-model": encodeJson({ input_price: 1.25, output_price: 2.5 }),
},
};
}
function defaultTriples(): Triple[] {
return [
{ s: iri("https://example.test/Alice"), p: iri("http://www.w3.org/2000/01/rdf-schema#label"), o: literal("Alice") },
{ s: iri("https://example.test/Bob"), p: iri("http://www.w3.org/2000/01/rdf-schema#label"), o: literal("Bob") },
{ s: iri("https://example.test/Acme"), p: iri("http://www.w3.org/2000/01/rdf-schema#label"), o: literal("Acme") },
{ s: iri("https://example.test/Alice"), p: iri("https://schema.org/worksFor"), o: iri("https://example.test/Acme") },
{ s: iri("https://example.test/Alice"), p: iri("https://schema.org/knows"), o: iri("https://example.test/Bob") },
];
}
function createState(fixture: MockWorkbenchFixture = {}): MockState {
const documents = cloneArray(fixture.library?.documents);
const defaultDocument: DocumentMetadata = {
id: "qa-doc-1",
title: "QA Document",
kind: "text/plain",
comments: "Seeded document for browser QA",
tags: ["qa", "seed"],
time: 1_700_000_000,
user: "qa-user",
"document-type": "source",
documentType: "source",
};
if (documents.length === 0) documents.push(defaultDocument);
const metadataById: Record<string, DocumentMetadata> = {
[defaultDocument.id ?? "qa-doc-1"]: defaultDocument,
...(fixture.library?.metadataById ?? {}),
};
for (const document of documents) {
if (document.id !== undefined) metadataById[document.id] = document;
}
const triples = defaultTriples();
return {
settings: {
user: fixture.settings?.user ?? "qa-user",
apiKey: fixture.settings?.apiKey ?? "",
gatewayUrl: fixture.settings?.gatewayUrl ?? "",
collection: fixture.settings?.collection ?? "default",
featureSwitches: {
flowClasses: true,
submissions: true,
tokenCost: true,
schemas: true,
structuredQuery: true,
ontologyEditor: true,
agentTools: true,
mcpTools: true,
llmModels: true,
...(fixture.settings?.featureSwitches ?? {}),
},
},
flows: {
activeIds: cloneArray(fixture.flows?.activeIds).length > 0 ? cloneArray(fixture.flows?.activeIds) : ["default", "qa-flow"],
definitions: {
default: { id: "default", description: "Default QA flow", class: "qa.default" },
"qa-flow": { id: "qa-flow", description: "Seeded QA flow", class: "qa.seed" },
...(fixture.flows?.definitions ?? {}),
},
blueprints: {
"qa-blueprint": {
name: "qa-blueprint",
description: "Blueprint used by browser QA",
parameters: { temperature: { type: "number", default: 0.1 } },
},
...(fixture.flows?.blueprints ?? {}),
},
},
config: {
prompt: {
system: "You are the QA system prompt.",
"qa-template": {
system: "QA template system",
prompt: "Answer the QA question: {{question}}",
},
...(fixture.config?.prompt ?? {}),
},
valuesByType: {
...defaultConfigValues(),
...(fixture.config?.valuesByType ?? {}),
},
},
library: {
documents,
processing: cloneArray(fixture.library?.processing),
metadataById,
uploads: {},
},
knowledge: {
kgCores: cloneArray(fixture.knowledge?.kgCores).length > 0 ? cloneArray(fixture.knowledge?.kgCores) : ["qa-core"],
deCores: cloneArray(fixture.knowledge?.deCores).length > 0 ? cloneArray(fixture.knowledge?.deCores) : ["qa-de-core"],
loadedKgCores: cloneArray(fixture.knowledge?.loadedKgCores),
},
collections: cloneArray(fixture.collections).length > 0
? cloneArray(fixture.collections)
: [{ id: "default", collection: "default", name: "default" }, { id: "qa-collection", collection: "qa-collection", name: "QA Collection" }],
graph: {
triplesByFlowCollection: {
"default:default": triples,
"qa-flow:default": triples,
...(fixture.graph?.triplesByFlowCollection ?? {}),
},
explainTriplesByGraph: {
"urn:qa-explain": triples.slice(0, 3),
...(fixture.graph?.explainTriplesByGraph ?? {}),
},
},
chat: {
delayFrames: fixture.chat?.delayFrames ?? 0,
},
};
}
function configValues(state: MockState, type: string) {
return Object.entries(state.config.valuesByType[type] ?? {}).map(([key, value]) => ({
type,
key,
value: typeof value === "string" ? value : encodeJson(value),
}));
}
function addDocument(state: MockState, metadata: DocumentMetadata): DocumentMetadata {
const id = metadata.id ?? `qa-doc-${state.library.documents.length + 1}`;
const document = {
...metadata,
id,
title: metadata.title ?? id,
kind: metadata.kind ?? metadata["document-type"] ?? "text/plain",
time: metadata.time ?? Math.floor(Date.now() / 1000),
user: metadata.user ?? state.settings.user,
tags: metadata.tags ?? [],
};
state.library.documents.push(document);
state.library.metadataById[id] = document;
return document;
}
function dispatchRequest(state: MockState, service: string, request: Record<string, unknown>, flow: string | undefined): unknown {
switch (service) {
case "flow":
return dispatchFlow(state, request);
case "config":
return dispatchConfig(state, request);
case "librarian":
return dispatchLibrarian(state, request);
case "knowledge":
return dispatchKnowledge(state, request);
case "collection-management":
return dispatchCollections(state, request);
case "triples":
return dispatchTriples(state, request, flow);
default:
return {};
}
}
function dispatchFlow(state: MockState, request: Record<string, unknown>): unknown {
const operation = request.operation;
if (operation === "list-flows") return { "flow-ids": [...state.flows.activeIds] };
if (operation === "get-flow") {
const id = stringValue(request["flow-id"], "default");
return { flow: encodeJson(state.flows.definitions[id] ?? { id, description: "Mock flow" }) };
}
if (operation === "list-blueprints") return { "blueprint-names": Object.keys(state.flows.blueprints).sort() };
if (operation === "get-blueprint") {
const name = stringValue(request["blueprint-name"], "qa-blueprint");
return { "blueprint-definition": encodeJson(state.flows.blueprints[name] ?? {}) };
}
if (operation === "start-flow") {
const id = stringValue(request["flow-id"], `qa-flow-${state.flows.activeIds.length + 1}`);
if (!state.flows.activeIds.includes(id)) state.flows.activeIds.push(id);
state.flows.definitions[id] = {
id,
description: stringValue(request.description, "QA flow"),
blueprint: stringValue(request["blueprint-name"], "qa-blueprint"),
parameters: asRecord(request.parameters),
};
state.graph.triplesByFlowCollection[`${id}:default`] = defaultTriples();
return {};
}
if (operation === "stop-flow") {
const id = stringValue(request["flow-id"]);
state.flows.activeIds.splice(0, state.flows.activeIds.length, ...state.flows.activeIds.filter((flowId) => flowId !== id));
return {};
}
return {};
}
function dispatchConfig(state: MockState, request: Record<string, unknown>): unknown {
const operation = request.operation;
if (operation === "config") return { config: { prompt: state.config.prompt } };
if (operation === "getvalues") return { values: configValues(state, stringValue(request.type)) };
if (operation === "list") {
const type = stringValue(request.type);
return { keys: Object.keys(state.config.valuesByType[type] ?? {}) };
}
if (operation === "put" && Array.isArray(request.values)) {
for (const value of request.values) {
const item = asRecord(value);
const type = stringValue(item.type);
const key = stringValue(item.key);
if (type.length > 0 && key.length > 0) {
state.config.valuesByType[type] = state.config.valuesByType[type] ?? {};
state.config.valuesByType[type][key] = item.value;
}
}
return {};
}
if (operation === "delete" && Array.isArray(request.keys)) {
for (const value of request.keys) {
const item = asRecord(value);
const type = stringValue(item.type);
const key = stringValue(item.key);
delete state.config.valuesByType[type]?.[key];
}
return {};
}
return {};
}
function dispatchLibrarian(state: MockState, request: Record<string, unknown>): unknown {
const operation = request.operation;
if (operation === "list-documents") return { "document-metadatas": [...state.library.documents] };
if (operation === "list-processing") return { "processing-metadatas": [...state.library.processing] };
if (operation === "get-document-metadata") {
const id = stringValue(request["document-id"] ?? request.documentId);
return { "document-metadata": state.library.metadataById[id] ?? null };
}
if (operation === "add-document") {
addDocument(state, asRecord(request["document-metadata"] ?? request.documentMetadata) as DocumentMetadata);
return {};
}
if (operation === "remove-document") {
const id = stringValue(request["document-id"] ?? request.documentId);
state.library.documents.splice(0, state.library.documents.length, ...state.library.documents.filter((document) => document.id !== id));
delete state.library.metadataById[id];
return {};
}
if (operation === "begin-upload") {
const totalSize = numberValue(request["total-size"], 0);
const chunkSize = Math.max(256_000, numberValue(request["chunk-size"], 512_000));
const totalChunks = Math.max(1, Math.ceil(totalSize / chunkSize));
const uploadId = `qa-upload-${Object.keys(state.library.uploads).length + 1}`;
state.library.uploads[uploadId] = {
metadata: asRecord(request["document-metadata"] ?? request.documentMetadata) as DocumentMetadata,
chunks: [],
totalSize,
chunkSize,
totalChunks,
};
return { "upload-id": uploadId, "chunk-size": chunkSize, "total-chunks": totalChunks };
}
if (operation === "upload-chunk") {
const uploadId = stringValue(request["upload-id"]);
const chunkIndex = numberValue(request["chunk-index"], 0);
const upload = state.library.uploads[uploadId];
if (upload !== undefined) upload.chunks[chunkIndex] = stringValue(request.content);
return { "chunks-received": upload?.chunks.filter((chunk) => chunk !== undefined).length ?? 0 };
}
if (operation === "complete-upload") {
const uploadId = stringValue(request["upload-id"]);
const upload = state.library.uploads[uploadId];
if (upload !== undefined) {
addDocument(state, { ...upload.metadata, id: upload.metadata.id ?? uploadId });
delete state.library.uploads[uploadId];
}
return { "document-id": uploadId, "object-id": uploadId };
}
return {};
}
function dispatchKnowledge(state: MockState, request: Record<string, unknown>): unknown {
const operation = request.operation;
const id = stringValue(request.id);
if (operation === "list-kg-cores") return { ids: [...state.knowledge.kgCores] };
if (operation === "list-de-cores") return { ids: [...state.knowledge.deCores] };
if (operation === "load-kg-core") {
if (id.length > 0 && !state.knowledge.loadedKgCores.includes(id)) state.knowledge.loadedKgCores.push(id);
return {};
}
if (operation === "delete-kg-core") {
state.knowledge.kgCores.splice(0, state.knowledge.kgCores.length, ...state.knowledge.kgCores.filter((core) => core !== id));
return {};
}
return {};
}
function dispatchCollections(state: MockState, request: Record<string, unknown>): unknown {
const operation = request.operation;
const collection = stringValue(request.collection, "default");
if (operation === "list-collections") return { collections: [...state.collections] };
if (operation === "update-collection") {
const entry = {
id: collection,
collection,
name: stringValue(request.name, collection),
description: stringValue(request.description),
tags: Array.isArray(request.tags) ? request.tags : [],
};
state.collections.splice(0, state.collections.length, ...state.collections.filter((item) => item.collection !== collection && item.id !== collection), entry);
return { collections: [entry] };
}
if (operation === "delete-collection") {
state.collections.splice(0, state.collections.length, ...state.collections.filter((item) => item.collection !== collection && item.id !== collection));
return {};
}
return {};
}
function dispatchTriples(state: MockState, request: Record<string, unknown>, flow: string | undefined): unknown {
const graph = stringValue(request.g);
const triples = graph.length > 0
? state.graph.explainTriplesByGraph[graph] ?? []
: state.graph.triplesByFlowCollection[graphKey(flow, request.collection)] ?? [];
return { triples: triples.slice(0, numberValue(request.limit, triples.length)) };
}
function streamResponses(service: string): Record<string, unknown>[] {
const metadata: StreamingMetadata = { model: "qa-model", in_token: 12, out_token: 24 };
if (service === "agent") {
return [
{ chunk_type: "thought", content: "Thinking about the QA request.", end_of_message: true },
{ chunk_type: "observation", content: "Observed seeded graph and document context.", end_of_message: true },
{ chunk_type: "answer", content: "Mock agent answer from TrustGraph.", end_of_message: true, end_of_dialog: true, ...metadata },
];
}
if (service === "document-rag") {
return [
{ message_type: "explain", explain_id: "qa-document-explain", explain_graph: "urn:qa-explain" },
{ response: "Mock document answer ", end_of_session: false },
{ response: "from QA library.", end_of_session: true, ...metadata },
];
}
return [
{ message_type: "explain", explain_id: "qa-graph-explain", explain_graph: "urn:qa-explain" },
{ response: "Mock graph answer ", end_of_session: false },
{ response: "from QA knowledge graph.", end_of_session: true, ...metadata },
];
}
function scheduleFrames(frames: number, callback: () => void): void {
const schedule = typeof requestAnimationFrame === "function" ? requestAnimationFrame : (fn: FrameRequestCallback) => queueMicrotask(() => fn(0));
if (frames <= 0) {
schedule(callback);
} else {
schedule(() => scheduleFrames(frames - 1, callback));
}
}
function dispatchStream<ResponseType>(
state: MockState,
service: string,
receiver: (resp: unknown) => boolean,
): Promise<ResponseType> {
const responses = streamResponses(service);
const emit = (index: number) => {
const response = responses[index];
if (response === undefined) return;
const complete = index === responses.length - 1;
const done = receiver({ response, complete });
if (done !== true && !complete) {
scheduleFrames(state.chat.delayFrames, () => emit(index + 1));
}
};
scheduleFrames(state.chat.delayFrames, () => emit(0));
return Promise.resolve({} as ResponseType);
}
export function makeMockBaseApi(fixture: MockWorkbenchFixture = {}): BaseApi {
const state = createState(fixture);
const api = Object.create(BaseApi.prototype) as MockBaseApi;
api.tag = "mock-workbench";
api.id = 1;
api.token = state.settings.apiKey.length > 0 ? state.settings.apiKey : undefined;
api.user = state.settings.user;
api.socketUrl = state.settings.gatewayUrl;
api.makeRequest = function makeRequest<RequestType extends object, ResponseType>(
service: string,
request: RequestType,
_timeout?: number,
_retries?: number,
flow?: string,
) {
return Promise.resolve(dispatchRequest(state, service, request as Record<string, unknown>, flow) as ResponseType);
};
api.makeRequestMulti = function makeRequestMulti<RequestType extends object, ResponseType>(
service: string,
_request: RequestType,
receiver: (resp: unknown) => boolean,
_timeout?: number,
_retries?: number,
_flow?: string,
) {
return dispatchStream<ResponseType>(state, service, receiver);
};
api.onConnectionStateChange = function onConnectionStateChange(listener: (state: ConnectionState) => void) {
listener({
status: api.token === undefined ? "unauthenticated" : "authenticated",
hasApiKey: api.token !== undefined,
});
return () => {};
};
api.close = function close() {};
return api;
}
export function qaSettingsFromFixture(fixture: MockWorkbenchFixture = {}) {
return createState(fixture).settings;
}
export function decodeConfigFixtureValue(value: unknown): unknown {
return decodeJson(value);
}