@semiont/backend 0.2.46 → 0.3.1

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,10 +15215,95 @@ var openapi_default = {
15204
15215
  description: "Entity types associated with the annotation"
15205
15216
  }
15206
15217
  }
15218
+ },
15219
+ userHint: {
15220
+ type: "string",
15221
+ description: "User-provided textual hint to supplement or replace the selected text for search and generation"
15222
+ },
15223
+ graphContext: {
15224
+ type: "object",
15225
+ description: "Graph-derived context from the knowledge base",
15226
+ properties: {
15227
+ connections: {
15228
+ type: "array",
15229
+ description: "Resources connected to the source resource via annotations",
15230
+ items: {
15231
+ type: "object",
15232
+ properties: {
15233
+ resourceId: {
15234
+ type: "string",
15235
+ description: "ID of the connected resource"
15236
+ },
15237
+ resourceName: {
15238
+ type: "string",
15239
+ description: "Name of the connected resource"
15240
+ },
15241
+ entityTypes: {
15242
+ type: "array",
15243
+ items: {
15244
+ type: "string"
15245
+ },
15246
+ description: "Entity types on the connected resource"
15247
+ },
15248
+ bidirectional: {
15249
+ type: "boolean",
15250
+ description: "Whether the connection goes both ways"
15251
+ }
15252
+ },
15253
+ required: [
15254
+ "resourceId",
15255
+ "resourceName",
15256
+ "bidirectional"
15257
+ ]
15258
+ }
15259
+ },
15260
+ citedByCount: {
15261
+ type: "integer",
15262
+ description: "Number of other resources that reference the source resource"
15263
+ },
15264
+ citedBy: {
15265
+ type: "array",
15266
+ description: "Resources that reference the source resource",
15267
+ items: {
15268
+ type: "object",
15269
+ properties: {
15270
+ resourceId: {
15271
+ type: "string"
15272
+ },
15273
+ resourceName: {
15274
+ type: "string"
15275
+ }
15276
+ },
15277
+ required: [
15278
+ "resourceId",
15279
+ "resourceName"
15280
+ ]
15281
+ }
15282
+ },
15283
+ siblingEntityTypes: {
15284
+ type: "array",
15285
+ items: {
15286
+ type: "string"
15287
+ },
15288
+ description: "Entity types from other annotations on the same resource"
15289
+ },
15290
+ entityTypeFrequencies: {
15291
+ type: "object",
15292
+ description: "Global frequency counts for entity types (for IDF-like weighting)",
15293
+ additionalProperties: {
15294
+ type: "integer"
15295
+ }
15296
+ },
15297
+ inferredRelationshipSummary: {
15298
+ type: "string",
15299
+ description: "LLM-generated summary of the annotation's relationships in the knowledge graph"
15300
+ }
15301
+ }
15207
15302
  }
15208
15303
  },
15209
15304
  required: [
15210
- "sourceContext"
15305
+ "annotation",
15306
+ "sourceResource"
15211
15307
  ]
15212
15308
  }
15213
15309
  }
@@ -16096,6 +16192,235 @@ adminRouter.get("/api/admin/oauth/config", async (c) => {
16096
16192
  };
16097
16193
  return c.json(response, 200);
16098
16194
  });
16195
+ var adminMiddleware2 = /* @__PURE__ */ __name(async (c, next) => {
16196
+ const user = c.get("user");
16197
+ if (!user || !user.isAdmin) {
16198
+ return c.json({
16199
+ error: "Forbidden: Admin access required"
16200
+ }, 403);
16201
+ }
16202
+ return next();
16203
+ }, "adminMiddleware");
16204
+ var moderatorMiddleware = /* @__PURE__ */ __name(async (c, next) => {
16205
+ const user = c.get("user");
16206
+ if (!user || !user.isModerator && !user.isAdmin) {
16207
+ return c.json({
16208
+ error: "Forbidden: Moderator or Admin access required"
16209
+ }, 403);
16210
+ }
16211
+ return next();
16212
+ }, "moderatorMiddleware");
16213
+ var exchangeRouter = new Hono();
16214
+ exchangeRouter.use("/api/admin/exchange/*", authMiddleware, adminMiddleware2);
16215
+ exchangeRouter.use("/api/moderate/exchange/*", authMiddleware, moderatorMiddleware);
16216
+ exchangeRouter.post("/api/admin/exchange/backup", async (c) => {
16217
+ const mm = c.get("makeMeaning");
16218
+ const config2 = c.get("config");
16219
+ const sourceUrl = config2.services?.backend?.publicURL ?? "http://localhost:4000";
16220
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16221
+ const filename = `semiont-backup-${timestamp}.tar.gz`;
16222
+ let controller;
16223
+ const webReadable = new ReadableStream({
16224
+ start(ctrl) {
16225
+ controller = ctrl;
16226
+ }
16227
+ });
16228
+ const nodeWritable = new Writable({
16229
+ write(chunk, _encoding, callback) {
16230
+ controller.enqueue(chunk);
16231
+ callback();
16232
+ },
16233
+ final(callback) {
16234
+ controller.close();
16235
+ callback();
16236
+ }
16237
+ });
16238
+ (async () => {
16239
+ try {
16240
+ await exportBackup({
16241
+ eventStore: mm.kb.eventStore,
16242
+ content: mm.kb.content,
16243
+ sourceUrl
16244
+ }, nodeWritable);
16245
+ } catch (err) {
16246
+ controller.error(err);
16247
+ }
16248
+ })();
16249
+ return new Response(webReadable, {
16250
+ headers: {
16251
+ "Content-Type": "application/gzip",
16252
+ "Content-Disposition": `attachment; filename="${filename}"`,
16253
+ "Cache-Control": "no-cache"
16254
+ }
16255
+ });
16256
+ });
16257
+ exchangeRouter.post("/api/admin/exchange/restore", async (c) => {
16258
+ const formData = await c.req.formData();
16259
+ const file = formData.get("file");
16260
+ if (!file) {
16261
+ return c.json({
16262
+ error: "No file provided"
16263
+ }, 400);
16264
+ }
16265
+ const eventBus2 = c.get("eventBus");
16266
+ const buffer = Buffer.from(await file.arrayBuffer());
16267
+ const encoder = new TextEncoder();
16268
+ const stream = new ReadableStream({
16269
+ async start(controller) {
16270
+ const send = /* @__PURE__ */ __name((data) => {
16271
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
16272
+
16273
+ `));
16274
+ }, "send");
16275
+ try {
16276
+ const input = new Readable({
16277
+ read() {
16278
+ }
16279
+ });
16280
+ input.push(buffer);
16281
+ input.push(null);
16282
+ send({
16283
+ phase: "started",
16284
+ message: "Restoring backup..."
16285
+ });
16286
+ const result = await importBackup(input, {
16287
+ eventBus: eventBus2
16288
+ });
16289
+ send({
16290
+ phase: "complete",
16291
+ result: {
16292
+ stats: result.stats,
16293
+ hashChainValid: result.hashChainValid
16294
+ }
16295
+ });
16296
+ } catch (err) {
16297
+ send({
16298
+ phase: "error",
16299
+ message: err instanceof Error ? err.message : String(err)
16300
+ });
16301
+ } finally {
16302
+ controller.close();
16303
+ }
16304
+ }
16305
+ });
16306
+ return new Response(stream, {
16307
+ headers: {
16308
+ "Content-Type": "text/event-stream",
16309
+ "Cache-Control": "no-cache",
16310
+ "Connection": "keep-alive"
16311
+ }
16312
+ });
16313
+ });
16314
+ exchangeRouter.post("/api/moderate/exchange/export", async (c) => {
16315
+ const mm = c.get("makeMeaning");
16316
+ const config2 = c.get("config");
16317
+ const sourceUrl = config2.services?.backend?.publicURL ?? "http://localhost:4000";
16318
+ const includeArchived = c.req.query("includeArchived") === "true";
16319
+ const entityTypes = await readEntityTypesProjection({
16320
+ services: {
16321
+ filesystem: config2.services?.filesystem
16322
+ },
16323
+ _metadata: config2._metadata
16324
+ });
16325
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
16326
+ const filename = `semiont-export-${timestamp}.tar.gz`;
16327
+ let controller;
16328
+ const webReadable = new ReadableStream({
16329
+ start(ctrl) {
16330
+ controller = ctrl;
16331
+ }
16332
+ });
16333
+ const nodeWritable = new Writable({
16334
+ write(chunk, _encoding, callback) {
16335
+ controller.enqueue(chunk);
16336
+ callback();
16337
+ },
16338
+ final(callback) {
16339
+ controller.close();
16340
+ callback();
16341
+ }
16342
+ });
16343
+ (async () => {
16344
+ try {
16345
+ await exportLinkedData({
16346
+ views: mm.kb.views,
16347
+ content: mm.kb.content,
16348
+ sourceUrl,
16349
+ entityTypes,
16350
+ includeArchived
16351
+ }, nodeWritable);
16352
+ } catch (err) {
16353
+ controller.error(err);
16354
+ }
16355
+ })();
16356
+ return new Response(webReadable, {
16357
+ headers: {
16358
+ "Content-Type": "application/gzip",
16359
+ "Content-Disposition": `attachment; filename="${filename}"`,
16360
+ "Cache-Control": "no-cache"
16361
+ }
16362
+ });
16363
+ });
16364
+ exchangeRouter.post("/api/moderate/exchange/import", async (c) => {
16365
+ const formData = await c.req.formData();
16366
+ const file = formData.get("file");
16367
+ if (!file) {
16368
+ return c.json({
16369
+ error: "No file provided"
16370
+ }, 400);
16371
+ }
16372
+ const eventBus2 = c.get("eventBus");
16373
+ const user = c.get("user");
16374
+ const buffer = Buffer.from(await file.arrayBuffer());
16375
+ const encoder = new TextEncoder();
16376
+ const stream = new ReadableStream({
16377
+ async start(controller) {
16378
+ const send = /* @__PURE__ */ __name((data) => {
16379
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
16380
+
16381
+ `));
16382
+ }, "send");
16383
+ try {
16384
+ const input = new Readable({
16385
+ read() {
16386
+ }
16387
+ });
16388
+ input.push(buffer);
16389
+ input.push(null);
16390
+ send({
16391
+ phase: "started",
16392
+ message: "Importing linked data..."
16393
+ });
16394
+ const result = await importLinkedData(input, {
16395
+ eventBus: eventBus2,
16396
+ userId: userId(user.id)
16397
+ });
16398
+ send({
16399
+ phase: "complete",
16400
+ result: {
16401
+ resourcesCreated: result.resourcesCreated,
16402
+ annotationsCreated: result.annotationsCreated,
16403
+ entityTypesAdded: result.entityTypesAdded
16404
+ }
16405
+ });
16406
+ } catch (err) {
16407
+ send({
16408
+ phase: "error",
16409
+ message: err instanceof Error ? err.message : String(err)
16410
+ });
16411
+ } finally {
16412
+ controller.close();
16413
+ }
16414
+ }
16415
+ });
16416
+ return new Response(stream, {
16417
+ headers: {
16418
+ "Content-Type": "text/event-stream",
16419
+ "Cache-Control": "no-cache",
16420
+ "Connection": "keep-alive"
16421
+ }
16422
+ });
16423
+ });
16099
16424
  function createResourceRouter() {
16100
16425
  const router = new Hono();
16101
16426
  router.use("/api/resources/*", authMiddleware);
@@ -16129,7 +16454,7 @@ function registerCreateResource(router) {
16129
16454
  const arrayBuffer = await file.arrayBuffer();
16130
16455
  const contentBuffer = Buffer.from(arrayBuffer);
16131
16456
  const eventBus2 = c.get("eventBus");
16132
- const resourceId17 = await ResourceOperations.createResource({
16457
+ const resourceId20 = await ResourceOperations.createResource({
16133
16458
  name,
16134
16459
  content: contentBuffer,
16135
16460
  format,
@@ -16138,7 +16463,7 @@ function registerCreateResource(router) {
16138
16463
  creationMethod: creationMethod || void 0
16139
16464
  }, userId(user.id), eventBus2);
16140
16465
  return c.json({
16141
- resourceId: resourceId17
16466
+ resourceId: resourceId20
16142
16467
  }, 202);
16143
16468
  });
16144
16469
  }
@@ -16188,7 +16513,7 @@ function getBodySource(body) {
16188
16513
  const itemType = item.type;
16189
16514
  const itemSource = item.source;
16190
16515
  if (itemType === "SpecificResource" && typeof itemSource === "string") {
16191
- return resourceUri(itemSource);
16516
+ return itemSource;
16192
16517
  }
16193
16518
  }
16194
16519
  }
@@ -16198,7 +16523,7 @@ function getBodySource(body) {
16198
16523
  const bodyType = body.type;
16199
16524
  const bodySource = body.source;
16200
16525
  if (bodyType === "SpecificResource" && typeof bodySource === "string") {
16201
- return resourceUri(bodySource);
16526
+ return bodySource;
16202
16527
  }
16203
16528
  }
16204
16529
  return null;
@@ -17868,7 +18193,6 @@ function registerGetResourceLLMContext(router) {
17868
18193
  router.get("/resources/:id/llm-context", async (c) => {
17869
18194
  const { id } = c.req.param();
17870
18195
  const query = c.req.query();
17871
- const config2 = c.get("config");
17872
18196
  const eventBus2 = c.get("eventBus");
17873
18197
  const depth = query.depth ? Number(query.depth) : 2;
17874
18198
  const maxResources = query.maxResources ? Number(query.maxResources) : 10;
@@ -17884,9 +18208,8 @@ function registerGetResourceLLMContext(router) {
17884
18208
  message: 'Query parameter "maxResources" must be between 1 and 20'
17885
18209
  });
17886
18210
  }
17887
- const resourceUri3 = `${config2.services.backend.publicURL}/resources/${id}`;
17888
18211
  eventBus2.get("gather:resource-requested").next({
17889
- resourceUri: resourceUri3,
18212
+ resourceId: resourceId(id),
17890
18213
  options: {
17891
18214
  depth,
17892
18215
  maxResources,
@@ -17895,10 +18218,10 @@ function registerGetResourceLLMContext(router) {
17895
18218
  }
17896
18219
  });
17897
18220
  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) => ({
18221
+ 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
18222
  ok: true,
17900
18223
  context: e.context
17901
- }))), eventBus2.get("gather:resource-failed").pipe((0, import_operators2.filter)((e) => e.resourceUri === resourceUri3), (0, import_operators2.map)((e) => ({
18224
+ }))), eventBus2.get("gather:resource-failed").pipe((0, import_operators2.filter)((e) => e.resourceId === id), (0, import_operators2.map)((e) => ({
17902
18225
  ok: false,
17903
18226
  error: e.error
17904
18227
  })))).pipe((0, import_operators2.take)(1), (0, import_operators2.timeout)(3e4)));
@@ -17932,7 +18255,6 @@ function registerGetAnnotationLLMContext(router) {
17932
18255
  router.get("/resources/:resourceId/annotations/:annotationId/llm-context", async (c) => {
17933
18256
  const { resourceId: resourceIdParam, annotationId: annotationIdParam } = c.req.param();
17934
18257
  const query = c.req.query();
17935
- const config2 = c.get("config");
17936
18258
  const eventBus2 = c.get("eventBus");
17937
18259
  const includeSourceContext = query.includeSourceContext === "false" ? false : true;
17938
18260
  const includeTargetContext = query.includeTargetContext === "false" ? false : true;
@@ -17942,11 +18264,9 @@ function registerGetAnnotationLLMContext(router) {
17942
18264
  message: 'Query parameter "contextWindow" must be between 100 and 5000'
17943
18265
  });
17944
18266
  }
17945
- const fullAnnotationUri = `${config2.services.backend.publicURL}/annotations/${annotationIdParam}`;
17946
- const resourceUri3 = `${config2.services.backend.publicURL}/resources/${resourceIdParam}`;
17947
18267
  eventBus2.get("gather:requested").next({
17948
- annotationUri: fullAnnotationUri,
17949
- resourceUri: resourceUri3,
18268
+ annotationId: annotationId(annotationIdParam),
18269
+ resourceId: resourceId(resourceIdParam),
17950
18270
  options: {
17951
18271
  includeSourceContext,
17952
18272
  includeTargetContext,
@@ -17954,10 +18274,10 @@ function registerGetAnnotationLLMContext(router) {
17954
18274
  }
17955
18275
  });
17956
18276
  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) => ({
18277
+ 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
18278
  ok: true,
17959
18279
  response: e.response
17960
- }))), eventBus2.get("gather:failed").pipe((0, import_operators3.filter)((e) => e.annotationUri === fullAnnotationUri), (0, import_operators3.map)((e) => ({
18280
+ }))), eventBus2.get("gather:failed").pipe((0, import_operators3.filter)((e) => e.annotationId === annotationIdParam), (0, import_operators3.map)((e) => ({
17961
18281
  ok: false,
17962
18282
  error: e.error
17963
18283
  })))).pipe((0, import_operators3.take)(1), (0, import_operators3.timeout)(3e4)));
@@ -18017,6 +18337,129 @@ function registerGetReferencedBy(router) {
18017
18337
  });
18018
18338
  }
18019
18339
  __name(registerGetReferencedBy, "registerGetReferencedBy");
18340
+ init_logger();
18341
+ function registerBindSearchStream(router) {
18342
+ router.post("/resources/:id/bind-search-stream", async (c) => {
18343
+ const { id } = c.req.param();
18344
+ const logger2 = getLogger().child({
18345
+ component: "bind-search-stream",
18346
+ resourceId: id
18347
+ });
18348
+ const user = c.get("user");
18349
+ if (!user) {
18350
+ throw new HTTPException(401, {
18351
+ message: "Authentication required"
18352
+ });
18353
+ }
18354
+ const body = await c.req.json();
18355
+ const { referenceId, context, limit, useSemanticScoring } = body;
18356
+ if (!referenceId || !context) {
18357
+ throw new HTTPException(400, {
18358
+ message: "referenceId and context are required"
18359
+ });
18360
+ }
18361
+ const eventBus2 = c.get("eventBus");
18362
+ const { kb } = c.get("makeMeaning");
18363
+ const resource = await ResourceContext.getResourceMetadata(resourceId(id), kb);
18364
+ if (!resource) {
18365
+ throw new HTTPException(404, {
18366
+ message: "Resource not found"
18367
+ });
18368
+ }
18369
+ const correlationId = crypto.randomUUID();
18370
+ logger2.info("Starting bind search stream", {
18371
+ referenceId,
18372
+ correlationId
18373
+ });
18374
+ c.header("X-Accel-Buffering", "no");
18375
+ c.header("Cache-Control", "no-cache, no-transform");
18376
+ return streamSSE(c, async (stream) => {
18377
+ let isStreamClosed = false;
18378
+ const subscriptions = [];
18379
+ let closeStreamCallback = null;
18380
+ const streamPromise = new Promise((resolve2) => {
18381
+ closeStreamCallback = resolve2;
18382
+ });
18383
+ const cleanup = /* @__PURE__ */ __name(() => {
18384
+ if (isStreamClosed) return;
18385
+ isStreamClosed = true;
18386
+ subscriptions.forEach((sub) => sub.unsubscribe());
18387
+ if (closeStreamCallback) closeStreamCallback();
18388
+ }, "cleanup");
18389
+ try {
18390
+ subscriptions.push(eventBus2.get("bind:search-results").subscribe(async (event) => {
18391
+ if (event.correlationId !== correlationId) return;
18392
+ if (isStreamClosed) return;
18393
+ logger2.info("Bind search completed", {
18394
+ referenceId,
18395
+ resultCount: event.results.length
18396
+ });
18397
+ try {
18398
+ await writeTypedSSE(stream, {
18399
+ data: JSON.stringify({
18400
+ referenceId: event.referenceId,
18401
+ results: event.results
18402
+ }),
18403
+ event: "bind:search-results",
18404
+ id: String(Date.now())
18405
+ });
18406
+ } catch (error) {
18407
+ logger2.warn("Client disconnected during results");
18408
+ }
18409
+ cleanup();
18410
+ }));
18411
+ subscriptions.push(eventBus2.get("bind:search-failed").subscribe(async (event) => {
18412
+ if (event.correlationId !== correlationId) return;
18413
+ if (isStreamClosed) return;
18414
+ logger2.error("Bind search failed", {
18415
+ referenceId,
18416
+ error: event.error
18417
+ });
18418
+ try {
18419
+ await writeTypedSSE(stream, {
18420
+ data: JSON.stringify({
18421
+ referenceId: event.referenceId,
18422
+ error: event.error.message
18423
+ }),
18424
+ event: "bind:search-failed",
18425
+ id: String(Date.now())
18426
+ });
18427
+ } catch (error) {
18428
+ logger2.warn("Client disconnected during error");
18429
+ }
18430
+ cleanup();
18431
+ }));
18432
+ eventBus2.get("bind:search-requested").next({
18433
+ correlationId,
18434
+ referenceId,
18435
+ context,
18436
+ limit,
18437
+ useSemanticScoring
18438
+ });
18439
+ c.req.raw.signal.addEventListener("abort", () => {
18440
+ logger2.info("Client disconnected from bind search stream");
18441
+ cleanup();
18442
+ });
18443
+ } catch (error) {
18444
+ try {
18445
+ await writeTypedSSE(stream, {
18446
+ data: JSON.stringify({
18447
+ referenceId,
18448
+ error: error instanceof Error ? error.message : "Search failed"
18449
+ }),
18450
+ event: "bind:search-failed",
18451
+ id: String(Date.now())
18452
+ });
18453
+ } catch (sseError) {
18454
+ logger2.warn("Could not send error to client");
18455
+ }
18456
+ cleanup();
18457
+ }
18458
+ return streamPromise;
18459
+ });
18460
+ });
18461
+ }
18462
+ __name(registerBindSearchStream, "registerBindSearchStream");
18020
18463
  function registerTokenRoutes(router) {
18021
18464
  router.get("/api/clone-tokens/:token", async (c) => {
18022
18465
  const { token } = c.req.param();
@@ -18195,18 +18638,17 @@ init_logger();
18195
18638
  function registerGetEventStream(router) {
18196
18639
  router.get("/resources/:id/events/stream", async (c) => {
18197
18640
  const { id } = c.req.param();
18198
- const config2 = c.get("config");
18199
18641
  const logger2 = getLogger().child({
18200
18642
  component: "events-stream",
18201
18643
  resourceId: id
18202
18644
  });
18203
- const rUri = resourceUri(`${config2.services.backend.publicURL}/resources/${id}`);
18645
+ const rId = resourceId(id);
18204
18646
  logger2.info("Client connecting to resource events stream", {
18205
- resourceUri: rUri
18647
+ resourceId: rId
18206
18648
  });
18207
18649
  const { eventStore } = c.get("makeMeaning");
18208
18650
  const query = new EventQuery(eventStore.log.storage);
18209
- const events = await query.getResourceEvents(resourceId(id));
18651
+ const events = await query.getResourceEvents(rId);
18210
18652
  if (events.length === 0) {
18211
18653
  logger2.warn("Resource not found - no events exist");
18212
18654
  throw new HTTPException(404, {
@@ -18249,11 +18691,11 @@ function registerGetEventStream(router) {
18249
18691
  }
18250
18692
  }, "cleanup");
18251
18693
  const streamId = `${id.substring(0, 16)}...${Math.random().toString(36).substring(7)}`;
18252
- logger2.info("Subscribing to events for resource URI", {
18694
+ logger2.info("Subscribing to events for resource", {
18253
18695
  streamId,
18254
- resourceUri: rUri
18696
+ resourceId: rId
18255
18697
  });
18256
- subscription = eventStore.bus.subscriptions.subscribe(rUri, async (storedEvent) => {
18698
+ subscription = eventStore.bus.subscriptions.subscribe(rId, async (storedEvent) => {
18257
18699
  if (isStreamClosed) {
18258
18700
  logger2.info("Stream already closed, ignoring event", {
18259
18701
  streamId,
@@ -18284,14 +18726,7 @@ function registerGetEventStream(router) {
18284
18726
  });
18285
18727
  let jsonData;
18286
18728
  try {
18287
- const startStringify = Date.now();
18288
18729
  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
18730
  } catch (stringifyError) {
18296
18731
  logger2.error("JSON.stringify FAILED", {
18297
18732
  streamId,
@@ -18299,23 +18734,14 @@ function registerGetEventStream(router) {
18299
18734
  });
18300
18735
  throw stringifyError;
18301
18736
  }
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
18737
  await stream.writeSSE({
18310
18738
  data: jsonData,
18311
18739
  event: storedEvent.event.type,
18312
18740
  id: storedEvent.metadata.sequenceNumber.toString()
18313
18741
  });
18314
- const writeTime = Date.now() - startWrite;
18315
18742
  logger2.info("Successfully wrote event to SSE stream", {
18316
18743
  streamId,
18317
- eventType: storedEvent.event.type,
18318
- time: writeTime
18744
+ eventType: storedEvent.event.type
18319
18745
  });
18320
18746
  } catch (error) {
18321
18747
  logger2.error("Error writing event to SSE stream", {
@@ -18355,16 +18781,9 @@ function registerCreateAnnotation(router) {
18355
18781
  const { id } = c.req.param();
18356
18782
  const request = c.get("validatedBody");
18357
18783
  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
18784
  let annotation;
18366
18785
  try {
18367
- ({ annotation } = assembleAnnotation(request, userToAgent(user), backendUrl));
18786
+ ({ annotation } = assembleAnnotation(request, userToAgent(user)));
18368
18787
  } catch (error) {
18369
18788
  if (error instanceof Error) {
18370
18789
  throw new HTTPException(400, {
@@ -18459,7 +18878,6 @@ function registerYieldResourceStream(router, jobQueue) {
18459
18878
  body
18460
18879
  });
18461
18880
  const user = c.get("user");
18462
- const config2 = c.get("config");
18463
18881
  if (!user) {
18464
18882
  throw new HTTPException(401, {
18465
18883
  message: "Authentication required"
@@ -18476,14 +18894,13 @@ function registerYieldResourceStream(router, jobQueue) {
18476
18894
  count: linkingAnnotations.length,
18477
18895
  ids: linkingAnnotations.map((a) => a.id)
18478
18896
  });
18479
- const expectedAnnotationUri = `${config2.services.backend.publicURL}/annotations/${annotationIdParam}`;
18480
- logger2.info("Looking for annotation URI", {
18481
- expectedAnnotationUri
18897
+ logger2.info("Looking for annotation", {
18898
+ annotationId: annotationIdParam
18482
18899
  });
18483
- const reference = projection.annotations.find((a) => a.id === expectedAnnotationUri && a.motivation === "linking");
18900
+ const reference = projection.annotations.find((a) => a.id === annotationIdParam && a.motivation === "linking");
18484
18901
  if (!reference) {
18485
18902
  logger2.warn("Annotation not found", {
18486
- expectedUri: expectedAnnotationUri,
18903
+ expectedId: annotationIdParam,
18487
18904
  availableIds: projection.annotations.map((a) => a.id)
18488
18905
  });
18489
18906
  throw new HTTPException(404, {
@@ -18665,14 +19082,14 @@ function registerYieldResourceStream(router, jobQueue) {
18665
19082
  __name(registerYieldResourceStream, "registerYieldResourceStream");
18666
19083
  function registerGetAnnotationHistory(router) {
18667
19084
  router.get("/resources/:resourceId/annotations/:annotationId/history", async (c) => {
18668
- const { resourceId: resourceId17, annotationId: annotationId5 } = c.req.param();
19085
+ const { resourceId: resourceId20, annotationId: annotationId6 } = c.req.param();
18669
19086
  const eventBus2 = c.get("eventBus");
18670
19087
  const correlationId = crypto.randomUUID();
18671
19088
  try {
18672
19089
  const response = await eventBusRequest(eventBus2, "browse:annotation-history-requested", {
18673
19090
  correlationId,
18674
- resourceId: resourceId(resourceId17),
18675
- annotationId: annotationId(annotationId5)
19091
+ resourceId: resourceId(resourceId20),
19092
+ annotationId: annotationId(annotationId6)
18676
19093
  }, "browse:annotation-history-result", "browse:annotation-history-failed");
18677
19094
  return c.json(response);
18678
19095
  } catch (error) {
@@ -18713,6 +19130,7 @@ function createResourcesRouter(jobQueue) {
18713
19130
  registerGetResourceLLMContext(resourcesRouter2);
18714
19131
  registerGetAnnotationLLMContext(resourcesRouter2);
18715
19132
  registerGetReferencedBy(resourcesRouter2);
19133
+ registerBindSearchStream(resourcesRouter2);
18716
19134
  registerGetResourceAnnotations(resourcesRouter2);
18717
19135
  registerCreateAnnotation(resourcesRouter2);
18718
19136
  registerGetAnnotation(resourcesRouter2);
@@ -18739,39 +19157,30 @@ function registerGetAnnotationUri(router) {
18739
19157
  const { id } = c.req.param();
18740
19158
  const query = c.req.query();
18741
19159
  const { kb } = c.get("makeMeaning");
18742
- const resourceUriOrId = query.resourceId;
18743
- if (!resourceUriOrId) {
19160
+ const resourceIdParam = query.resourceId;
19161
+ if (!resourceIdParam) {
18744
19162
  throw new HTTPException(400, {
18745
19163
  message: "resourceId query parameter is required"
18746
19164
  });
18747
19165
  }
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
19166
  if (prefersHtml(c)) {
18757
19167
  const frontendUrl = getFrontendUrl();
18758
19168
  const normalizedBase = frontendUrl.endsWith("/") ? frontendUrl.slice(0, -1) : frontendUrl;
18759
- const redirectUrl = `${normalizedBase}/annotations/${id}?resourceId=${extractedResourceId}`;
19169
+ const redirectUrl = `${normalizedBase}/annotations/${id}?resourceId=${resourceIdParam}`;
18760
19170
  return c.redirect(redirectUrl, 302);
18761
19171
  }
18762
- const projection = await AnnotationContext.getResourceAnnotations(resourceId(extractedResourceId), kb);
19172
+ const projection = await AnnotationContext.getResourceAnnotations(resourceId(resourceIdParam), kb);
18763
19173
  const annotation = projection.annotations.find((a) => a.id === id);
18764
19174
  if (!annotation) {
18765
19175
  throw new HTTPException(404, {
18766
19176
  message: "Annotation not found in resource"
18767
19177
  });
18768
19178
  }
18769
- const resource = await ResourceContext.getResourceMetadata(resourceId(extractedResourceId), kb);
19179
+ const resource = await ResourceContext.getResourceMetadata(resourceId(resourceIdParam), kb);
18770
19180
  let resolvedResource = null;
18771
19181
  const bodySource = getBodySource(annotation.body);
18772
19182
  if (annotation.motivation === "linking" && bodySource) {
18773
- const bodyDocId = bodySource.includes("://") ? uriToResourceId(bodySource) : bodySource;
18774
- resolvedResource = await ResourceContext.getResourceMetadata(resourceId(bodyDocId), kb);
19183
+ resolvedResource = await ResourceContext.getResourceMetadata(resourceId(bodySource), kb);
18775
19184
  }
18776
19185
  const response = {
18777
19186
  annotation,
@@ -18788,8 +19197,8 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
18788
19197
  const { id } = c.req.param();
18789
19198
  const query = c.req.query();
18790
19199
  const { kb } = c.get("makeMeaning");
18791
- const resourceId17 = query.resourceId;
18792
- if (!resourceId17) {
19200
+ const resourceId20 = query.resourceId;
19201
+ if (!resourceId20) {
18793
19202
  throw new HTTPException(400, {
18794
19203
  message: "resourceId query parameter is required"
18795
19204
  });
@@ -18807,7 +19216,7 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
18807
19216
  });
18808
19217
  }
18809
19218
  try {
18810
- const response = await AnnotationContext.getAnnotationContext(annotationId(id), resourceId(resourceId17), contextBefore, contextAfter, kb);
19219
+ const response = await AnnotationContext.getAnnotationContext(annotationId(id), resourceId(resourceId20), contextBefore, contextAfter, kb);
18811
19220
  return c.json(response);
18812
19221
  } catch (error) {
18813
19222
  if (error instanceof Error && error.message === "Annotation not found") {
@@ -18837,14 +19246,14 @@ operationsRouter.get("/api/annotations/:id/summary", async (c) => {
18837
19246
  const { id } = c.req.param();
18838
19247
  const query = c.req.query();
18839
19248
  const { kb, inferenceClient } = c.get("makeMeaning");
18840
- const resourceId17 = query.resourceId;
18841
- if (!resourceId17) {
19249
+ const resourceId20 = query.resourceId;
19250
+ if (!resourceId20) {
18842
19251
  throw new HTTPException(400, {
18843
19252
  message: "resourceId query parameter is required"
18844
19253
  });
18845
19254
  }
18846
19255
  try {
18847
- const response = await AnnotationContext.generateAnnotationSummary(annotationId(id), resourceId(resourceId17), kb, inferenceClient);
19256
+ const response = await AnnotationContext.generateAnnotationSummary(annotationId(id), resourceId(resourceId20), kb, inferenceClient);
18848
19257
  return c.json(response);
18849
19258
  } catch (error) {
18850
19259
  if (error instanceof Error && error.message === "Annotation not found") {
@@ -19205,6 +19614,7 @@ app.route("/", healthRouter);
19205
19614
  app.route("/", authRouter);
19206
19615
  app.route("/", statusRouter);
19207
19616
  app.route("/", adminRouter);
19617
+ app.route("/", exchangeRouter);
19208
19618
  var resourcesRouter = createResourcesRouter(makeMeaning.jobQueue);
19209
19619
  app.route("/", resourcesRouter);
19210
19620
  app.route("/", annotationsRouter);
@@ -19271,8 +19681,7 @@ app.all("/api/*", (c) => {
19271
19681
  }, 404);
19272
19682
  });
19273
19683
  var port = backendService.port || 4e3;
19274
- var nodeEnv = config.env?.NODE_ENV || "development";
19275
- if (nodeEnv !== "test") {
19684
+ if (config.env?.NODE_ENV !== "test") {
19276
19685
  serve({
19277
19686
  fetch: app.fetch,
19278
19687
  port,
@@ -19280,7 +19689,7 @@ if (nodeEnv !== "test") {
19280
19689
  }, async (info) => {
19281
19690
  logger.info("Semiont Backend ready", {
19282
19691
  url: `http://localhost:${info.port}/api`,
19283
- environment: nodeEnv
19692
+ environment: config.env?.NODE_ENV ?? "development"
19284
19693
  });
19285
19694
  try {
19286
19695
  const { JWTService: JWTService2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));