@semiont/backend 0.4.11 → 0.4.13

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/dist/index.js CHANGED
@@ -268,11 +268,11 @@ var init_jwt = __esm({
268
268
  throw error;
269
269
  }
270
270
  }
271
- static generateMediaToken(resourceId21, userId14) {
272
- const payload = { purpose: "media", sub: resourceId21, userId: userId14 };
271
+ static generateMediaToken(resourceId22, userId15) {
272
+ const payload = { purpose: "media", sub: resourceId22, userId: userId15 };
273
273
  return jwt.sign(payload, this.getSecret(), { expiresIn: "5m" });
274
274
  }
275
- static verifyMediaToken(token, resourceId21) {
275
+ static verifyMediaToken(token, resourceId22) {
276
276
  let decoded;
277
277
  try {
278
278
  decoded = jwt.verify(token, this.getSecret());
@@ -281,7 +281,7 @@ var init_jwt = __esm({
281
281
  throw new Error("Invalid media token");
282
282
  }
283
283
  if (decoded["purpose"] !== "media") throw new Error("Invalid media token");
284
- if (decoded["sub"] !== resourceId21) throw new Error("Media token resource mismatch");
284
+ if (decoded["sub"] !== resourceId22) throw new Error("Media token resource mismatch");
285
285
  }
286
286
  static isAllowedDomain(email) {
287
287
  const parts = email.split("@");
@@ -9982,7 +9982,9 @@ function makeMeaningConfigFrom(config2) {
9982
9982
  const meta = config2._metadata;
9983
9983
  return {
9984
9984
  services: {
9985
- graph: config2.services?.graph
9985
+ graph: config2.services?.graph,
9986
+ vectors: config2.services?.vectors,
9987
+ embedding: config2.services?.embedding
9986
9988
  },
9987
9989
  actors: meta?.actors,
9988
9990
  workers: meta?.workers
@@ -10003,15 +10005,17 @@ rootRouter.get("/", (c) => {
10003
10005
  <meta charset="UTF-8">
10004
10006
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
10005
10007
  <title>Semiont</title>
10006
- <link rel="preconnect" href="https://fonts.googleapis.com">
10007
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10008
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500&family=Orbitron:wght@700&display=swap" rel="stylesheet">
10009
10008
  <style>
10010
10009
  * { margin: 0; padding: 0; box-sizing: border-box; }
10010
+ @keyframes fadeIn {
10011
+ from { opacity: 0; transform: translateY(0.5rem); }
10012
+ to { opacity: 1; transform: translateY(0); }
10013
+ }
10011
10014
  body {
10015
+ animation: fadeIn 0.4s ease-out;
10012
10016
  background: #ffffff;
10013
10017
  color: #111827;
10014
- font-family: 'Inter', -apple-system, sans-serif;
10018
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10015
10019
  min-height: 100vh;
10016
10020
  display: flex;
10017
10021
  flex-direction: column;
@@ -10020,22 +10024,33 @@ rootRouter.get("/", (c) => {
10020
10024
  gap: 0.75rem;
10021
10025
  }
10022
10026
  h1 {
10023
- font-family: 'Orbitron', sans-serif;
10024
- font-size: 3rem;
10025
- font-weight: 700;
10026
- letter-spacing: 0.15em;
10027
- color: #0066cc;
10027
+ font-size: 2.5rem;
10028
+ font-weight: 800;
10029
+ letter-spacing: 0.2em;
10030
+ color: #0f172a;
10028
10031
  }
10029
10032
  h2 {
10030
- font-family: 'Inter', sans-serif;
10031
10033
  font-size: 0.9rem;
10032
10034
  font-weight: 500;
10033
10035
  letter-spacing: 0.3em;
10034
10036
  color: #6b7280;
10035
10037
  text-transform: uppercase;
10036
10038
  }
10039
+ .tagline {
10040
+ font-size: 0.85rem;
10041
+ font-weight: 400;
10042
+ letter-spacing: 0.2em;
10043
+ color: #9ca3af;
10044
+ text-transform: lowercase;
10045
+ font-style: italic;
10046
+ }
10047
+ hr {
10048
+ width: 4rem;
10049
+ border: none;
10050
+ border-top: 2px solid #e5e7eb;
10051
+ margin: 1rem 0;
10052
+ }
10037
10053
  .meta {
10038
- margin-top: 1.5rem;
10039
10054
  font-size: 0.8rem;
10040
10055
  color: #9ca3af;
10041
10056
  display: flex;
@@ -10043,22 +10058,49 @@ rootRouter.get("/", (c) => {
10043
10058
  align-items: center;
10044
10059
  gap: 0.25rem;
10045
10060
  }
10046
- a {
10061
+ nav {
10062
+ display: flex;
10063
+ gap: 1.5rem;
10064
+ margin-top: 0.5rem;
10065
+ }
10066
+ nav a {
10047
10067
  color: #0066cc;
10048
10068
  text-decoration: none;
10049
- font-size: 0.8rem;
10069
+ font-size: 0.85rem;
10070
+ padding: 0.4rem 0.75rem;
10071
+ border: 1px solid #e5e7eb;
10072
+ border-radius: 0.375rem;
10073
+ transition: background 0.15s, border-color 0.15s;
10074
+ }
10075
+ nav a:hover {
10076
+ background: #f3f4f6;
10077
+ border-color: #0066cc;
10078
+ }
10079
+ @media (prefers-color-scheme: dark) {
10080
+ body { background: #111827; color: #f3f4f6; }
10081
+ h1 { color: #e2e8f0; }
10082
+ h2 { color: #9ca3af; }
10083
+ .tagline { color: #6b7280; }
10084
+ hr { border-top-color: #374151; }
10085
+ .meta { color: #6b7280; }
10086
+ nav a { color: #60a5fa; border-color: #374151; }
10087
+ nav a:hover { background: #1f2937; border-color: #60a5fa; }
10050
10088
  }
10051
- a:hover { text-decoration: underline; }
10052
10089
  </style>
10053
10090
  </head>
10054
10091
  <body>
10055
10092
  <h1>SEMIONT</h1>
10056
10093
  <h2>knowledge base</h2>
10094
+ <p class="tagline">make meaning</p>
10095
+ <hr>
10057
10096
  <div class="meta">
10058
10097
  ${siteName ? `<span>${siteName}</span>` : ""}
10059
10098
  ${projectName ? `<span>${projectName}${projectVersion ? " v" + projectVersion : ""}</span>` : ""}
10060
- <a href="/api/health">/api/health</a>
10061
10099
  </div>
10100
+ <nav>
10101
+ <a href="/api/docs">API Docs</a>
10102
+ <a href="/api/health">Health</a>
10103
+ </nav>
10062
10104
  </body>
10063
10105
  </html>`;
10064
10106
  return c.html(html);
@@ -13062,6 +13104,39 @@ var openapi_default = {
13062
13104
  }
13063
13105
  }
13064
13106
  },
13107
+ SemanticMatch: {
13108
+ type: "object",
13109
+ properties: {
13110
+ text: {
13111
+ type: "string",
13112
+ description: "The chunk text that matched"
13113
+ },
13114
+ resourceId: {
13115
+ type: "string",
13116
+ description: "Source resource ID"
13117
+ },
13118
+ annotationId: {
13119
+ type: "string",
13120
+ description: "Source annotation ID, if the match is from an annotation"
13121
+ },
13122
+ score: {
13123
+ type: "number",
13124
+ description: "Cosine similarity score (0-1)"
13125
+ },
13126
+ entityTypes: {
13127
+ type: "array",
13128
+ items: {
13129
+ type: "string"
13130
+ },
13131
+ description: "Entity types on the matched passage"
13132
+ }
13133
+ },
13134
+ required: [
13135
+ "text",
13136
+ "resourceId",
13137
+ "score"
13138
+ ]
13139
+ },
13065
13140
  ResourceLLMContextResponse: {
13066
13141
  type: "object",
13067
13142
  properties: {
@@ -13750,6 +13825,22 @@ var openapi_default = {
13750
13825
  description: "LLM-generated summary of the annotation's relationships in the knowledge graph"
13751
13826
  }
13752
13827
  }
13828
+ },
13829
+ semanticContext: {
13830
+ type: "object",
13831
+ description: "Semantically similar passages from across the knowledge base, found via vector search",
13832
+ properties: {
13833
+ similar: {
13834
+ type: "array",
13835
+ items: {
13836
+ $ref: "#/components/schemas/SemanticMatch"
13837
+ },
13838
+ description: "Passages ranked by cosine similarity to the focal text"
13839
+ }
13840
+ },
13841
+ required: [
13842
+ "similar"
13843
+ ]
13753
13844
  }
13754
13845
  },
13755
13846
  required: [
@@ -14003,10 +14094,10 @@ var OAuthService = class {
14003
14094
  }
14004
14095
  return user;
14005
14096
  }
14006
- static async acceptTerms(userId14) {
14097
+ static async acceptTerms(userId15) {
14007
14098
  const prisma2 = DatabaseConnection.getClient();
14008
14099
  const user = await prisma2.user.update({
14009
- where: { id: userId14 },
14100
+ where: { id: userId15 },
14010
14101
  data: {
14011
14102
  termsAcceptedAt: /* @__PURE__ */ new Date()
14012
14103
  }
@@ -14023,10 +14114,10 @@ var authMiddleware = async (c, next) => {
14023
14114
  if (c.req.method === "GET") {
14024
14115
  const mediaTokenParam = c.req.query("token");
14025
14116
  const match = c.req.path.match(MEDIA_TOKEN_PATH);
14026
- const resourceId21 = match?.[1];
14027
- if (mediaTokenParam && resourceId21) {
14117
+ const resourceId22 = match?.[1];
14118
+ if (mediaTokenParam && resourceId22) {
14028
14119
  try {
14029
- JWTService.verifyMediaToken(mediaTokenParam, resourceId21);
14120
+ JWTService.verifyMediaToken(mediaTokenParam, resourceId22);
14030
14121
  c.set("token", mediaTokenParam);
14031
14122
  await next();
14032
14123
  return;
@@ -14947,7 +15038,7 @@ function registerCreateResource(router) {
14947
15038
  const arrayBuffer = await file.arrayBuffer();
14948
15039
  const contentBuffer = Buffer.from(arrayBuffer);
14949
15040
  const eventBus2 = c.get("eventBus");
14950
- const resourceId21 = await ResourceOperations.createResource(
15041
+ const resourceId22 = await ResourceOperations.createResource(
14951
15042
  {
14952
15043
  name,
14953
15044
  content: contentBuffer,
@@ -14960,7 +15051,7 @@ function registerCreateResource(router) {
14960
15051
  userId(userToDid(user)),
14961
15052
  eventBus2
14962
15053
  );
14963
- return c.json({ resourceId: resourceId21 }, 202);
15054
+ return c.json({ resourceId: resourceId22 }, 202);
14964
15055
  });
14965
15056
  }
14966
15057
  var SSE_STREAM_CONNECTED = "stream-connected";
@@ -16724,7 +16815,7 @@ function registerGetEvents(router) {
16724
16815
  const queryParams = c.req.query();
16725
16816
  const eventBus2 = c.get("eventBus");
16726
16817
  const type = queryParams.type;
16727
- const userId14 = queryParams.userId;
16818
+ const userId15 = queryParams.userId;
16728
16819
  const limit = queryParams.limit ? Number(queryParams.limit) : 100;
16729
16820
  if (type && !isValidEventType(type)) {
16730
16821
  throw new HTTPException(400, { message: `Invalid event type. Must be one of: ${eventTypes.join(", ")}` });
@@ -16737,7 +16828,7 @@ function registerGetEvents(router) {
16737
16828
  const response = await eventBusRequest(
16738
16829
  eventBus2,
16739
16830
  "browse:events-requested",
16740
- { correlationId, resourceId: resourceId(id), type, userId: userId14, limit },
16831
+ { correlationId, resourceId: resourceId(id), type, userId: userId15, limit },
16741
16832
  "browse:events-result",
16742
16833
  "browse:events-failed"
16743
16834
  );
@@ -16961,6 +17052,102 @@ function registerUpdateAnnotationBody(router) {
16961
17052
  );
16962
17053
  }
16963
17054
  init_logger();
17055
+ var BIND_TIMEOUT_MS = 1e4;
17056
+ function registerBindAnnotationStream(router) {
17057
+ router.post(
17058
+ "/resources/:resourceId/annotations/:annotationId/bind-stream",
17059
+ validateRequestBody("BindAnnotationStreamRequest"),
17060
+ async (c) => {
17061
+ const { resourceId: resourceIdParam, annotationId: annotationIdParam } = c.req.param();
17062
+ const request = c.get("validatedBody");
17063
+ const user = c.get("user");
17064
+ if (!user) {
17065
+ throw new HTTPException(401, { message: "Authentication required" });
17066
+ }
17067
+ const eventBus2 = c.get("eventBus");
17068
+ const { knowledgeSystem: { kb: { eventStore } } } = c.get("makeMeaning");
17069
+ const logger2 = getLogger().child({
17070
+ component: "bind-annotation-stream",
17071
+ resourceId: resourceIdParam,
17072
+ annotationId: annotationIdParam
17073
+ });
17074
+ logger2.info("Starting bind stream", { operationCount: request.operations.length });
17075
+ const rId = resourceId(resourceIdParam);
17076
+ const aid = annotationId(annotationIdParam);
17077
+ return streamSSE(c, async (stream) => {
17078
+ let isStreamClosed = false;
17079
+ let closeStreamCallback = null;
17080
+ let timeoutHandle = null;
17081
+ let subscription = null;
17082
+ const streamPromise = new Promise((resolve) => {
17083
+ closeStreamCallback = resolve;
17084
+ });
17085
+ const cleanup = () => {
17086
+ if (isStreamClosed) return;
17087
+ isStreamClosed = true;
17088
+ if (timeoutHandle) clearTimeout(timeoutHandle);
17089
+ if (subscription) subscription.unsubscribe();
17090
+ if (closeStreamCallback) closeStreamCallback();
17091
+ };
17092
+ try {
17093
+ subscription = eventStore.bus.subscriptions.subscribe(rId, async (storedEvent) => {
17094
+ if (isStreamClosed) return;
17095
+ if (storedEvent.event.type !== "annotation.body.updated") return;
17096
+ if (storedEvent.event.payload?.annotationId !== annotationIdParam) return;
17097
+ logger2.info("Bind completed", { annotationId: annotationIdParam });
17098
+ try {
17099
+ await stream.writeSSE({
17100
+ data: JSON.stringify({ annotationId: String(aid) }),
17101
+ event: "bind:finished",
17102
+ id: String(Date.now())
17103
+ });
17104
+ } catch {
17105
+ logger2.warn("Client disconnected before bind:finished");
17106
+ }
17107
+ cleanup();
17108
+ });
17109
+ timeoutHandle = setTimeout(async () => {
17110
+ if (isStreamClosed) return;
17111
+ logger2.warn("Bind timed out", { timeoutMs: BIND_TIMEOUT_MS });
17112
+ try {
17113
+ await stream.writeSSE({
17114
+ data: JSON.stringify({ error: "Bind operation timed out" }),
17115
+ event: "bind:failed",
17116
+ id: String(Date.now())
17117
+ });
17118
+ } catch {
17119
+ }
17120
+ cleanup();
17121
+ }, BIND_TIMEOUT_MS);
17122
+ c.req.raw.signal.addEventListener("abort", () => {
17123
+ logger2.info("Client disconnected from bind stream");
17124
+ cleanup();
17125
+ });
17126
+ eventBus2.get("mark:update-body").next({
17127
+ annotationId: aid,
17128
+ resourceId: rId,
17129
+ userId: userId(userToDid(user)),
17130
+ operations: request.operations
17131
+ });
17132
+ logger2.info("Emitted mark:update-body");
17133
+ } catch (error) {
17134
+ logger2.error("Bind stream error", { error: error instanceof Error ? error.message : String(error) });
17135
+ try {
17136
+ await stream.writeSSE({
17137
+ data: JSON.stringify({ error: error instanceof Error ? error.message : "Bind failed" }),
17138
+ event: "bind:failed",
17139
+ id: String(Date.now())
17140
+ });
17141
+ } catch {
17142
+ }
17143
+ cleanup();
17144
+ }
17145
+ return streamPromise;
17146
+ });
17147
+ }
17148
+ );
17149
+ }
17150
+ init_logger();
16964
17151
  function registerYieldResourceStream(router, jobQueue) {
16965
17152
  router.post(
16966
17153
  "/resources/:resourceId/annotations/:annotationId/yield-resource-stream",
@@ -17276,14 +17463,14 @@ function registerGatherAnnotationStream(router) {
17276
17463
  }
17277
17464
  function registerGetAnnotationHistory(router) {
17278
17465
  router.get("/resources/:resourceId/annotations/:annotationId/history", async (c) => {
17279
- const { resourceId: resourceId21, annotationId: annotationId7 } = c.req.param();
17466
+ const { resourceId: resourceId22, annotationId: annotationId8 } = c.req.param();
17280
17467
  const eventBus2 = c.get("eventBus");
17281
17468
  const correlationId = crypto.randomUUID();
17282
17469
  try {
17283
17470
  const response = await eventBusRequest(
17284
17471
  eventBus2,
17285
17472
  "browse:annotation-history-requested",
17286
- { correlationId, resourceId: resourceId(resourceId21), annotationId: annotationId(annotationId7) },
17473
+ { correlationId, resourceId: resourceId(resourceId22), annotationId: annotationId(annotationId8) },
17287
17474
  "browse:annotation-history-result",
17288
17475
  "browse:annotation-history-failed"
17289
17476
  );
@@ -17322,6 +17509,7 @@ function createResourcesRouter(jobQueue) {
17322
17509
  registerCreateAnnotation(resourcesRouter2);
17323
17510
  registerGetAnnotation(resourcesRouter2);
17324
17511
  registerUpdateAnnotationBody(resourcesRouter2);
17512
+ registerBindAnnotationStream(resourcesRouter2);
17325
17513
  registerYieldResourceStream(resourcesRouter2, jobQueue);
17326
17514
  registerGatherAnnotationStream(resourcesRouter2);
17327
17515
  registerGetAnnotationHistory(resourcesRouter2);
@@ -17372,8 +17560,8 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17372
17560
  const { id } = c.req.param();
17373
17561
  const query = c.req.query();
17374
17562
  const { knowledgeSystem: { kb } } = c.get("makeMeaning");
17375
- const resourceId21 = query.resourceId;
17376
- if (!resourceId21) {
17563
+ const resourceId22 = query.resourceId;
17564
+ if (!resourceId22) {
17377
17565
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17378
17566
  }
17379
17567
  const contextBefore = query.contextBefore ? Number(query.contextBefore) : 100;
@@ -17387,7 +17575,7 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17387
17575
  try {
17388
17576
  const response = await AnnotationContext.getAnnotationContext(
17389
17577
  annotationId(id),
17390
- resourceId(resourceId21),
17578
+ resourceId(resourceId22),
17391
17579
  contextBefore,
17392
17580
  contextAfter,
17393
17581
  kb
@@ -17413,14 +17601,14 @@ operationsRouter.get("/api/annotations/:id/summary", async (c) => {
17413
17601
  const { id } = c.req.param();
17414
17602
  const query = c.req.query();
17415
17603
  const { knowledgeSystem: { gatherer } } = c.get("makeMeaning");
17416
- const resourceId21 = query.resourceId;
17417
- if (!resourceId21) {
17604
+ const resourceId22 = query.resourceId;
17605
+ if (!resourceId22) {
17418
17606
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17419
17607
  }
17420
17608
  try {
17421
17609
  const response = await gatherer.generateAnnotationSummary(
17422
17610
  annotationId(id),
17423
- resourceId(resourceId21)
17611
+ resourceId(resourceId22)
17424
17612
  );
17425
17613
  return c.json(response);
17426
17614
  } catch (error) {