@manta-eu/client 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/dist/generated/graphql.d.ts +1132 -2
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +637 -0
- package/dist/generated/graphql.js.map +1 -0
- package/dist/graphql-clients.d.ts +10 -1
- package/dist/graphql-clients.d.ts.map +1 -1
- package/dist/graphql-clients.js +25 -3
- package/dist/graphql-clients.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/shared/action-document.js +1 -0
- package/dist/shared/action-document.js.map +1 -0
- package/dist/shared/action-plan.js +1 -0
- package/dist/shared/action-plan.js.map +1 -0
- package/dist/shared/agent-mode.js +1 -0
- package/dist/shared/agent-mode.js.map +1 -0
- package/dist/shared/agent-types.js +1 -0
- package/dist/shared/agent-types.js.map +1 -0
- package/dist/shared/chat-context.js +1 -0
- package/dist/shared/chat-context.js.map +1 -0
- package/dist/shared/chat-manager.d.ts +6 -0
- package/dist/shared/chat-manager.d.ts.map +1 -1
- package/dist/shared/chat-manager.js +5 -0
- package/dist/shared/chat-manager.js.map +1 -0
- package/dist/shared/chat-turn-errors.d.ts +10 -0
- package/dist/shared/chat-turn-errors.d.ts.map +1 -1
- package/dist/shared/chat-turn-errors.js +11 -0
- package/dist/shared/chat-turn-errors.js.map +1 -0
- package/dist/shared/constants.d.ts +1 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +2 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/conversation-starter-defaults.js +1 -0
- package/dist/shared/conversation-starter-defaults.js.map +1 -0
- package/dist/shared/datastore-id.js +1 -0
- package/dist/shared/datastore-id.js.map +1 -0
- package/dist/shared/datastore-path.js +1 -0
- package/dist/shared/datastore-path.js.map +1 -0
- package/dist/shared/duration.js +1 -0
- package/dist/shared/duration.js.map +1 -0
- package/dist/shared/email.js +1 -0
- package/dist/shared/email.js.map +1 -0
- package/dist/shared/entity-set.js +1 -0
- package/dist/shared/entity-set.js.map +1 -0
- package/dist/shared/error-utils.js +1 -0
- package/dist/shared/error-utils.js.map +1 -0
- package/dist/shared/explorer-paths.js +1 -0
- package/dist/shared/explorer-paths.js.map +1 -0
- package/dist/shared/fileIri.js +1 -0
- package/dist/shared/fileIri.js.map +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/inferal-stream.js +1 -0
- package/dist/shared/inferal-stream.js.map +1 -0
- package/dist/shared/live-context.js +1 -0
- package/dist/shared/live-context.js.map +1 -0
- package/dist/shared/maintenance-schedule.js +1 -0
- package/dist/shared/maintenance-schedule.js.map +1 -0
- package/dist/shared/memory-citation-sampling.js +1 -0
- package/dist/shared/memory-citation-sampling.js.map +1 -0
- package/dist/shared/memory-confidence.js +1 -0
- package/dist/shared/memory-confidence.js.map +1 -0
- package/dist/shared/memory-consolidation-limits.js +1 -0
- package/dist/shared/memory-consolidation-limits.js.map +1 -0
- package/dist/shared/memory-dates.js +1 -0
- package/dist/shared/memory-dates.js.map +1 -0
- package/dist/shared/memory-decay.js +1 -0
- package/dist/shared/memory-decay.js.map +1 -0
- package/dist/shared/memory-diversity.js +1 -0
- package/dist/shared/memory-diversity.js.map +1 -0
- package/dist/shared/memory-entity-recall.js +1 -0
- package/dist/shared/memory-entity-recall.js.map +1 -0
- package/dist/shared/memory-importance.js +1 -0
- package/dist/shared/memory-importance.js.map +1 -0
- package/dist/shared/memory-keyword-recall.js +1 -0
- package/dist/shared/memory-keyword-recall.js.map +1 -0
- package/dist/shared/memory-kind-iris.js +1 -0
- package/dist/shared/memory-kind-iris.js.map +1 -0
- package/dist/shared/memory-preload-gate.js +1 -0
- package/dist/shared/memory-preload-gate.js.map +1 -0
- package/dist/shared/memory-sources.js +1 -0
- package/dist/shared/memory-sources.js.map +1 -0
- package/dist/shared/memory-teams.js +1 -0
- package/dist/shared/memory-teams.js.map +1 -0
- package/dist/shared/memory-token-budget.js +1 -0
- package/dist/shared/memory-token-budget.js.map +1 -0
- package/dist/shared/memory-volatility.js +1 -0
- package/dist/shared/memory-volatility.js.map +1 -0
- package/dist/shared/message-utils.js +1 -0
- package/dist/shared/message-utils.js.map +1 -0
- package/dist/shared/model.js +1 -0
- package/dist/shared/model.js.map +1 -0
- package/dist/shared/org-roles.js +1 -0
- package/dist/shared/org-roles.js.map +1 -0
- package/dist/shared/page-context.js +1 -0
- package/dist/shared/page-context.js.map +1 -0
- package/dist/shared/permissions.js +1 -0
- package/dist/shared/permissions.js.map +1 -0
- package/dist/shared/questionnaire.js +1 -0
- package/dist/shared/questionnaire.js.map +1 -0
- package/dist/shared/rdf-namespaces.js +1 -0
- package/dist/shared/rdf-namespaces.js.map +1 -0
- package/dist/shared/rule-iri.js +1 -0
- package/dist/shared/rule-iri.js.map +1 -0
- package/dist/shared/rule-search.js +1 -0
- package/dist/shared/rule-search.js.map +1 -0
- package/dist/shared/session-types.js +1 -0
- package/dist/shared/session-types.js.map +1 -0
- package/dist/shared/stakeholder-iri.js +1 -0
- package/dist/shared/stakeholder-iri.js.map +1 -0
- package/dist/shared/team-activation.js +1 -0
- package/dist/shared/team-activation.js.map +1 -0
- package/dist/shared/team-filters.js +1 -0
- package/dist/shared/team-filters.js.map +1 -0
- package/dist/shared/team.d.ts +1 -0
- package/dist/shared/team.d.ts.map +1 -1
- package/dist/shared/team.js +1 -0
- package/dist/shared/team.js.map +1 -0
- package/dist/shared/tools/coercers.js +1 -0
- package/dist/shared/tools/coercers.js.map +1 -0
- package/dist/shared/tools/index.js +1 -0
- package/dist/shared/tools/index.js.map +1 -0
- package/dist/shared/tools/metadata.js +1 -0
- package/dist/shared/tools/metadata.js.map +1 -0
- package/dist/shared/tools/test-triple.js +1 -0
- package/dist/shared/tools/test-triple.js.map +1 -0
- package/dist/shared/tools/types.js +1 -0
- package/dist/shared/tools/types.js.map +1 -0
- package/dist/shared/upload.js +1 -0
- package/dist/shared/upload.js.map +1 -0
- package/dist/shared/website-url.js +1 -0
- package/dist/shared/website-url.js.map +1 -0
- package/dist/shared/workspace-artifacts.js +1 -0
- package/dist/shared/workspace-artifacts.js.map +1 -0
- package/dist/shared/writable-shape-query.js +1 -0
- package/dist/shared/writable-shape-query.js.map +1 -0
- package/dist/types/jsonld.js +1 -0
- package/dist/types/jsonld.js.map +1 -0
- package/dist/types/sparql-types.js +1 -0
- package/dist/types/sparql-types.js.map +1 -0
- package/package.json +11 -6
- package/src/generated/graphql.ts +12158 -0
- package/src/graphql-clients.ts +67 -0
- package/src/index.ts +2 -0
- package/src/shared/action-document.ts +56 -0
- package/src/shared/action-plan.ts +1 -0
- package/src/shared/agent-mode.ts +46 -0
- package/src/shared/agent-types.ts +46 -0
- package/src/shared/chat-context.ts +47 -0
- package/src/shared/chat-manager.ts +199 -0
- package/src/shared/chat-turn-errors.ts +35 -0
- package/src/shared/constants.ts +19 -0
- package/src/shared/conversation-starter-defaults.ts +27 -0
- package/src/shared/datastore-id.ts +25 -0
- package/src/shared/datastore-path.ts +80 -0
- package/src/shared/duration.ts +61 -0
- package/src/shared/email.ts +54 -0
- package/src/shared/entity-set.ts +33 -0
- package/src/shared/error-utils.ts +79 -0
- package/src/shared/explorer-paths.ts +9 -0
- package/src/shared/fileIri.ts +25 -0
- package/src/shared/index.ts +31 -0
- package/src/shared/inferal-stream.ts +2 -0
- package/src/shared/live-context.ts +15 -0
- package/src/shared/maintenance-schedule.ts +47 -0
- package/src/shared/memory-citation-sampling.ts +26 -0
- package/src/shared/memory-confidence.ts +8 -0
- package/src/shared/memory-consolidation-limits.ts +9 -0
- package/src/shared/memory-dates.ts +7 -0
- package/src/shared/memory-decay.ts +70 -0
- package/src/shared/memory-diversity.ts +55 -0
- package/src/shared/memory-entity-recall.ts +26 -0
- package/src/shared/memory-importance.ts +12 -0
- package/src/shared/memory-keyword-recall.ts +149 -0
- package/src/shared/memory-kind-iris.ts +36 -0
- package/src/shared/memory-preload-gate.ts +150 -0
- package/src/shared/memory-sources.ts +5 -0
- package/src/shared/memory-teams.ts +3 -0
- package/src/shared/memory-token-budget.ts +31 -0
- package/src/shared/memory-volatility.ts +90 -0
- package/src/shared/message-utils.ts +37 -0
- package/src/shared/model.ts +117 -0
- package/src/shared/org-roles.ts +21 -0
- package/src/shared/page-context.ts +7 -0
- package/src/shared/permissions.ts +19 -0
- package/src/shared/questionnaire.ts +76 -0
- package/src/shared/rdf-namespaces.ts +62 -0
- package/src/shared/rule-iri.ts +15 -0
- package/src/shared/rule-search.ts +1 -0
- package/src/shared/session-types.ts +14 -0
- package/src/shared/stakeholder-iri.ts +23 -0
- package/src/shared/team-activation.ts +9 -0
- package/src/shared/team-filters.ts +33 -0
- package/src/shared/team.ts +244 -0
- package/src/shared/tools/coercers.ts +82 -0
- package/src/shared/tools/index.ts +49 -0
- package/src/shared/tools/metadata.ts +205 -0
- package/src/shared/tools/test-triple.ts +33 -0
- package/src/shared/tools/types.ts +302 -0
- package/src/shared/upload.ts +57 -0
- package/src/shared/website-url.ts +32 -0
- package/src/shared/workspace-artifacts.ts +68 -0
- package/src/shared/writable-shape-query.ts +24 -0
- package/src/types/jsonld.ts +19 -0
- package/src/types/sparql-types.ts +10 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
import type { Duration } from "iso8601-duration";
|
|
3
|
+
import { parse, toSeconds } from "iso8601-duration";
|
|
4
|
+
|
|
5
|
+
export function parseDuration(isoDuration: string): Duration | null {
|
|
6
|
+
try {
|
|
7
|
+
return parse(isoDuration);
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatDuration(isoDuration: string): string {
|
|
14
|
+
const duration = parseDuration(isoDuration);
|
|
15
|
+
if (!duration) return isoDuration;
|
|
16
|
+
|
|
17
|
+
const parts = [];
|
|
18
|
+
|
|
19
|
+
if (duration.days) parts.push(`${duration.days}d`);
|
|
20
|
+
if (duration.hours) parts.push(`${duration.hours}h`);
|
|
21
|
+
if (duration.minutes) parts.push(`${duration.minutes}m`);
|
|
22
|
+
if (duration.seconds) parts.push(`${duration.seconds}s`);
|
|
23
|
+
|
|
24
|
+
return isPresent(parts) ? parts.join(" ") : "0m";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function durationToSeconds(isoDuration: string): number | null {
|
|
28
|
+
const duration = parseDuration(isoDuration);
|
|
29
|
+
if (!duration) return null;
|
|
30
|
+
|
|
31
|
+
return toSeconds(duration);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatDurationSmart(isoDuration: string): string {
|
|
35
|
+
const duration = parseDuration(isoDuration);
|
|
36
|
+
if (!duration) return isoDuration;
|
|
37
|
+
|
|
38
|
+
const totalSeconds = toSeconds(duration);
|
|
39
|
+
|
|
40
|
+
if (totalSeconds < 60) {
|
|
41
|
+
return `${Math.round(totalSeconds)}s`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (totalSeconds < 3600) {
|
|
45
|
+
const minutes = Math.round(totalSeconds / 60);
|
|
46
|
+
return `${minutes}m`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (totalSeconds < 86400) {
|
|
50
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
51
|
+
const remainingMinutes = Math.round((totalSeconds % 3600) / 60);
|
|
52
|
+
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
56
|
+
const remainingHours = Math.floor((totalSeconds % 86400) / 3600);
|
|
57
|
+
if (remainingHours > 0) {
|
|
58
|
+
return `${days}d ${remainingHours}h`;
|
|
59
|
+
}
|
|
60
|
+
return `${days}d`;
|
|
61
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { nco, nmo } from "@manta-eu/ldo/namespaces";
|
|
2
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
3
|
+
import { ExecuteAgentActionAdditionDocument, type GQLExecuteAgentActionAdditionMutation } from "../generated/graphql";
|
|
4
|
+
|
|
5
|
+
export type EmailGraphql = (document: string, variables: Record<string, unknown>) => Promise<unknown>;
|
|
6
|
+
|
|
7
|
+
export type RealmEmailTerms = { emailMessageShape: string; renderedFrom: string };
|
|
8
|
+
|
|
9
|
+
export type RenderedEmail = {
|
|
10
|
+
subjectIri: string;
|
|
11
|
+
from: string;
|
|
12
|
+
to: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
renderedFrom: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type EmailStart = { ok: true; plan: string } | { ok: false; error: string };
|
|
18
|
+
|
|
19
|
+
function renderedEmailAddition(terms: RealmEmailTerms, email: RenderedEmail) {
|
|
20
|
+
return {
|
|
21
|
+
subjectIri: email.subjectIri,
|
|
22
|
+
rdfType: nmo.Email,
|
|
23
|
+
actionShape: terms.emailMessageShape,
|
|
24
|
+
properties: {
|
|
25
|
+
"@id": email.subjectIri,
|
|
26
|
+
"@type": nmo.Email,
|
|
27
|
+
[nmo.messageFrom]: { "@type": nco.EmailAddress, [nco.emailAddress]: email.from },
|
|
28
|
+
[nmo.emailTo]: { "@type": nco.EmailAddress, [nco.emailAddress]: email.to },
|
|
29
|
+
[nmo.messageSubject]: email.subject,
|
|
30
|
+
[nmo.contentMimeType]: "text/html",
|
|
31
|
+
[terms.renderedFrom]: { "@id": email.renderedFrom },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function startRenderedEmail(
|
|
37
|
+
graphql: EmailGraphql,
|
|
38
|
+
terms: RealmEmailTerms,
|
|
39
|
+
email: RenderedEmail,
|
|
40
|
+
): Promise<EmailStart> {
|
|
41
|
+
try {
|
|
42
|
+
const data = (await graphql(ExecuteAgentActionAdditionDocument, {
|
|
43
|
+
input: renderedEmailAddition(terms, email),
|
|
44
|
+
})) as GQLExecuteAgentActionAdditionMutation | undefined;
|
|
45
|
+
const addition = data?.executeActionAddition;
|
|
46
|
+
|
|
47
|
+
if (addition?.success === true && isPresent(addition.namedGraphIri)) {
|
|
48
|
+
return { ok: true, plan: addition.namedGraphIri };
|
|
49
|
+
}
|
|
50
|
+
return { ok: false, error: addition?.error ?? "The email action did not start" };
|
|
51
|
+
} catch (cause) {
|
|
52
|
+
return { ok: false, error: cause instanceof Error ? cause.message : String(cause) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { isPresent } from "@manta-eu/lib/present";
|
|
2
|
+
import { detailViewTypes, lensViewTypes } from "@manta-eu/rdf-lib/detail-view-types";
|
|
3
|
+
import { groupBy, toPairs } from "ramda";
|
|
4
|
+
import { getJsonLdGraph, getNodeId, getNodeTypes, type JsonLdGraphDocument } from "../types/jsonld";
|
|
5
|
+
import { expandPrefix } from "./rdf-namespaces";
|
|
6
|
+
|
|
7
|
+
export const selfFetchingTypes = detailViewTypes.map(expandPrefix);
|
|
8
|
+
|
|
9
|
+
export const displayFactTypes = lensViewTypes.map(expandPrefix);
|
|
10
|
+
|
|
11
|
+
export function entityTypesByIri(graph: JsonLdGraphDocument) {
|
|
12
|
+
const typesByIri = new Map<string, string[]>();
|
|
13
|
+
|
|
14
|
+
for (const node of getJsonLdGraph(graph)) {
|
|
15
|
+
const id = getNodeId(node);
|
|
16
|
+
if (id === undefined) continue;
|
|
17
|
+
typesByIri.set(expandPrefix(id), getNodeTypes(node).map(expandPrefix));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return typesByIri;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type EntityTypeGroup = { typeIris: string[]; iris: string[] };
|
|
24
|
+
|
|
25
|
+
export function groupEntitiesByType(entityIris: readonly string[], typesByIri: Map<string, string[]>) {
|
|
26
|
+
const typeKey = (iri: string) => [...(typesByIri.get(expandPrefix(iri)) ?? [])].sort().join("|");
|
|
27
|
+
|
|
28
|
+
return toPairs(groupBy(typeKey, entityIris)).map(([key, iris = []]) => ({
|
|
29
|
+
key,
|
|
30
|
+
typeIris: isPresent(key) ? key.split("|") : [],
|
|
31
|
+
iris,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// hono owns this union. A hand-written copy drifts, and `Exclude<number, ...>` does not narrow.
|
|
2
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
3
|
+
|
|
4
|
+
export type ErrorInfo = {
|
|
5
|
+
message: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type HttpError = Error & { status: ContentfulStatusCode };
|
|
10
|
+
|
|
11
|
+
export function createHttpError(message: string, status: ContentfulStatusCode): HttpError {
|
|
12
|
+
const err = new Error(message) as HttpError;
|
|
13
|
+
err.status = status;
|
|
14
|
+
return err;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isHttpError(e: unknown): e is HttpError {
|
|
18
|
+
return e instanceof Error && typeof (e as HttpError).status === "number";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isNetworkError(error: unknown): boolean {
|
|
22
|
+
return (
|
|
23
|
+
error instanceof TypeError &&
|
|
24
|
+
(error.message.includes("fetch") || error.message.includes("network") || error.message.includes("Failed to fetch"))
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isGraphQLError(error: unknown): error is { message: string; graphQLErrors?: unknown[] } {
|
|
29
|
+
return (
|
|
30
|
+
typeof error === "object" &&
|
|
31
|
+
error !== null &&
|
|
32
|
+
"message" in error &&
|
|
33
|
+
(("graphQLErrors" in error && Array.isArray(error.graphQLErrors)) || "networkError" in error)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function serializeError(error: unknown) {
|
|
38
|
+
if (error instanceof Error) return { name: error.name, message: error.message, stack: error.stack };
|
|
39
|
+
|
|
40
|
+
return { value: String(error) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function parseError(error: unknown, defaultMessage: string): ErrorInfo {
|
|
44
|
+
if (error instanceof Error) {
|
|
45
|
+
if (isNetworkError(error)) {
|
|
46
|
+
return {
|
|
47
|
+
message: defaultMessage,
|
|
48
|
+
description: "Network error. Please check your connection and try again.",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
message: defaultMessage,
|
|
54
|
+
description: error.message,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (isGraphQLError(error) && error.graphQLErrors && Array.isArray(error.graphQLErrors)) {
|
|
59
|
+
const firstError = error.graphQLErrors[0];
|
|
60
|
+
if (typeof firstError === "object" && firstError && "message" in firstError) {
|
|
61
|
+
return {
|
|
62
|
+
message: defaultMessage,
|
|
63
|
+
description: String(firstError.message),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof error === "string") {
|
|
69
|
+
return {
|
|
70
|
+
message: defaultMessage,
|
|
71
|
+
description: error,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
message: defaultMessage,
|
|
77
|
+
description: "An unexpected error occurred",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The URL shape of the data explorer. The server redirects into these paths and the app routes
|
|
2
|
+
// them, so both sides must read one definition.
|
|
3
|
+
export const explorerPath = "/data";
|
|
4
|
+
|
|
5
|
+
export const entitySegment = "entity";
|
|
6
|
+
|
|
7
|
+
export function entityPath(iri: string): string {
|
|
8
|
+
return `${explorerPath}/${entitySegment}/${encodeURIComponent(iri)}`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const fileIriScheme = "s3";
|
|
2
|
+
|
|
3
|
+
const fileIriPattern = /^s3:\/\/([^/]+)\/(.+)$/;
|
|
4
|
+
|
|
5
|
+
export function buildFileIri(input: { bucket: string; key: string }) {
|
|
6
|
+
return `${fileIriScheme}://${input.bucket}/${input.key}`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function isFileIri(iri: string) {
|
|
10
|
+
return fileIriPattern.test(iri);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateFileIri(iri: string) {
|
|
14
|
+
if (!isFileIri(iri)) {
|
|
15
|
+
throw new Error(`Invalid file IRI: expected ${fileIriScheme}://<bucket>/<key> - got "${iri}"`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseFileIri(iri: string) {
|
|
20
|
+
validateFileIri(iri);
|
|
21
|
+
|
|
22
|
+
const [, bucket = "", storageKey = ""] = fileIriPattern.exec(iri) ?? [];
|
|
23
|
+
|
|
24
|
+
return { bucket, storageKey };
|
|
25
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { UIMessage } from "ai";
|
|
2
|
+
import type { OperatorAllowedModel } from "./model";
|
|
3
|
+
import type { PageContext } from "./page-context";
|
|
4
|
+
|
|
5
|
+
// Approval string to be shared across frontend and backend
|
|
6
|
+
export const APPROVAL = {
|
|
7
|
+
YES: "Yes, confirmed.",
|
|
8
|
+
NO: "No, denied.",
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export type AssistantResponseState = "in-progress" | "finished" | "failed" | "aborted";
|
|
12
|
+
|
|
13
|
+
export type ChatUIMessage = UIMessage<{
|
|
14
|
+
createdAt: string;
|
|
15
|
+
userId?: string;
|
|
16
|
+
datastoreId?: string;
|
|
17
|
+
totalTokens?: number;
|
|
18
|
+
finishReason?: string;
|
|
19
|
+
responseState?: AssistantResponseState;
|
|
20
|
+
isTask?: boolean;
|
|
21
|
+
traceId?: string;
|
|
22
|
+
pageContext?: PageContext;
|
|
23
|
+
model?: OperatorAllowedModel;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
/** A blank chat, which `/` stops being once an app gives its datastores a home page. */
|
|
27
|
+
export const newChatPath = "/chat";
|
|
28
|
+
|
|
29
|
+
export function chatIdToPath(id: string) {
|
|
30
|
+
return `/chat/${encodeURIComponent(id)}`;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// :now[:Clock] is MAX(schema:dateModified) over ingested platform data, not real time, and is unbound on an
|
|
2
|
+
// empty datastore - every reader of :LiveContext_Chat has to apply memory dates itself, against NOW().
|
|
3
|
+
export const liveContextMemoryDateFilter = `FILTER NOT EXISTS {
|
|
4
|
+
?entity a :Memory .
|
|
5
|
+
{
|
|
6
|
+
?entity :memoryTrustedUntil ?horizon .
|
|
7
|
+
FILTER(?horizon <= NOW())
|
|
8
|
+
} UNION {
|
|
9
|
+
?entity :memoryValidUntil ?expiry .
|
|
10
|
+
FILTER(?expiry <= NOW())
|
|
11
|
+
} UNION {
|
|
12
|
+
?entity :memoryValidFrom ?validFrom .
|
|
13
|
+
FILTER(?validFrom > NOW())
|
|
14
|
+
}
|
|
15
|
+
}`;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { msPerDay } from "./memory-dates";
|
|
2
|
+
|
|
3
|
+
export const maintenanceTickMinutes = 30;
|
|
4
|
+
|
|
5
|
+
export const compactionPeriodHours = 24;
|
|
6
|
+
|
|
7
|
+
export function ticksInPeriod(periodHours: number): number {
|
|
8
|
+
return Math.max(1, Math.round((periodHours * 60) / maintenanceTickMinutes));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// This is FNV-1a, kept synchronous because this file ships to the browser without node:crypto.
|
|
12
|
+
export function stableSlot(key: string, slots: number): number {
|
|
13
|
+
let hash = 0x811c9dc5;
|
|
14
|
+
for (let i = 0; i < key.length; i++) {
|
|
15
|
+
hash ^= key.charCodeAt(i);
|
|
16
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
17
|
+
}
|
|
18
|
+
return hash % slots;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function sampledByKey(key: string, rate: number): boolean {
|
|
22
|
+
if (rate <= 0) return false;
|
|
23
|
+
if (rate >= 1) return true;
|
|
24
|
+
|
|
25
|
+
// 0x100000000 is 2^32, the hash's full range, so dividing by it maps the hash to a fraction in [0, 1).
|
|
26
|
+
return stableSlot(key, 0x100000000) / 0x100000000 < rate;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function currentTick(now: Date, periodHours: number): number {
|
|
30
|
+
return Math.floor(now.getTime() / (maintenanceTickMinutes * 60_000)) % ticksInPeriod(periodHours);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isDueThisTick(key: string, now: Date, periodHours: number): boolean {
|
|
34
|
+
const slots = ticksInPeriod(periodHours);
|
|
35
|
+
return stableSlot(key, slots) === currentTick(now, periodHours);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function dueThisTick(keys: readonly string[], now: Date, periodHours: number): string[] {
|
|
39
|
+
return keys.filter((key) => isDueThisTick(key, now, periodHours));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const redundancySweepDayBuckets = 7;
|
|
43
|
+
|
|
44
|
+
export function isDueOnDay(key: string, now: Date, buckets: number = redundancySweepDayBuckets): boolean {
|
|
45
|
+
const day = Math.floor(now.getTime() / msPerDay) % buckets;
|
|
46
|
+
return stableSlot(key, buckets) === day;
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sampledByKey } from "./maintenance-schedule";
|
|
2
|
+
|
|
3
|
+
// citeMemories under-counts memories that work by suppressing an answer; treat as diagnostic, not a score.
|
|
4
|
+
export const memoryCitationSampleRate = 0.1;
|
|
5
|
+
|
|
6
|
+
export function shouldRequestCitations(seed: string, rate: number = memoryCitationSampleRate): boolean {
|
|
7
|
+
return sampledByKey(seed, rate);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type CitationRequestTurn = {
|
|
11
|
+
hasPersonalPreload: boolean;
|
|
12
|
+
hasSharedSections: boolean;
|
|
13
|
+
seed: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type CitationDecision =
|
|
17
|
+
| { requested: false; reason: "none" }
|
|
18
|
+
| { requested: true; reason: "personal" | "sampled" };
|
|
19
|
+
|
|
20
|
+
export function citationDecisionForTurn(turn: CitationRequestTurn, rate?: number): CitationDecision {
|
|
21
|
+
if (turn.hasPersonalPreload) return { requested: true, reason: "personal" };
|
|
22
|
+
if (turn.hasSharedSections && shouldRequestCitations(turn.seed, rate)) {
|
|
23
|
+
return { requested: true, reason: "sampled" };
|
|
24
|
+
}
|
|
25
|
+
return { requested: false, reason: "none" };
|
|
26
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const consolidationChunkSize = 12;
|
|
2
|
+
|
|
3
|
+
export const consolidationMaxChunks = 12;
|
|
4
|
+
|
|
5
|
+
export const consolidationCandidateLimit = consolidationChunkSize * consolidationMaxChunks;
|
|
6
|
+
|
|
7
|
+
export const consolidationSeedBatchSize = 8;
|
|
8
|
+
|
|
9
|
+
export const consolidationJudgeBatchSize = 4;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { msPerDay, parseMemoryDate } from "./memory-dates";
|
|
2
|
+
|
|
3
|
+
export const personalMemoryScope = "user";
|
|
4
|
+
|
|
5
|
+
export type MemoryDecayCandidate = {
|
|
6
|
+
scope?: string | null;
|
|
7
|
+
memoryUseCount?: number | null;
|
|
8
|
+
memoryCiteCount?: number | null;
|
|
9
|
+
memoryLastUsedAt?: Date | string | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const preloadedUseWeight = 1;
|
|
13
|
+
|
|
14
|
+
export const citedUseWeight = 0;
|
|
15
|
+
|
|
16
|
+
export type UseWeights = { preloaded?: number; cited?: number };
|
|
17
|
+
|
|
18
|
+
export function weightedUseCount(memory: MemoryDecayCandidate, weights?: UseWeights): number {
|
|
19
|
+
const preloaded = Math.max(memory.memoryUseCount ?? 0, 0) * (weights?.preloaded ?? preloadedUseWeight);
|
|
20
|
+
const cited = Math.max(memory.memoryCiteCount ?? 0, 0) * (weights?.cited ?? citedUseWeight);
|
|
21
|
+
return preloaded + cited;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const decayUseCountCap = 20;
|
|
25
|
+
|
|
26
|
+
export const decayHalfLifeDays = 7;
|
|
27
|
+
|
|
28
|
+
export const decayDemotionThreshold = 0.5;
|
|
29
|
+
|
|
30
|
+
export const decayHoldOutDays = 30;
|
|
31
|
+
|
|
32
|
+
export function usageWeight(
|
|
33
|
+
useCount: number | null | undefined,
|
|
34
|
+
lastUsedAt: Date | string | null | undefined,
|
|
35
|
+
now: Date = new Date(),
|
|
36
|
+
): number {
|
|
37
|
+
const uses = Math.min(Math.max(useCount ?? 0, 0), decayUseCountCap);
|
|
38
|
+
if (uses === 0) return 0;
|
|
39
|
+
|
|
40
|
+
const lastUsed = parseMemoryDate(lastUsedAt);
|
|
41
|
+
if (!lastUsed) return uses;
|
|
42
|
+
|
|
43
|
+
const days = Math.max((now.getTime() - lastUsed.getTime()) / msPerDay, 0);
|
|
44
|
+
return uses * Math.pow(0.5, days / decayHalfLifeDays);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function coldOnsetDays(useCount: number): number {
|
|
48
|
+
// Floor at the demotion threshold, not 1: a fractional weighted count is already below it at day 0,
|
|
49
|
+
// so clamping to 1 would report an onset the decay curve never had.
|
|
50
|
+
const uses = Math.min(Math.max(useCount, decayDemotionThreshold), decayUseCountCap);
|
|
51
|
+
return decayHalfLifeDays * Math.log2(uses / decayDemotionThreshold);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function shouldDemoteOnUsage(
|
|
55
|
+
memory: MemoryDecayCandidate,
|
|
56
|
+
now: Date = new Date(),
|
|
57
|
+
weights?: UseWeights,
|
|
58
|
+
): boolean {
|
|
59
|
+
if (memory.scope !== personalMemoryScope) return false;
|
|
60
|
+
|
|
61
|
+
const uses = weightedUseCount(memory, weights);
|
|
62
|
+
if (uses <= 0) return false;
|
|
63
|
+
if (usageWeight(uses, memory.memoryLastUsedAt, now) >= decayDemotionThreshold) return false;
|
|
64
|
+
|
|
65
|
+
const lastUsed = parseMemoryDate(memory.memoryLastUsedAt);
|
|
66
|
+
if (!lastUsed) return false;
|
|
67
|
+
|
|
68
|
+
const daysSinceUse = (now.getTime() - lastUsed.getTime()) / msPerDay;
|
|
69
|
+
return daysSinceUse < coldOnsetDays(uses) + decayHoldOutDays;
|
|
70
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const memoryRedundancyThreshold = 0.6;
|
|
2
|
+
|
|
3
|
+
export const memoryDiversityCandidateMultiplier = 3;
|
|
4
|
+
|
|
5
|
+
export type DiversityCandidate = {
|
|
6
|
+
label?: string | null;
|
|
7
|
+
description?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const tokenPattern = /[a-z0-9]+/g;
|
|
11
|
+
|
|
12
|
+
const minimumTokenLength = 3;
|
|
13
|
+
|
|
14
|
+
export function memoryTokens(memory: DiversityCandidate): Set<string> {
|
|
15
|
+
const text = `${memory.label ?? ""} ${memory.description ?? ""}`.toLowerCase();
|
|
16
|
+
const tokens = text.match(tokenPattern) ?? [];
|
|
17
|
+
return new Set(tokens.filter((t) => t.length >= minimumTokenLength));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
|
|
21
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
22
|
+
let shared = 0;
|
|
23
|
+
for (const token of a) if (b.has(token)) shared += 1;
|
|
24
|
+
const union = a.size + b.size - shared;
|
|
25
|
+
return union === 0 ? 0 : shared / union;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function selectDiverseMemories<T extends DiversityCandidate>(
|
|
29
|
+
candidates: readonly T[],
|
|
30
|
+
limit: number,
|
|
31
|
+
threshold: number = memoryRedundancyThreshold,
|
|
32
|
+
): T[] {
|
|
33
|
+
if (limit <= 0) return [];
|
|
34
|
+
if (candidates.length <= limit) return [...candidates];
|
|
35
|
+
|
|
36
|
+
const tokens = candidates.map(memoryTokens);
|
|
37
|
+
const taken: number[] = [];
|
|
38
|
+
const skipped: number[] = [];
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < candidates.length && taken.length < limit; i += 1) {
|
|
41
|
+
const redundant = taken.some((j) => jaccardSimilarity(tokens[i] ?? new Set(), tokens[j] ?? new Set()) >= threshold);
|
|
42
|
+
if (redundant) skipped.push(i);
|
|
43
|
+
else taken.push(i);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const i of skipped) {
|
|
47
|
+
if (taken.length >= limit) break;
|
|
48
|
+
taken.push(i);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return taken
|
|
52
|
+
.sort((a, b) => a - b)
|
|
53
|
+
.map((i) => candidates[i])
|
|
54
|
+
.filter((memory): memory is T => memory !== undefined);
|
|
55
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const entityHopLimit = 8;
|
|
2
|
+
|
|
3
|
+
export const entityLabelMatchLimit = 6;
|
|
4
|
+
|
|
5
|
+
export type EntityHopCandidate = { aboutEntityIris?: readonly string[] | null };
|
|
6
|
+
|
|
7
|
+
export function collectAboutEntities(
|
|
8
|
+
memories: readonly EntityHopCandidate[],
|
|
9
|
+
limit: number = entityHopLimit,
|
|
10
|
+
): string[] {
|
|
11
|
+
const seen = new Set<string>();
|
|
12
|
+
|
|
13
|
+
for (const memory of memories) {
|
|
14
|
+
for (const iri of memory.aboutEntityIris ?? []) {
|
|
15
|
+
if (seen.size >= limit) return [...seen];
|
|
16
|
+
seen.add(iri);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return [...seen];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function withoutEntities(entityIris: readonly string[], exclude: readonly string[]): string[] {
|
|
24
|
+
const excluded = new Set(exclude);
|
|
25
|
+
return entityIris.filter((iri) => !excluded.has(iri));
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { clamp } from "ramda";
|
|
2
|
+
import { maxImportanceForVolatility } from "./memory-volatility";
|
|
3
|
+
|
|
4
|
+
export const defaultMemoryImportance = 3;
|
|
5
|
+
|
|
6
|
+
export function clampImportance(value: number): number {
|
|
7
|
+
return clamp(1, 5, Math.round(value));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function clampImportanceForVolatility(value: number, volatility: unknown): number {
|
|
11
|
+
return Math.min(clampImportance(value), maxImportanceForVolatility(volatility));
|
|
12
|
+
}
|