@semiont/backend 0.4.12 → 0.4.14

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: {
@@ -13227,6 +13302,10 @@ var openapi_default = {
13227
13302
  },
13228
13303
  authenticatedAs: {
13229
13304
  type: "string"
13305
+ },
13306
+ projectName: {
13307
+ type: "string",
13308
+ description: "Name of the knowledge base project"
13230
13309
  }
13231
13310
  },
13232
13311
  required: [
@@ -13750,6 +13829,22 @@ var openapi_default = {
13750
13829
  description: "LLM-generated summary of the annotation's relationships in the knowledge graph"
13751
13830
  }
13752
13831
  }
13832
+ },
13833
+ semanticContext: {
13834
+ type: "object",
13835
+ description: "Semantically similar passages from across the knowledge base, found via vector search",
13836
+ properties: {
13837
+ similar: {
13838
+ type: "array",
13839
+ items: {
13840
+ $ref: "#/components/schemas/SemanticMatch"
13841
+ },
13842
+ description: "Passages ranked by cosine similarity to the focal text"
13843
+ }
13844
+ },
13845
+ required: [
13846
+ "similar"
13847
+ ]
13753
13848
  }
13754
13849
  },
13755
13850
  required: [
@@ -14003,10 +14098,10 @@ var OAuthService = class {
14003
14098
  }
14004
14099
  return user;
14005
14100
  }
14006
- static async acceptTerms(userId14) {
14101
+ static async acceptTerms(userId15) {
14007
14102
  const prisma2 = DatabaseConnection.getClient();
14008
14103
  const user = await prisma2.user.update({
14009
- where: { id: userId14 },
14104
+ where: { id: userId15 },
14010
14105
  data: {
14011
14106
  termsAcceptedAt: /* @__PURE__ */ new Date()
14012
14107
  }
@@ -14023,10 +14118,10 @@ var authMiddleware = async (c, next) => {
14023
14118
  if (c.req.method === "GET") {
14024
14119
  const mediaTokenParam = c.req.query("token");
14025
14120
  const match = c.req.path.match(MEDIA_TOKEN_PATH);
14026
- const resourceId21 = match?.[1];
14027
- if (mediaTokenParam && resourceId21) {
14121
+ const resourceId22 = match?.[1];
14122
+ if (mediaTokenParam && resourceId22) {
14028
14123
  try {
14029
- JWTService.verifyMediaToken(mediaTokenParam, resourceId21);
14124
+ JWTService.verifyMediaToken(mediaTokenParam, resourceId22);
14030
14125
  c.set("token", mediaTokenParam);
14031
14126
  await next();
14032
14127
  return;
@@ -14533,6 +14628,8 @@ var statusRouter = new Hono();
14533
14628
  statusRouter.use("/api/status", authMiddleware);
14534
14629
  statusRouter.get("/api/status", async (c) => {
14535
14630
  const user = c.get("user");
14631
+ const config2 = c.get("config");
14632
+ const projectName = config2._metadata?.projectName;
14536
14633
  const response = {
14537
14634
  status: "operational",
14538
14635
  version: "0.1.0",
@@ -14542,7 +14639,8 @@ statusRouter.get("/api/status", async (c) => {
14542
14639
  rbac: "planned"
14543
14640
  },
14544
14641
  message: "Ready to build the future of knowledge management!",
14545
- authenticatedAs: user?.email
14642
+ authenticatedAs: user?.email,
14643
+ projectName
14546
14644
  };
14547
14645
  return c.json(response, 200);
14548
14646
  });
@@ -14947,7 +15045,7 @@ function registerCreateResource(router) {
14947
15045
  const arrayBuffer = await file.arrayBuffer();
14948
15046
  const contentBuffer = Buffer.from(arrayBuffer);
14949
15047
  const eventBus2 = c.get("eventBus");
14950
- const resourceId21 = await ResourceOperations.createResource(
15048
+ const resourceId22 = await ResourceOperations.createResource(
14951
15049
  {
14952
15050
  name,
14953
15051
  content: contentBuffer,
@@ -14960,7 +15058,7 @@ function registerCreateResource(router) {
14960
15058
  userId(userToDid(user)),
14961
15059
  eventBus2
14962
15060
  );
14963
- return c.json({ resourceId: resourceId21 }, 202);
15061
+ return c.json({ resourceId: resourceId22 }, 202);
14964
15062
  });
14965
15063
  }
14966
15064
  var SSE_STREAM_CONNECTED = "stream-connected";
@@ -16724,7 +16822,7 @@ function registerGetEvents(router) {
16724
16822
  const queryParams = c.req.query();
16725
16823
  const eventBus2 = c.get("eventBus");
16726
16824
  const type = queryParams.type;
16727
- const userId14 = queryParams.userId;
16825
+ const userId15 = queryParams.userId;
16728
16826
  const limit = queryParams.limit ? Number(queryParams.limit) : 100;
16729
16827
  if (type && !isValidEventType(type)) {
16730
16828
  throw new HTTPException(400, { message: `Invalid event type. Must be one of: ${eventTypes.join(", ")}` });
@@ -16737,7 +16835,7 @@ function registerGetEvents(router) {
16737
16835
  const response = await eventBusRequest(
16738
16836
  eventBus2,
16739
16837
  "browse:events-requested",
16740
- { correlationId, resourceId: resourceId(id), type, userId: userId14, limit },
16838
+ { correlationId, resourceId: resourceId(id), type, userId: userId15, limit },
16741
16839
  "browse:events-result",
16742
16840
  "browse:events-failed"
16743
16841
  );
@@ -16961,6 +17059,102 @@ function registerUpdateAnnotationBody(router) {
16961
17059
  );
16962
17060
  }
16963
17061
  init_logger();
17062
+ var BIND_TIMEOUT_MS = 1e4;
17063
+ function registerBindAnnotationStream(router) {
17064
+ router.post(
17065
+ "/resources/:resourceId/annotations/:annotationId/bind-stream",
17066
+ validateRequestBody("BindAnnotationStreamRequest"),
17067
+ async (c) => {
17068
+ const { resourceId: resourceIdParam, annotationId: annotationIdParam } = c.req.param();
17069
+ const request = c.get("validatedBody");
17070
+ const user = c.get("user");
17071
+ if (!user) {
17072
+ throw new HTTPException(401, { message: "Authentication required" });
17073
+ }
17074
+ const eventBus2 = c.get("eventBus");
17075
+ const { knowledgeSystem: { kb: { eventStore } } } = c.get("makeMeaning");
17076
+ const logger2 = getLogger().child({
17077
+ component: "bind-annotation-stream",
17078
+ resourceId: resourceIdParam,
17079
+ annotationId: annotationIdParam
17080
+ });
17081
+ logger2.info("Starting bind stream", { operationCount: request.operations.length });
17082
+ const rId = resourceId(resourceIdParam);
17083
+ const aid = annotationId(annotationIdParam);
17084
+ return streamSSE(c, async (stream) => {
17085
+ let isStreamClosed = false;
17086
+ let closeStreamCallback = null;
17087
+ let timeoutHandle = null;
17088
+ let subscription = null;
17089
+ const streamPromise = new Promise((resolve) => {
17090
+ closeStreamCallback = resolve;
17091
+ });
17092
+ const cleanup = () => {
17093
+ if (isStreamClosed) return;
17094
+ isStreamClosed = true;
17095
+ if (timeoutHandle) clearTimeout(timeoutHandle);
17096
+ if (subscription) subscription.unsubscribe();
17097
+ if (closeStreamCallback) closeStreamCallback();
17098
+ };
17099
+ try {
17100
+ subscription = eventStore.bus.subscriptions.subscribe(rId, async (storedEvent) => {
17101
+ if (isStreamClosed) return;
17102
+ if (storedEvent.event.type !== "annotation.body.updated") return;
17103
+ if (storedEvent.event.payload?.annotationId !== annotationIdParam) return;
17104
+ logger2.info("Bind completed", { annotationId: annotationIdParam });
17105
+ try {
17106
+ await stream.writeSSE({
17107
+ data: JSON.stringify({ annotationId: String(aid) }),
17108
+ event: "bind:finished",
17109
+ id: String(Date.now())
17110
+ });
17111
+ } catch {
17112
+ logger2.warn("Client disconnected before bind:finished");
17113
+ }
17114
+ cleanup();
17115
+ });
17116
+ timeoutHandle = setTimeout(async () => {
17117
+ if (isStreamClosed) return;
17118
+ logger2.warn("Bind timed out", { timeoutMs: BIND_TIMEOUT_MS });
17119
+ try {
17120
+ await stream.writeSSE({
17121
+ data: JSON.stringify({ error: "Bind operation timed out" }),
17122
+ event: "bind:failed",
17123
+ id: String(Date.now())
17124
+ });
17125
+ } catch {
17126
+ }
17127
+ cleanup();
17128
+ }, BIND_TIMEOUT_MS);
17129
+ c.req.raw.signal.addEventListener("abort", () => {
17130
+ logger2.info("Client disconnected from bind stream");
17131
+ cleanup();
17132
+ });
17133
+ eventBus2.get("mark:update-body").next({
17134
+ annotationId: aid,
17135
+ resourceId: rId,
17136
+ userId: userId(userToDid(user)),
17137
+ operations: request.operations
17138
+ });
17139
+ logger2.info("Emitted mark:update-body");
17140
+ } catch (error) {
17141
+ logger2.error("Bind stream error", { error: error instanceof Error ? error.message : String(error) });
17142
+ try {
17143
+ await stream.writeSSE({
17144
+ data: JSON.stringify({ error: error instanceof Error ? error.message : "Bind failed" }),
17145
+ event: "bind:failed",
17146
+ id: String(Date.now())
17147
+ });
17148
+ } catch {
17149
+ }
17150
+ cleanup();
17151
+ }
17152
+ return streamPromise;
17153
+ });
17154
+ }
17155
+ );
17156
+ }
17157
+ init_logger();
16964
17158
  function registerYieldResourceStream(router, jobQueue) {
16965
17159
  router.post(
16966
17160
  "/resources/:resourceId/annotations/:annotationId/yield-resource-stream",
@@ -17276,14 +17470,14 @@ function registerGatherAnnotationStream(router) {
17276
17470
  }
17277
17471
  function registerGetAnnotationHistory(router) {
17278
17472
  router.get("/resources/:resourceId/annotations/:annotationId/history", async (c) => {
17279
- const { resourceId: resourceId21, annotationId: annotationId7 } = c.req.param();
17473
+ const { resourceId: resourceId22, annotationId: annotationId8 } = c.req.param();
17280
17474
  const eventBus2 = c.get("eventBus");
17281
17475
  const correlationId = crypto.randomUUID();
17282
17476
  try {
17283
17477
  const response = await eventBusRequest(
17284
17478
  eventBus2,
17285
17479
  "browse:annotation-history-requested",
17286
- { correlationId, resourceId: resourceId(resourceId21), annotationId: annotationId(annotationId7) },
17480
+ { correlationId, resourceId: resourceId(resourceId22), annotationId: annotationId(annotationId8) },
17287
17481
  "browse:annotation-history-result",
17288
17482
  "browse:annotation-history-failed"
17289
17483
  );
@@ -17322,6 +17516,7 @@ function createResourcesRouter(jobQueue) {
17322
17516
  registerCreateAnnotation(resourcesRouter2);
17323
17517
  registerGetAnnotation(resourcesRouter2);
17324
17518
  registerUpdateAnnotationBody(resourcesRouter2);
17519
+ registerBindAnnotationStream(resourcesRouter2);
17325
17520
  registerYieldResourceStream(resourcesRouter2, jobQueue);
17326
17521
  registerGatherAnnotationStream(resourcesRouter2);
17327
17522
  registerGetAnnotationHistory(resourcesRouter2);
@@ -17372,8 +17567,8 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17372
17567
  const { id } = c.req.param();
17373
17568
  const query = c.req.query();
17374
17569
  const { knowledgeSystem: { kb } } = c.get("makeMeaning");
17375
- const resourceId21 = query.resourceId;
17376
- if (!resourceId21) {
17570
+ const resourceId22 = query.resourceId;
17571
+ if (!resourceId22) {
17377
17572
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17378
17573
  }
17379
17574
  const contextBefore = query.contextBefore ? Number(query.contextBefore) : 100;
@@ -17387,7 +17582,7 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17387
17582
  try {
17388
17583
  const response = await AnnotationContext.getAnnotationContext(
17389
17584
  annotationId(id),
17390
- resourceId(resourceId21),
17585
+ resourceId(resourceId22),
17391
17586
  contextBefore,
17392
17587
  contextAfter,
17393
17588
  kb
@@ -17413,14 +17608,14 @@ operationsRouter.get("/api/annotations/:id/summary", async (c) => {
17413
17608
  const { id } = c.req.param();
17414
17609
  const query = c.req.query();
17415
17610
  const { knowledgeSystem: { gatherer } } = c.get("makeMeaning");
17416
- const resourceId21 = query.resourceId;
17417
- if (!resourceId21) {
17611
+ const resourceId22 = query.resourceId;
17612
+ if (!resourceId22) {
17418
17613
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17419
17614
  }
17420
17615
  try {
17421
17616
  const response = await gatherer.generateAnnotationSummary(
17422
17617
  annotationId(id),
17423
- resourceId(resourceId21)
17618
+ resourceId(resourceId22)
17424
17619
  );
17425
17620
  return c.json(response);
17426
17621
  } catch (error) {