@semiont/backend 0.2.46 → 0.3.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/dist/index.js CHANGED
@@ -2,12 +2,12 @@
2
2
  import winston from 'winston';
3
3
  import { z } from 'zod';
4
4
  import jwt from 'jsonwebtoken';
5
- import { createConfigLoader, email, userId, accessToken, googleCredential, annotationId, resourceId, EventBus, uriToResourceId, jobId, resourceUri, entityType, assembleAnnotation, userToAgent } from '@semiont/core';
5
+ import { createConfigLoader, email, userId, accessToken, googleCredential, annotationId, resourceId, EventBus, jobId, entityType, assembleAnnotation, userToAgent } from '@semiont/core';
6
6
  import { cors } from 'hono/cors';
7
7
  import { serve } from '@hono/node-server';
8
8
  import { Hono } from 'hono';
9
9
  import { swaggerUI } from '@hono/swagger-ui';
10
- import { AnnotationContext, startMakeMeaning, ResourceContext, ResourceOperations } from '@semiont/make-meaning';
10
+ import { exportBackup, importBackup, readEntityTypesProjection, exportLinkedData, importLinkedData, AnnotationContext, startMakeMeaning, ResourceContext, ResourceOperations } from '@semiont/make-meaning';
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import { PrismaClient } from '@prisma/client';
@@ -15,6 +15,7 @@ import { HTTPException } from 'hono/http-exception';
15
15
  import Ajv from 'ajv';
16
16
  import addFormats from 'ajv-formats';
17
17
  import * as argon2 from 'argon2';
18
+ import { Writable, Readable } from 'stream';
18
19
  import { streamSSE } from 'hono/streaming';
19
20
  import crypto2, { randomUUID } from 'crypto';
20
21
  import { EventQuery } from '@semiont/event-sourcing';
@@ -66,8 +67,8 @@ __export(logger_exports, {
66
67
  function getLoggerConfig(logLevel) {
67
68
  const level = logLevel || process.env.LOG_LEVEL || "info";
68
69
  const format = process.env.LOG_FORMAT || "json";
69
- const nodeEnv2 = process.env.NODE_ENV || "development";
70
- if (nodeEnv2 === "test") {
70
+ const nodeEnv = process.env.NODE_ENV || "development";
71
+ if (nodeEnv === "test") {
71
72
  return {
72
73
  level: "error",
73
74
  format: "simple",
@@ -12073,8 +12074,8 @@ var getRouteLogger = /* @__PURE__ */ __name(() => getLogger().child({
12073
12074
  }), "getRouteLogger");
12074
12075
  var healthRouter = new Hono();
12075
12076
  healthRouter.get("/api/health", async (c) => {
12076
- const nodeEnv2 = process.env.NODE_ENV;
12077
- if (!nodeEnv2) {
12077
+ const nodeEnv = process.env.NODE_ENV;
12078
+ if (!nodeEnv) {
12078
12079
  throw new Error("NODE_ENV environment variable is required");
12079
12080
  }
12080
12081
  let startupFailed = false;
@@ -12098,7 +12099,7 @@ healthRouter.get("/api/health", async (c) => {
12098
12099
  version: "0.1.0",
12099
12100
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12100
12101
  database: "unknown",
12101
- environment: nodeEnv2
12102
+ environment: nodeEnv
12102
12103
  };
12103
12104
  return c.json(response2, 200);
12104
12105
  }
@@ -12109,7 +12110,7 @@ healthRouter.get("/api/health", async (c) => {
12109
12110
  version: "0.1.0",
12110
12111
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12111
12112
  database: dbStatus ? "connected" : "disconnected",
12112
- environment: nodeEnv2
12113
+ environment: nodeEnv
12113
12114
  };
12114
12115
  return c.json(response, 200);
12115
12116
  });
@@ -12553,8 +12554,8 @@ var openapi_default = {
12553
12554
  ]
12554
12555
  },
12555
12556
  context: {
12556
- $ref: "#/components/schemas/YieldContext",
12557
- description: "Generation context for this annotation"
12557
+ $ref: "#/components/schemas/GatheredContext",
12558
+ description: "Gathered context for this annotation"
12558
12559
  },
12559
12560
  sourceContext: {
12560
12561
  type: "object",
@@ -13529,8 +13530,8 @@ var openapi_default = {
13529
13530
  description: 'Language locale for generated content (e.g., "es", "fr", "ja")'
13530
13531
  },
13531
13532
  context: {
13532
- $ref: "#/components/schemas/YieldContext",
13533
- description: "Generation context including source document excerpts and metadata"
13533
+ $ref: "#/components/schemas/GatheredContext",
13534
+ description: "Gathered context including source document excerpts, metadata, and graph context"
13534
13535
  },
13535
13536
  temperature: {
13536
13537
  type: "number",
@@ -14449,7 +14450,6 @@ var openapi_default = {
14449
14450
  },
14450
14451
  "@id": {
14451
14452
  type: "string",
14452
- format: "uri",
14453
14453
  description: "Canonical URI/URN of the resource being described."
14454
14454
  },
14455
14455
  "@type": {
@@ -14560,14 +14560,12 @@ var openapi_default = {
14560
14560
  description: "W3C PROV - source resources this was derived from",
14561
14561
  oneOf: [
14562
14562
  {
14563
- type: "string",
14564
- format: "uri"
14563
+ type: "string"
14565
14564
  },
14566
14565
  {
14567
14566
  type: "array",
14568
14567
  items: {
14569
- type: "string",
14570
- format: "uri"
14568
+ type: "string"
14571
14569
  }
14572
14570
  }
14573
14571
  ]
@@ -14657,6 +14655,11 @@ var openapi_default = {
14657
14655
  sourceResourceId: {
14658
14656
  type: "string",
14659
14657
  description: "Application-specific: ID of source resource for clones/derivatives"
14658
+ },
14659
+ originatedFrom: {
14660
+ type: "string",
14661
+ format: "uri",
14662
+ description: "Original URI from a source knowledge base when this resource was imported"
14660
14663
  }
14661
14664
  }
14662
14665
  },
@@ -15159,10 +15162,18 @@ var openapi_default = {
15159
15162
  "value"
15160
15163
  ]
15161
15164
  },
15162
- YieldContext: {
15165
+ GatheredContext: {
15163
15166
  type: "object",
15164
- description: "Context information used for AI generation. Includes source document excerpts and metadata.",
15167
+ description: "Context gathered for an annotation. Includes source document excerpts, metadata, and graph-derived context. Used by both Find (bind) and Generate (yield) flows.",
15165
15168
  properties: {
15169
+ annotation: {
15170
+ $ref: "#/components/schemas/Annotation",
15171
+ description: "The annotation this context was gathered for"
15172
+ },
15173
+ sourceResource: {
15174
+ $ref: "#/components/schemas/ResourceDescriptor",
15175
+ description: "The resource containing the annotation"
15176
+ },
15166
15177
  sourceContext: {
15167
15178
  type: "object",
15168
15179
  description: "Text context from the source document",
@@ -15186,7 +15197,7 @@ var openapi_default = {
15186
15197
  },
15187
15198
  metadata: {
15188
15199
  type: "object",
15189
- description: "Additional context metadata (reserved for future use)",
15200
+ description: "Context metadata about the annotation and its source",
15190
15201
  properties: {
15191
15202
  resourceType: {
15192
15203
  type: "string",
@@ -15204,9 +15215,91 @@ var openapi_default = {
15204
15215
  description: "Entity types associated with the annotation"
15205
15216
  }
15206
15217
  }
15218
+ },
15219
+ graphContext: {
15220
+ type: "object",
15221
+ description: "Graph-derived context from the knowledge base",
15222
+ properties: {
15223
+ connections: {
15224
+ type: "array",
15225
+ description: "Resources connected to the source resource via annotations",
15226
+ items: {
15227
+ type: "object",
15228
+ properties: {
15229
+ resourceId: {
15230
+ type: "string",
15231
+ description: "ID of the connected resource"
15232
+ },
15233
+ resourceName: {
15234
+ type: "string",
15235
+ description: "Name of the connected resource"
15236
+ },
15237
+ entityTypes: {
15238
+ type: "array",
15239
+ items: {
15240
+ type: "string"
15241
+ },
15242
+ description: "Entity types on the connected resource"
15243
+ },
15244
+ bidirectional: {
15245
+ type: "boolean",
15246
+ description: "Whether the connection goes both ways"
15247
+ }
15248
+ },
15249
+ required: [
15250
+ "resourceId",
15251
+ "resourceName",
15252
+ "bidirectional"
15253
+ ]
15254
+ }
15255
+ },
15256
+ citedByCount: {
15257
+ type: "integer",
15258
+ description: "Number of other resources that reference the source resource"
15259
+ },
15260
+ citedBy: {
15261
+ type: "array",
15262
+ description: "Resources that reference the source resource",
15263
+ items: {
15264
+ type: "object",
15265
+ properties: {
15266
+ resourceId: {
15267
+ type: "string"
15268
+ },
15269
+ resourceName: {
15270
+ type: "string"
15271
+ }
15272
+ },
15273
+ required: [
15274
+ "resourceId",
15275
+ "resourceName"
15276
+ ]
15277
+ }
15278
+ },
15279
+ siblingEntityTypes: {
15280
+ type: "array",
15281
+ items: {
15282
+ type: "string"
15283
+ },
15284
+ description: "Entity types from other annotations on the same resource"
15285
+ },
15286
+ entityTypeFrequencies: {
15287
+ type: "object",
15288
+ description: "Global frequency counts for entity types (for IDF-like weighting)",
15289
+ additionalProperties: {
15290
+ type: "integer"
15291
+ }
15292
+ },
15293
+ inferredRelationshipSummary: {
15294
+ type: "string",
15295
+ description: "LLM-generated summary of the annotation's relationships in the knowledge graph"
15296
+ }
15297
+ }
15207
15298
  }
15208
15299
  },
15209
15300
  required: [
15301
+ "annotation",
15302
+ "sourceResource",
15210
15303
  "sourceContext"
15211
15304
  ]
15212
15305
  }
@@ -16096,6 +16189,235 @@ adminRouter.get("/api/admin/oauth/config", async (c) => {
16096
16189
  };
16097
16190
  return c.json(response, 200);
16098
16191
  });
16192
+ var adminMiddleware2 = /* @__PURE__ */ __name(async (c, next) => {
16193
+ const user = c.get("user");
16194
+ if (!user || !user.isAdmin) {
16195
+ return c.json({
16196
+ error: "Forbidden: Admin access required"
16197
+ }, 403);
16198
+ }
16199
+ return next();
16200
+ }, "adminMiddleware");
16201
+ var moderatorMiddleware = /* @__PURE__ */ __name(async (c, next) => {
16202
+ const user = c.get("user");
16203
+ if (!user || !user.isModerator && !user.isAdmin) {
16204
+ return c.json({
16205
+ error: "Forbidden: Moderator or Admin access required"
16206
+ }, 403);
16207
+ }
16208
+ return next();
16209
+ }, "moderatorMiddleware");
16210
+ var exchangeRouter = new Hono();
16211
+ exchangeRouter.use("/api/admin/exchange/*", authMiddleware, adminMiddleware2);
16212
+ exchangeRouter.use("/api/moderate/exchange/*", authMiddleware, moderatorMiddleware);
16213
+ exchangeRouter.post("/api/admin/exchange/backup", async (c) => {
16214
+ const mm = c.get("makeMeaning");
16215
+ const config2 = c.get("config");
16216
+ const sourceUrl = config2.services?.backend?.publicURL ?? "http://localhost:4000";
16217
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16218
+ const filename = `semiont-backup-${timestamp}.tar.gz`;
16219
+ let controller;
16220
+ const webReadable = new ReadableStream({
16221
+ start(ctrl) {
16222
+ controller = ctrl;
16223
+ }
16224
+ });
16225
+ const nodeWritable = new Writable({
16226
+ write(chunk, _encoding, callback) {
16227
+ controller.enqueue(chunk);
16228
+ callback();
16229
+ },
16230
+ final(callback) {
16231
+ controller.close();
16232
+ callback();
16233
+ }
16234
+ });
16235
+ (async () => {
16236
+ try {
16237
+ await exportBackup({
16238
+ eventStore: mm.kb.eventStore,
16239
+ content: mm.kb.content,
16240
+ sourceUrl
16241
+ }, nodeWritable);
16242
+ } catch (err) {
16243
+ controller.error(err);
16244
+ }
16245
+ })();
16246
+ return new Response(webReadable, {
16247
+ headers: {
16248
+ "Content-Type": "application/gzip",
16249
+ "Content-Disposition": `attachment; filename="${filename}"`,
16250
+ "Cache-Control": "no-cache"
16251
+ }
16252
+ });
16253
+ });
16254
+ exchangeRouter.post("/api/admin/exchange/restore", async (c) => {
16255
+ const formData = await c.req.formData();
16256
+ const file = formData.get("file");
16257
+ if (!file) {
16258
+ return c.json({
16259
+ error: "No file provided"
16260
+ }, 400);
16261
+ }
16262
+ const eventBus2 = c.get("eventBus");
16263
+ const buffer = Buffer.from(await file.arrayBuffer());
16264
+ const encoder = new TextEncoder();
16265
+ const stream = new ReadableStream({
16266
+ async start(controller) {
16267
+ const send = /* @__PURE__ */ __name((data) => {
16268
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
16269
+
16270
+ `));
16271
+ }, "send");
16272
+ try {
16273
+ const input = new Readable({
16274
+ read() {
16275
+ }
16276
+ });
16277
+ input.push(buffer);
16278
+ input.push(null);
16279
+ send({
16280
+ phase: "started",
16281
+ message: "Restoring backup..."
16282
+ });
16283
+ const result = await importBackup(input, {
16284
+ eventBus: eventBus2
16285
+ });
16286
+ send({
16287
+ phase: "complete",
16288
+ result: {
16289
+ stats: result.stats,
16290
+ hashChainValid: result.hashChainValid
16291
+ }
16292
+ });
16293
+ } catch (err) {
16294
+ send({
16295
+ phase: "error",
16296
+ message: err instanceof Error ? err.message : String(err)
16297
+ });
16298
+ } finally {
16299
+ controller.close();
16300
+ }
16301
+ }
16302
+ });
16303
+ return new Response(stream, {
16304
+ headers: {
16305
+ "Content-Type": "text/event-stream",
16306
+ "Cache-Control": "no-cache",
16307
+ "Connection": "keep-alive"
16308
+ }
16309
+ });
16310
+ });
16311
+ exchangeRouter.post("/api/moderate/exchange/export", async (c) => {
16312
+ const mm = c.get("makeMeaning");
16313
+ const config2 = c.get("config");
16314
+ const sourceUrl = config2.services?.backend?.publicURL ?? "http://localhost:4000";
16315
+ const includeArchived = c.req.query("includeArchived") === "true";
16316
+ const entityTypes = await readEntityTypesProjection({
16317
+ services: {
16318
+ filesystem: config2.services?.filesystem
16319
+ },
16320
+ _metadata: config2._metadata
16321
+ });
16322
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16323
+ const filename = `semiont-export-${timestamp}.tar.gz`;
16324
+ let controller;
16325
+ const webReadable = new ReadableStream({
16326
+ start(ctrl) {
16327
+ controller = ctrl;
16328
+ }
16329
+ });
16330
+ const nodeWritable = new Writable({
16331
+ write(chunk, _encoding, callback) {
16332
+ controller.enqueue(chunk);
16333
+ callback();
16334
+ },
16335
+ final(callback) {
16336
+ controller.close();
16337
+ callback();
16338
+ }
16339
+ });
16340
+ (async () => {
16341
+ try {
16342
+ await exportLinkedData({
16343
+ views: mm.kb.views,
16344
+ content: mm.kb.content,
16345
+ sourceUrl,
16346
+ entityTypes,
16347
+ includeArchived
16348
+ }, nodeWritable);
16349
+ } catch (err) {
16350
+ controller.error(err);
16351
+ }
16352
+ })();
16353
+ return new Response(webReadable, {
16354
+ headers: {
16355
+ "Content-Type": "application/gzip",
16356
+ "Content-Disposition": `attachment; filename="${filename}"`,
16357
+ "Cache-Control": "no-cache"
16358
+ }
16359
+ });
16360
+ });
16361
+ exchangeRouter.post("/api/moderate/exchange/import", async (c) => {
16362
+ const formData = await c.req.formData();
16363
+ const file = formData.get("file");
16364
+ if (!file) {
16365
+ return c.json({
16366
+ error: "No file provided"
16367
+ }, 400);
16368
+ }
16369
+ const eventBus2 = c.get("eventBus");
16370
+ const user = c.get("user");
16371
+ const buffer = Buffer.from(await file.arrayBuffer());
16372
+ const encoder = new TextEncoder();
16373
+ const stream = new ReadableStream({
16374
+ async start(controller) {
16375
+ const send = /* @__PURE__ */ __name((data) => {
16376
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
16377
+
16378
+ `));
16379
+ }, "send");
16380
+ try {
16381
+ const input = new Readable({
16382
+ read() {
16383
+ }
16384
+ });
16385
+ input.push(buffer);
16386
+ input.push(null);
16387
+ send({
16388
+ phase: "started",
16389
+ message: "Importing linked data..."
16390
+ });
16391
+ const result = await importLinkedData(input, {
16392
+ eventBus: eventBus2,
16393
+ userId: userId(user.id)
16394
+ });
16395
+ send({
16396
+ phase: "complete",
16397
+ result: {
16398
+ resourcesCreated: result.resourcesCreated,
16399
+ annotationsCreated: result.annotationsCreated,
16400
+ entityTypesAdded: result.entityTypesAdded
16401
+ }
16402
+ });
16403
+ } catch (err) {
16404
+ send({
16405
+ phase: "error",
16406
+ message: err instanceof Error ? err.message : String(err)
16407
+ });
16408
+ } finally {
16409
+ controller.close();
16410
+ }
16411
+ }
16412
+ });
16413
+ return new Response(stream, {
16414
+ headers: {
16415
+ "Content-Type": "text/event-stream",
16416
+ "Cache-Control": "no-cache",
16417
+ "Connection": "keep-alive"
16418
+ }
16419
+ });
16420
+ });
16099
16421
  function createResourceRouter() {
16100
16422
  const router = new Hono();
16101
16423
  router.use("/api/resources/*", authMiddleware);
@@ -16129,7 +16451,7 @@ function registerCreateResource(router) {
16129
16451
  const arrayBuffer = await file.arrayBuffer();
16130
16452
  const contentBuffer = Buffer.from(arrayBuffer);
16131
16453
  const eventBus2 = c.get("eventBus");
16132
- const resourceId17 = await ResourceOperations.createResource({
16454
+ const resourceId20 = await ResourceOperations.createResource({
16133
16455
  name,
16134
16456
  content: contentBuffer,
16135
16457
  format,
@@ -16138,7 +16460,7 @@ function registerCreateResource(router) {
16138
16460
  creationMethod: creationMethod || void 0
16139
16461
  }, userId(user.id), eventBus2);
16140
16462
  return c.json({
16141
- resourceId: resourceId17
16463
+ resourceId: resourceId20
16142
16464
  }, 202);
16143
16465
  });
16144
16466
  }
@@ -16188,7 +16510,7 @@ function getBodySource(body) {
16188
16510
  const itemType = item.type;
16189
16511
  const itemSource = item.source;
16190
16512
  if (itemType === "SpecificResource" && typeof itemSource === "string") {
16191
- return resourceUri(itemSource);
16513
+ return itemSource;
16192
16514
  }
16193
16515
  }
16194
16516
  }
@@ -16198,7 +16520,7 @@ function getBodySource(body) {
16198
16520
  const bodyType = body.type;
16199
16521
  const bodySource = body.source;
16200
16522
  if (bodyType === "SpecificResource" && typeof bodySource === "string") {
16201
- return resourceUri(bodySource);
16523
+ return bodySource;
16202
16524
  }
16203
16525
  }
16204
16526
  return null;
@@ -17868,7 +18190,6 @@ function registerGetResourceLLMContext(router) {
17868
18190
  router.get("/resources/:id/llm-context", async (c) => {
17869
18191
  const { id } = c.req.param();
17870
18192
  const query = c.req.query();
17871
- const config2 = c.get("config");
17872
18193
  const eventBus2 = c.get("eventBus");
17873
18194
  const depth = query.depth ? Number(query.depth) : 2;
17874
18195
  const maxResources = query.maxResources ? Number(query.maxResources) : 10;
@@ -17884,9 +18205,8 @@ function registerGetResourceLLMContext(router) {
17884
18205
  message: 'Query parameter "maxResources" must be between 1 and 20'
17885
18206
  });
17886
18207
  }
17887
- const resourceUri3 = `${config2.services.backend.publicURL}/resources/${id}`;
17888
18208
  eventBus2.get("gather:resource-requested").next({
17889
- resourceUri: resourceUri3,
18209
+ resourceId: resourceId(id),
17890
18210
  options: {
17891
18211
  depth,
17892
18212
  maxResources,
@@ -17895,10 +18215,10 @@ function registerGetResourceLLMContext(router) {
17895
18215
  }
17896
18216
  });
17897
18217
  try {
17898
- const result = await (0, import_rxjs2.firstValueFrom)((0, import_rxjs2.merge)(eventBus2.get("gather:resource-complete").pipe((0, import_operators2.filter)((e) => e.resourceUri === resourceUri3), (0, import_operators2.map)((e) => ({
18218
+ const result = await (0, import_rxjs2.firstValueFrom)((0, import_rxjs2.merge)(eventBus2.get("gather:resource-complete").pipe((0, import_operators2.filter)((e) => e.resourceId === id), (0, import_operators2.map)((e) => ({
17899
18219
  ok: true,
17900
18220
  context: e.context
17901
- }))), eventBus2.get("gather:resource-failed").pipe((0, import_operators2.filter)((e) => e.resourceUri === resourceUri3), (0, import_operators2.map)((e) => ({
18221
+ }))), eventBus2.get("gather:resource-failed").pipe((0, import_operators2.filter)((e) => e.resourceId === id), (0, import_operators2.map)((e) => ({
17902
18222
  ok: false,
17903
18223
  error: e.error
17904
18224
  })))).pipe((0, import_operators2.take)(1), (0, import_operators2.timeout)(3e4)));
@@ -17932,7 +18252,6 @@ function registerGetAnnotationLLMContext(router) {
17932
18252
  router.get("/resources/:resourceId/annotations/:annotationId/llm-context", async (c) => {
17933
18253
  const { resourceId: resourceIdParam, annotationId: annotationIdParam } = c.req.param();
17934
18254
  const query = c.req.query();
17935
- const config2 = c.get("config");
17936
18255
  const eventBus2 = c.get("eventBus");
17937
18256
  const includeSourceContext = query.includeSourceContext === "false" ? false : true;
17938
18257
  const includeTargetContext = query.includeTargetContext === "false" ? false : true;
@@ -17942,11 +18261,9 @@ function registerGetAnnotationLLMContext(router) {
17942
18261
  message: 'Query parameter "contextWindow" must be between 100 and 5000'
17943
18262
  });
17944
18263
  }
17945
- const fullAnnotationUri = `${config2.services.backend.publicURL}/annotations/${annotationIdParam}`;
17946
- const resourceUri3 = `${config2.services.backend.publicURL}/resources/${resourceIdParam}`;
17947
18264
  eventBus2.get("gather:requested").next({
17948
- annotationUri: fullAnnotationUri,
17949
- resourceUri: resourceUri3,
18265
+ annotationId: annotationId(annotationIdParam),
18266
+ resourceId: resourceId(resourceIdParam),
17950
18267
  options: {
17951
18268
  includeSourceContext,
17952
18269
  includeTargetContext,
@@ -17954,10 +18271,10 @@ function registerGetAnnotationLLMContext(router) {
17954
18271
  }
17955
18272
  });
17956
18273
  try {
17957
- const result = await (0, import_rxjs3.firstValueFrom)((0, import_rxjs3.merge)(eventBus2.get("gather:complete").pipe((0, import_operators3.filter)((e) => e.annotationUri === fullAnnotationUri), (0, import_operators3.map)((e) => ({
18274
+ const result = await (0, import_rxjs3.firstValueFrom)((0, import_rxjs3.merge)(eventBus2.get("gather:complete").pipe((0, import_operators3.filter)((e) => e.annotationId === annotationIdParam), (0, import_operators3.map)((e) => ({
17958
18275
  ok: true,
17959
18276
  response: e.response
17960
- }))), eventBus2.get("gather:failed").pipe((0, import_operators3.filter)((e) => e.annotationUri === fullAnnotationUri), (0, import_operators3.map)((e) => ({
18277
+ }))), eventBus2.get("gather:failed").pipe((0, import_operators3.filter)((e) => e.annotationId === annotationIdParam), (0, import_operators3.map)((e) => ({
17961
18278
  ok: false,
17962
18279
  error: e.error
17963
18280
  })))).pipe((0, import_operators3.take)(1), (0, import_operators3.timeout)(3e4)));
@@ -18017,6 +18334,129 @@ function registerGetReferencedBy(router) {
18017
18334
  });
18018
18335
  }
18019
18336
  __name(registerGetReferencedBy, "registerGetReferencedBy");
18337
+ init_logger();
18338
+ function registerBindSearchStream(router) {
18339
+ router.post("/resources/:id/bind-search-stream", async (c) => {
18340
+ const { id } = c.req.param();
18341
+ const logger2 = getLogger().child({
18342
+ component: "bind-search-stream",
18343
+ resourceId: id
18344
+ });
18345
+ const user = c.get("user");
18346
+ if (!user) {
18347
+ throw new HTTPException(401, {
18348
+ message: "Authentication required"
18349
+ });
18350
+ }
18351
+ const body = await c.req.json();
18352
+ const { referenceId, context, limit, useSemanticScoring } = body;
18353
+ if (!referenceId || !context) {
18354
+ throw new HTTPException(400, {
18355
+ message: "referenceId and context are required"
18356
+ });
18357
+ }
18358
+ const eventBus2 = c.get("eventBus");
18359
+ const { kb } = c.get("makeMeaning");
18360
+ const resource = await ResourceContext.getResourceMetadata(resourceId(id), kb);
18361
+ if (!resource) {
18362
+ throw new HTTPException(404, {
18363
+ message: "Resource not found"
18364
+ });
18365
+ }
18366
+ const correlationId = crypto.randomUUID();
18367
+ logger2.info("Starting bind search stream", {
18368
+ referenceId,
18369
+ correlationId
18370
+ });
18371
+ c.header("X-Accel-Buffering", "no");
18372
+ c.header("Cache-Control", "no-cache, no-transform");
18373
+ return streamSSE(c, async (stream) => {
18374
+ let isStreamClosed = false;
18375
+ const subscriptions = [];
18376
+ let closeStreamCallback = null;
18377
+ const streamPromise = new Promise((resolve2) => {
18378
+ closeStreamCallback = resolve2;
18379
+ });
18380
+ const cleanup = /* @__PURE__ */ __name(() => {
18381
+ if (isStreamClosed) return;
18382
+ isStreamClosed = true;
18383
+ subscriptions.forEach((sub) => sub.unsubscribe());
18384
+ if (closeStreamCallback) closeStreamCallback();
18385
+ }, "cleanup");
18386
+ try {
18387
+ subscriptions.push(eventBus2.get("bind:search-results").subscribe(async (event) => {
18388
+ if (event.correlationId !== correlationId) return;
18389
+ if (isStreamClosed) return;
18390
+ logger2.info("Bind search completed", {
18391
+ referenceId,
18392
+ resultCount: event.results.length
18393
+ });
18394
+ try {
18395
+ await writeTypedSSE(stream, {
18396
+ data: JSON.stringify({
18397
+ referenceId: event.referenceId,
18398
+ results: event.results
18399
+ }),
18400
+ event: "bind:search-results",
18401
+ id: String(Date.now())
18402
+ });
18403
+ } catch (error) {
18404
+ logger2.warn("Client disconnected during results");
18405
+ }
18406
+ cleanup();
18407
+ }));
18408
+ subscriptions.push(eventBus2.get("bind:search-failed").subscribe(async (event) => {
18409
+ if (event.correlationId !== correlationId) return;
18410
+ if (isStreamClosed) return;
18411
+ logger2.error("Bind search failed", {
18412
+ referenceId,
18413
+ error: event.error
18414
+ });
18415
+ try {
18416
+ await writeTypedSSE(stream, {
18417
+ data: JSON.stringify({
18418
+ referenceId: event.referenceId,
18419
+ error: event.error.message
18420
+ }),
18421
+ event: "bind:search-failed",
18422
+ id: String(Date.now())
18423
+ });
18424
+ } catch (error) {
18425
+ logger2.warn("Client disconnected during error");
18426
+ }
18427
+ cleanup();
18428
+ }));
18429
+ eventBus2.get("bind:search-requested").next({
18430
+ correlationId,
18431
+ referenceId,
18432
+ context,
18433
+ limit,
18434
+ useSemanticScoring
18435
+ });
18436
+ c.req.raw.signal.addEventListener("abort", () => {
18437
+ logger2.info("Client disconnected from bind search stream");
18438
+ cleanup();
18439
+ });
18440
+ } catch (error) {
18441
+ try {
18442
+ await writeTypedSSE(stream, {
18443
+ data: JSON.stringify({
18444
+ referenceId,
18445
+ error: error instanceof Error ? error.message : "Search failed"
18446
+ }),
18447
+ event: "bind:search-failed",
18448
+ id: String(Date.now())
18449
+ });
18450
+ } catch (sseError) {
18451
+ logger2.warn("Could not send error to client");
18452
+ }
18453
+ cleanup();
18454
+ }
18455
+ return streamPromise;
18456
+ });
18457
+ });
18458
+ }
18459
+ __name(registerBindSearchStream, "registerBindSearchStream");
18020
18460
  function registerTokenRoutes(router) {
18021
18461
  router.get("/api/clone-tokens/:token", async (c) => {
18022
18462
  const { token } = c.req.param();
@@ -18195,18 +18635,17 @@ init_logger();
18195
18635
  function registerGetEventStream(router) {
18196
18636
  router.get("/resources/:id/events/stream", async (c) => {
18197
18637
  const { id } = c.req.param();
18198
- const config2 = c.get("config");
18199
18638
  const logger2 = getLogger().child({
18200
18639
  component: "events-stream",
18201
18640
  resourceId: id
18202
18641
  });
18203
- const rUri = resourceUri(`${config2.services.backend.publicURL}/resources/${id}`);
18642
+ const rId = resourceId(id);
18204
18643
  logger2.info("Client connecting to resource events stream", {
18205
- resourceUri: rUri
18644
+ resourceId: rId
18206
18645
  });
18207
18646
  const { eventStore } = c.get("makeMeaning");
18208
18647
  const query = new EventQuery(eventStore.log.storage);
18209
- const events = await query.getResourceEvents(resourceId(id));
18648
+ const events = await query.getResourceEvents(rId);
18210
18649
  if (events.length === 0) {
18211
18650
  logger2.warn("Resource not found - no events exist");
18212
18651
  throw new HTTPException(404, {
@@ -18249,11 +18688,11 @@ function registerGetEventStream(router) {
18249
18688
  }
18250
18689
  }, "cleanup");
18251
18690
  const streamId = `${id.substring(0, 16)}...${Math.random().toString(36).substring(7)}`;
18252
- logger2.info("Subscribing to events for resource URI", {
18691
+ logger2.info("Subscribing to events for resource", {
18253
18692
  streamId,
18254
- resourceUri: rUri
18693
+ resourceId: rId
18255
18694
  });
18256
- subscription = eventStore.bus.subscriptions.subscribe(rUri, async (storedEvent) => {
18695
+ subscription = eventStore.bus.subscriptions.subscribe(rId, async (storedEvent) => {
18257
18696
  if (isStreamClosed) {
18258
18697
  logger2.info("Stream already closed, ignoring event", {
18259
18698
  streamId,
@@ -18284,14 +18723,7 @@ function registerGetEventStream(router) {
18284
18723
  });
18285
18724
  let jsonData;
18286
18725
  try {
18287
- const startStringify = Date.now();
18288
18726
  jsonData = JSON.stringify(eventData);
18289
- const stringifyTime = Date.now() - startStringify;
18290
- logger2.info("JSON.stringify completed", {
18291
- streamId,
18292
- time: stringifyTime,
18293
- size: jsonData.length
18294
- });
18295
18727
  } catch (stringifyError) {
18296
18728
  logger2.error("JSON.stringify FAILED", {
18297
18729
  streamId,
@@ -18299,23 +18731,14 @@ function registerGetEventStream(router) {
18299
18731
  });
18300
18732
  throw stringifyError;
18301
18733
  }
18302
- if (storedEvent.event.type === "annotation.body.updated") {
18303
- logger2.info("annotation.body.updated payload", {
18304
- streamId,
18305
- payload: storedEvent.event.payload
18306
- });
18307
- }
18308
- const startWrite = Date.now();
18309
18734
  await stream.writeSSE({
18310
18735
  data: jsonData,
18311
18736
  event: storedEvent.event.type,
18312
18737
  id: storedEvent.metadata.sequenceNumber.toString()
18313
18738
  });
18314
- const writeTime = Date.now() - startWrite;
18315
18739
  logger2.info("Successfully wrote event to SSE stream", {
18316
18740
  streamId,
18317
- eventType: storedEvent.event.type,
18318
- time: writeTime
18741
+ eventType: storedEvent.event.type
18319
18742
  });
18320
18743
  } catch (error) {
18321
18744
  logger2.error("Error writing event to SSE stream", {
@@ -18355,16 +18778,9 @@ function registerCreateAnnotation(router) {
18355
18778
  const { id } = c.req.param();
18356
18779
  const request = c.get("validatedBody");
18357
18780
  const user = c.get("user");
18358
- const config2 = c.get("config");
18359
- const backendUrl = config2.services.backend?.publicURL;
18360
- if (!backendUrl) {
18361
- throw new HTTPException(500, {
18362
- message: "Backend publicURL not configured"
18363
- });
18364
- }
18365
18781
  let annotation;
18366
18782
  try {
18367
- ({ annotation } = assembleAnnotation(request, userToAgent(user), backendUrl));
18783
+ ({ annotation } = assembleAnnotation(request, userToAgent(user)));
18368
18784
  } catch (error) {
18369
18785
  if (error instanceof Error) {
18370
18786
  throw new HTTPException(400, {
@@ -18459,7 +18875,6 @@ function registerYieldResourceStream(router, jobQueue) {
18459
18875
  body
18460
18876
  });
18461
18877
  const user = c.get("user");
18462
- const config2 = c.get("config");
18463
18878
  if (!user) {
18464
18879
  throw new HTTPException(401, {
18465
18880
  message: "Authentication required"
@@ -18476,14 +18891,13 @@ function registerYieldResourceStream(router, jobQueue) {
18476
18891
  count: linkingAnnotations.length,
18477
18892
  ids: linkingAnnotations.map((a) => a.id)
18478
18893
  });
18479
- const expectedAnnotationUri = `${config2.services.backend.publicURL}/annotations/${annotationIdParam}`;
18480
- logger2.info("Looking for annotation URI", {
18481
- expectedAnnotationUri
18894
+ logger2.info("Looking for annotation", {
18895
+ annotationId: annotationIdParam
18482
18896
  });
18483
- const reference = projection.annotations.find((a) => a.id === expectedAnnotationUri && a.motivation === "linking");
18897
+ const reference = projection.annotations.find((a) => a.id === annotationIdParam && a.motivation === "linking");
18484
18898
  if (!reference) {
18485
18899
  logger2.warn("Annotation not found", {
18486
- expectedUri: expectedAnnotationUri,
18900
+ expectedId: annotationIdParam,
18487
18901
  availableIds: projection.annotations.map((a) => a.id)
18488
18902
  });
18489
18903
  throw new HTTPException(404, {
@@ -18665,14 +19079,14 @@ function registerYieldResourceStream(router, jobQueue) {
18665
19079
  __name(registerYieldResourceStream, "registerYieldResourceStream");
18666
19080
  function registerGetAnnotationHistory(router) {
18667
19081
  router.get("/resources/:resourceId/annotations/:annotationId/history", async (c) => {
18668
- const { resourceId: resourceId17, annotationId: annotationId5 } = c.req.param();
19082
+ const { resourceId: resourceId20, annotationId: annotationId6 } = c.req.param();
18669
19083
  const eventBus2 = c.get("eventBus");
18670
19084
  const correlationId = crypto.randomUUID();
18671
19085
  try {
18672
19086
  const response = await eventBusRequest(eventBus2, "browse:annotation-history-requested", {
18673
19087
  correlationId,
18674
- resourceId: resourceId(resourceId17),
18675
- annotationId: annotationId(annotationId5)
19088
+ resourceId: resourceId(resourceId20),
19089
+ annotationId: annotationId(annotationId6)
18676
19090
  }, "browse:annotation-history-result", "browse:annotation-history-failed");
18677
19091
  return c.json(response);
18678
19092
  } catch (error) {
@@ -18713,6 +19127,7 @@ function createResourcesRouter(jobQueue) {
18713
19127
  registerGetResourceLLMContext(resourcesRouter2);
18714
19128
  registerGetAnnotationLLMContext(resourcesRouter2);
18715
19129
  registerGetReferencedBy(resourcesRouter2);
19130
+ registerBindSearchStream(resourcesRouter2);
18716
19131
  registerGetResourceAnnotations(resourcesRouter2);
18717
19132
  registerCreateAnnotation(resourcesRouter2);
18718
19133
  registerGetAnnotation(resourcesRouter2);
@@ -18739,39 +19154,30 @@ function registerGetAnnotationUri(router) {
18739
19154
  const { id } = c.req.param();
18740
19155
  const query = c.req.query();
18741
19156
  const { kb } = c.get("makeMeaning");
18742
- const resourceUriOrId = query.resourceId;
18743
- if (!resourceUriOrId) {
19157
+ const resourceIdParam = query.resourceId;
19158
+ if (!resourceIdParam) {
18744
19159
  throw new HTTPException(400, {
18745
19160
  message: "resourceId query parameter is required"
18746
19161
  });
18747
19162
  }
18748
- let extractedResourceId;
18749
- try {
18750
- extractedResourceId = resourceUriOrId.includes("://") ? uriToResourceId(resourceUriOrId) : resourceUriOrId;
18751
- } catch (error) {
18752
- throw new HTTPException(400, {
18753
- message: "Invalid resourceId parameter"
18754
- });
18755
- }
18756
19163
  if (prefersHtml(c)) {
18757
19164
  const frontendUrl = getFrontendUrl();
18758
19165
  const normalizedBase = frontendUrl.endsWith("/") ? frontendUrl.slice(0, -1) : frontendUrl;
18759
- const redirectUrl = `${normalizedBase}/annotations/${id}?resourceId=${extractedResourceId}`;
19166
+ const redirectUrl = `${normalizedBase}/annotations/${id}?resourceId=${resourceIdParam}`;
18760
19167
  return c.redirect(redirectUrl, 302);
18761
19168
  }
18762
- const projection = await AnnotationContext.getResourceAnnotations(resourceId(extractedResourceId), kb);
19169
+ const projection = await AnnotationContext.getResourceAnnotations(resourceId(resourceIdParam), kb);
18763
19170
  const annotation = projection.annotations.find((a) => a.id === id);
18764
19171
  if (!annotation) {
18765
19172
  throw new HTTPException(404, {
18766
19173
  message: "Annotation not found in resource"
18767
19174
  });
18768
19175
  }
18769
- const resource = await ResourceContext.getResourceMetadata(resourceId(extractedResourceId), kb);
19176
+ const resource = await ResourceContext.getResourceMetadata(resourceId(resourceIdParam), kb);
18770
19177
  let resolvedResource = null;
18771
19178
  const bodySource = getBodySource(annotation.body);
18772
19179
  if (annotation.motivation === "linking" && bodySource) {
18773
- const bodyDocId = bodySource.includes("://") ? uriToResourceId(bodySource) : bodySource;
18774
- resolvedResource = await ResourceContext.getResourceMetadata(resourceId(bodyDocId), kb);
19180
+ resolvedResource = await ResourceContext.getResourceMetadata(resourceId(bodySource), kb);
18775
19181
  }
18776
19182
  const response = {
18777
19183
  annotation,
@@ -18788,8 +19194,8 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
18788
19194
  const { id } = c.req.param();
18789
19195
  const query = c.req.query();
18790
19196
  const { kb } = c.get("makeMeaning");
18791
- const resourceId17 = query.resourceId;
18792
- if (!resourceId17) {
19197
+ const resourceId20 = query.resourceId;
19198
+ if (!resourceId20) {
18793
19199
  throw new HTTPException(400, {
18794
19200
  message: "resourceId query parameter is required"
18795
19201
  });
@@ -18807,7 +19213,7 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
18807
19213
  });
18808
19214
  }
18809
19215
  try {
18810
- const response = await AnnotationContext.getAnnotationContext(annotationId(id), resourceId(resourceId17), contextBefore, contextAfter, kb);
19216
+ const response = await AnnotationContext.getAnnotationContext(annotationId(id), resourceId(resourceId20), contextBefore, contextAfter, kb);
18811
19217
  return c.json(response);
18812
19218
  } catch (error) {
18813
19219
  if (error instanceof Error && error.message === "Annotation not found") {
@@ -18837,14 +19243,14 @@ operationsRouter.get("/api/annotations/:id/summary", async (c) => {
18837
19243
  const { id } = c.req.param();
18838
19244
  const query = c.req.query();
18839
19245
  const { kb, inferenceClient } = c.get("makeMeaning");
18840
- const resourceId17 = query.resourceId;
18841
- if (!resourceId17) {
19246
+ const resourceId20 = query.resourceId;
19247
+ if (!resourceId20) {
18842
19248
  throw new HTTPException(400, {
18843
19249
  message: "resourceId query parameter is required"
18844
19250
  });
18845
19251
  }
18846
19252
  try {
18847
- const response = await AnnotationContext.generateAnnotationSummary(annotationId(id), resourceId(resourceId17), kb, inferenceClient);
19253
+ const response = await AnnotationContext.generateAnnotationSummary(annotationId(id), resourceId(resourceId20), kb, inferenceClient);
18848
19254
  return c.json(response);
18849
19255
  } catch (error) {
18850
19256
  if (error instanceof Error && error.message === "Annotation not found") {
@@ -19205,6 +19611,7 @@ app.route("/", healthRouter);
19205
19611
  app.route("/", authRouter);
19206
19612
  app.route("/", statusRouter);
19207
19613
  app.route("/", adminRouter);
19614
+ app.route("/", exchangeRouter);
19208
19615
  var resourcesRouter = createResourcesRouter(makeMeaning.jobQueue);
19209
19616
  app.route("/", resourcesRouter);
19210
19617
  app.route("/", annotationsRouter);
@@ -19271,8 +19678,7 @@ app.all("/api/*", (c) => {
19271
19678
  }, 404);
19272
19679
  });
19273
19680
  var port = backendService.port || 4e3;
19274
- var nodeEnv = config.env?.NODE_ENV || "development";
19275
- if (nodeEnv !== "test") {
19681
+ if (config.env?.NODE_ENV !== "test") {
19276
19682
  serve({
19277
19683
  fetch: app.fetch,
19278
19684
  port,
@@ -19280,7 +19686,7 @@ if (nodeEnv !== "test") {
19280
19686
  }, async (info) => {
19281
19687
  logger.info("Semiont Backend ready", {
19282
19688
  url: `http://localhost:${info.port}/api`,
19283
- environment: nodeEnv
19689
+ environment: config.env?.NODE_ENV ?? "development"
19284
19690
  });
19285
19691
  try {
19286
19692
  const { JWTService: JWTService2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));