@semiont/backend 0.4.7 → 0.4.11

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,6 +268,21 @@ 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 };
273
+ return jwt.sign(payload, this.getSecret(), { expiresIn: "5m" });
274
+ }
275
+ static verifyMediaToken(token, resourceId21) {
276
+ let decoded;
277
+ try {
278
+ decoded = jwt.verify(token, this.getSecret());
279
+ } catch (error) {
280
+ if (error instanceof jwt.TokenExpiredError) throw new Error("Media token expired");
281
+ throw new Error("Invalid media token");
282
+ }
283
+ if (decoded["purpose"] !== "media") throw new Error("Invalid media token");
284
+ if (decoded["sub"] !== resourceId21) throw new Error("Media token resource mismatch");
285
+ }
271
286
  static isAllowedDomain(email) {
272
287
  const parts = email.split("@");
273
288
  if (parts.length !== 2 || !parts[0] || !parts[1]) {
@@ -9976,6 +9991,78 @@ function makeMeaningConfigFrom(config2) {
9976
9991
 
9977
9992
  // src/index.ts
9978
9993
  init_logger();
9994
+ var rootRouter = new Hono();
9995
+ rootRouter.get("/", (c) => {
9996
+ const config2 = c.get("config");
9997
+ const projectName = config2._metadata?.projectName ?? "";
9998
+ const projectVersion = config2._metadata?.projectVersion ?? "";
9999
+ const siteName = config2.site?.siteName ?? "";
10000
+ const html = `<!DOCTYPE html>
10001
+ <html lang="en">
10002
+ <head>
10003
+ <meta charset="UTF-8">
10004
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
10005
+ <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
+ <style>
10010
+ * { margin: 0; padding: 0; box-sizing: border-box; }
10011
+ body {
10012
+ background: #ffffff;
10013
+ color: #111827;
10014
+ font-family: 'Inter', -apple-system, sans-serif;
10015
+ min-height: 100vh;
10016
+ display: flex;
10017
+ flex-direction: column;
10018
+ align-items: center;
10019
+ justify-content: center;
10020
+ gap: 0.75rem;
10021
+ }
10022
+ h1 {
10023
+ font-family: 'Orbitron', sans-serif;
10024
+ font-size: 3rem;
10025
+ font-weight: 700;
10026
+ letter-spacing: 0.15em;
10027
+ color: #0066cc;
10028
+ }
10029
+ h2 {
10030
+ font-family: 'Inter', sans-serif;
10031
+ font-size: 0.9rem;
10032
+ font-weight: 500;
10033
+ letter-spacing: 0.3em;
10034
+ color: #6b7280;
10035
+ text-transform: uppercase;
10036
+ }
10037
+ .meta {
10038
+ margin-top: 1.5rem;
10039
+ font-size: 0.8rem;
10040
+ color: #9ca3af;
10041
+ display: flex;
10042
+ flex-direction: column;
10043
+ align-items: center;
10044
+ gap: 0.25rem;
10045
+ }
10046
+ a {
10047
+ color: #0066cc;
10048
+ text-decoration: none;
10049
+ font-size: 0.8rem;
10050
+ }
10051
+ a:hover { text-decoration: underline; }
10052
+ </style>
10053
+ </head>
10054
+ <body>
10055
+ <h1>SEMIONT</h1>
10056
+ <h2>knowledge base</h2>
10057
+ <div class="meta">
10058
+ ${siteName ? `<span>${siteName}</span>` : ""}
10059
+ ${projectName ? `<span>${projectName}${projectVersion ? " v" + projectVersion : ""}</span>` : ""}
10060
+ <a href="/api/health">/api/health</a>
10061
+ </div>
10062
+ </body>
10063
+ </html>`;
10064
+ return c.html(html);
10065
+ });
9979
10066
  var globalForPrisma = global;
9980
10067
  var DatabaseConnection = class {
9981
10068
  static instance = null;
@@ -11542,6 +11629,10 @@ var openapi_default = {
11542
11629
  GatherResourceStreamRequest: {
11543
11630
  type: "object",
11544
11631
  properties: {
11632
+ correlationId: {
11633
+ type: "string",
11634
+ description: "Client-generated correlation ID to thread the response back to the originating request"
11635
+ },
11545
11636
  depth: {
11546
11637
  type: "integer",
11547
11638
  minimum: 1,
@@ -11567,6 +11658,10 @@ var openapi_default = {
11567
11658
  GatherAnnotationStreamRequest: {
11568
11659
  type: "object",
11569
11660
  properties: {
11661
+ correlationId: {
11662
+ type: "string",
11663
+ description: "Client-generated correlation ID to thread the response back to the originating request"
11664
+ },
11570
11665
  contextWindow: {
11571
11666
  type: "integer",
11572
11667
  minimum: 100,
@@ -13392,6 +13487,9 @@ var openapi_default = {
13392
13487
  isAdmin: {
13393
13488
  type: "boolean"
13394
13489
  },
13490
+ isModerator: {
13491
+ type: "boolean"
13492
+ },
13395
13493
  isActive: {
13396
13494
  type: "boolean"
13397
13495
  },
@@ -13419,6 +13517,7 @@ var openapi_default = {
13419
13517
  "domain",
13420
13518
  "provider",
13421
13519
  "isAdmin",
13520
+ "isModerator",
13422
13521
  "isActive",
13423
13522
  "termsAcceptedAt",
13424
13523
  "lastLogin",
@@ -13490,6 +13589,30 @@ var openapi_default = {
13490
13589
  ],
13491
13590
  description: "W3C Web Annotation body purpose vocabulary - https://www.w3.org/TR/annotation-vocab/#motivation"
13492
13591
  },
13592
+ MediaTokenRequest: {
13593
+ type: "object",
13594
+ properties: {
13595
+ resourceId: {
13596
+ type: "string",
13597
+ description: "The resource ID to generate a media token for"
13598
+ }
13599
+ },
13600
+ required: [
13601
+ "resourceId"
13602
+ ]
13603
+ },
13604
+ MediaTokenResponse: {
13605
+ type: "object",
13606
+ properties: {
13607
+ token: {
13608
+ type: "string",
13609
+ description: "Short-lived media token for use as ?token= query parameter on resource URLs"
13610
+ }
13611
+ },
13612
+ required: [
13613
+ "token"
13614
+ ]
13615
+ },
13493
13616
  GatheredContext: {
13494
13617
  type: "object",
13495
13618
  description: "Context gathered for an annotation. Includes source document excerpts, metadata, and graph-derived context. Used by both Find (bind) and Generate (yield) flows.",
@@ -13633,6 +13756,37 @@ var openapi_default = {
13633
13756
  "annotation",
13634
13757
  "sourceResource"
13635
13758
  ]
13759
+ },
13760
+ MatchSearchStreamRequest: {
13761
+ type: "object",
13762
+ properties: {
13763
+ correlationId: {
13764
+ type: "string",
13765
+ description: "Client-generated correlation ID to thread the response back to the originating request"
13766
+ },
13767
+ referenceId: {
13768
+ type: "string",
13769
+ description: "Annotation ID of the reference to search candidates for"
13770
+ },
13771
+ context: {
13772
+ $ref: "#/components/schemas/GatheredContext",
13773
+ description: "Gathered context for the reference annotation"
13774
+ },
13775
+ limit: {
13776
+ type: "integer",
13777
+ minimum: 1,
13778
+ maximum: 20,
13779
+ description: "Maximum number of candidate results to return (default: 10)"
13780
+ },
13781
+ useSemanticScoring: {
13782
+ type: "boolean",
13783
+ description: "Enable semantic similarity scoring in addition to keyword matching (default: true)"
13784
+ }
13785
+ },
13786
+ required: [
13787
+ "referenceId",
13788
+ "context"
13789
+ ]
13636
13790
  }
13637
13791
  }
13638
13792
  }
@@ -13860,8 +14014,33 @@ var OAuthService = class {
13860
14014
  return user;
13861
14015
  }
13862
14016
  };
14017
+
14018
+ // src/middleware/auth.ts
14019
+ init_jwt();
14020
+ var MEDIA_TOKEN_PATH = /^\/api\/resources\/([^/]+)$/;
13863
14021
  var authMiddleware = async (c, next) => {
13864
14022
  const logger2 = c.get("logger");
14023
+ if (c.req.method === "GET") {
14024
+ const mediaTokenParam = c.req.query("token");
14025
+ const match = c.req.path.match(MEDIA_TOKEN_PATH);
14026
+ const resourceId21 = match?.[1];
14027
+ if (mediaTokenParam && resourceId21) {
14028
+ try {
14029
+ JWTService.verifyMediaToken(mediaTokenParam, resourceId21);
14030
+ c.set("token", mediaTokenParam);
14031
+ await next();
14032
+ return;
14033
+ } catch (error) {
14034
+ logger2.warn("Authentication failed: Invalid media token", {
14035
+ type: "auth_failed",
14036
+ reason: "invalid_media_token",
14037
+ path: c.req.path,
14038
+ error: error instanceof Error ? error.message : String(error)
14039
+ });
14040
+ return c.json({ error: "Unauthorized" }, 401);
14041
+ }
14042
+ }
14043
+ }
13865
14044
  const authHeader = c.req.header("Authorization");
13866
14045
  let tokenStr;
13867
14046
  if (authHeader?.startsWith("Bearer ")) {
@@ -14195,6 +14374,7 @@ authRouter.get("/api/users/me", authMiddleware, async (c) => {
14195
14374
  domain: user.domain,
14196
14375
  provider: user.provider,
14197
14376
  isAdmin: user.isAdmin,
14377
+ isModerator: user.isModerator,
14198
14378
  isActive: user.isActive,
14199
14379
  termsAcceptedAt: user.termsAcceptedAt?.toISOString() || null,
14200
14380
  lastLogin: user.lastLogin?.toISOString() || null,
@@ -14260,6 +14440,20 @@ authRouter.post("/api/tokens/mcp-generate", authMiddleware, async (c) => {
14260
14440
  return c.json({ error: "Failed to generate refresh token" }, 401);
14261
14441
  }
14262
14442
  });
14443
+ authRouter.post("/api/tokens/media", authMiddleware, async (c) => {
14444
+ const user = c.get("user");
14445
+ let body;
14446
+ try {
14447
+ body = await c.req.json();
14448
+ } catch {
14449
+ return c.json({ error: "Invalid request body" }, 400);
14450
+ }
14451
+ if (!body.resourceId || typeof body.resourceId !== "string") {
14452
+ return c.json({ error: "resourceId is required" }, 400);
14453
+ }
14454
+ const token = JWTService.generateMediaToken(body.resourceId, user.id);
14455
+ return c.json({ token }, 200);
14456
+ });
14263
14457
  authRouter.post("/api/users/accept-terms", authMiddleware, async (c) => {
14264
14458
  const user = c.get("user");
14265
14459
  await OAuthService.acceptTerms(userId(user.id));
@@ -14753,7 +14947,7 @@ function registerCreateResource(router) {
14753
14947
  const arrayBuffer = await file.arrayBuffer();
14754
14948
  const contentBuffer = Buffer.from(arrayBuffer);
14755
14949
  const eventBus2 = c.get("eventBus");
14756
- const resourceId20 = await ResourceOperations.createResource(
14950
+ const resourceId21 = await ResourceOperations.createResource(
14757
14951
  {
14758
14952
  name,
14759
14953
  content: contentBuffer,
@@ -14766,47 +14960,9 @@ function registerCreateResource(router) {
14766
14960
  userId(userToDid(user)),
14767
14961
  eventBus2
14768
14962
  );
14769
- return c.json({ resourceId: resourceId20 }, 202);
14963
+ return c.json({ resourceId: resourceId21 }, 202);
14770
14964
  });
14771
14965
  }
14772
-
14773
- // src/middleware/content-negotiation.ts
14774
- function prefersHtml(c) {
14775
- const acceptHeader = c.req.header("Accept") || "";
14776
- const userAgent = c.req.header("User-Agent") || "";
14777
- const acceptsHtml = acceptHeader.includes("text/html");
14778
- const acceptsJson = acceptHeader.includes("application/json") || acceptHeader.includes("application/ld+json");
14779
- if (acceptsJson && !acceptsHtml) {
14780
- return false;
14781
- }
14782
- if (acceptsHtml && acceptsJson) {
14783
- const htmlIndex = acceptHeader.indexOf("text/html");
14784
- const jsonIndex = Math.min(
14785
- acceptHeader.indexOf("application/json") >= 0 ? acceptHeader.indexOf("application/json") : Infinity,
14786
- acceptHeader.indexOf("application/ld+json") >= 0 ? acceptHeader.indexOf("application/ld+json") : Infinity
14787
- );
14788
- if (htmlIndex < jsonIndex) {
14789
- return true;
14790
- } else {
14791
- return false;
14792
- }
14793
- }
14794
- const isBrowser = /Mozilla|Chrome|Safari|Edge|Firefox|Opera/.test(userAgent);
14795
- if (isBrowser && !acceptHeader && !userAgent.includes("curl")) {
14796
- return true;
14797
- }
14798
- if (isBrowser && acceptsHtml && !userAgent.includes("curl")) {
14799
- return true;
14800
- }
14801
- return acceptsHtml && !acceptsJson;
14802
- }
14803
- function getFrontendUrl() {
14804
- const frontendUrl = process.env.FRONTEND_URL;
14805
- if (!frontendUrl) {
14806
- throw new Error("FRONTEND_URL environment variable is required for content negotiation");
14807
- }
14808
- return frontendUrl;
14809
- }
14810
14966
  var SSE_STREAM_CONNECTED = "stream-connected";
14811
14967
  function getBodySource(body) {
14812
14968
  if (Array.isArray(body)) {
@@ -14966,13 +15122,6 @@ function registerGetResourceUri(router) {
14966
15122
  });
14967
15123
  router.get("/resources/:id", async (c) => {
14968
15124
  const { id } = c.req.param();
14969
- const view = c.req.query("view");
14970
- if (view === "semiont") {
14971
- const frontendUrl = getFrontendUrl();
14972
- const normalizedBase = frontendUrl.endsWith("/") ? frontendUrl.slice(0, -1) : frontendUrl;
14973
- const redirectUrl = `${normalizedBase}/know/resource/${id}`;
14974
- return c.redirect(redirectUrl, 302);
14975
- }
14976
15125
  const acceptHeader = c.req.header("Accept") || "application/ld+json";
14977
15126
  if (acceptHeader.includes("text/") || acceptHeader.includes("image/") || acceptHeader.includes("application/pdf")) {
14978
15127
  const { knowledgeSystem: { kb } } = c.get("makeMeaning");
@@ -15126,7 +15275,7 @@ var nanoid = (size = 21) => {
15126
15275
  async function writeTypedSSE(stream, options) {
15127
15276
  await stream.writeSSE({
15128
15277
  event: options.event,
15129
- data: options.data,
15278
+ data: JSON.stringify(options.data),
15130
15279
  id: options.id
15131
15280
  });
15132
15281
  }
@@ -15210,16 +15359,13 @@ function registerAnnotateReferencesStream(router, jobQueue) {
15210
15359
  logger2.info("[EventBus] Received mark:progress event", { progress, resourceId: id });
15211
15360
  try {
15212
15361
  await writeTypedSSE(stream, {
15213
- data: JSON.stringify({
15362
+ data: {
15214
15363
  status: progress.status || "scanning",
15215
15364
  resourceId: resourceId(id),
15216
15365
  currentEntityType: progress.currentEntityType,
15217
- totalEntityTypes: entityTypes.length,
15218
- processedEntityTypes: progress.completedEntityTypes?.length || 0,
15219
- foundCount: progress.completedEntityTypes?.reduce((sum, et) => sum + et.foundCount, 0),
15220
- message: progress.message || (progress.currentEntityType ? `Scanning for ${progress.currentEntityType}...` : "Processing..."),
15221
- percentage: progress.percentage
15222
- }),
15366
+ percentage: progress.percentage,
15367
+ message: progress.message || (progress.currentEntityType ? `Scanning for ${progress.currentEntityType}...` : "Processing...")
15368
+ },
15223
15369
  event: "mark:progress",
15224
15370
  id: String(Date.now())
15225
15371
  });
@@ -15237,15 +15383,13 @@ function registerAnnotateReferencesStream(router, jobQueue) {
15237
15383
  try {
15238
15384
  const result = event.payload.result;
15239
15385
  await writeTypedSSE(stream, {
15240
- data: JSON.stringify({
15386
+ data: {
15241
15387
  motivation: "linking",
15242
15388
  status: "complete",
15243
15389
  resourceId: resourceId(id),
15244
- totalEntityTypes: entityTypes.length,
15245
- processedEntityTypes: entityTypes.length,
15246
15390
  foundCount: result?.totalFound,
15247
15391
  message: result?.totalFound !== void 0 ? `Detection complete! Found ${result.totalFound} entities` : "Detection complete!"
15248
- }),
15392
+ },
15249
15393
  event: "mark:assist-finished",
15250
15394
  id: String(Date.now())
15251
15395
  });
@@ -15262,13 +15406,10 @@ function registerAnnotateReferencesStream(router, jobQueue) {
15262
15406
  logger2.info("Detection failed", { error: event.payload.error });
15263
15407
  try {
15264
15408
  await writeTypedSSE(stream, {
15265
- data: JSON.stringify({
15266
- status: "error",
15409
+ data: {
15267
15410
  resourceId: resourceId(id),
15268
- totalEntityTypes: entityTypes.length,
15269
- processedEntityTypes: 0,
15270
15411
  message: event.payload.error || "Detection failed"
15271
- }),
15412
+ },
15272
15413
  event: "mark:assist-failed",
15273
15414
  id: String(Date.now())
15274
15415
  });
@@ -15398,11 +15539,11 @@ function registerAnnotateHighlightsStream(router, jobQueue) {
15398
15539
  logger2.info("Detection started");
15399
15540
  try {
15400
15541
  await writeTypedSSE(stream, {
15401
- data: JSON.stringify({
15542
+ data: {
15402
15543
  status: "started",
15403
15544
  resourceId: resourceId(id),
15404
15545
  message: "Starting detection..."
15405
- }),
15546
+ },
15406
15547
  event: "mark:progress",
15407
15548
  id: String(Date.now())
15408
15549
  });
@@ -15418,13 +15559,12 @@ function registerAnnotateHighlightsStream(router, jobQueue) {
15418
15559
  logger2.info("Detection progress", { progress });
15419
15560
  try {
15420
15561
  await writeTypedSSE(stream, {
15421
- data: JSON.stringify({
15562
+ data: {
15422
15563
  status: progress.status || "analyzing",
15423
15564
  resourceId: resourceId(id),
15424
- stage: progress.status === "analyzing" || progress.status === "creating" ? progress.status : void 0,
15425
15565
  percentage: progress.percentage,
15426
15566
  message: progress.message || "Processing..."
15427
- }),
15567
+ },
15428
15568
  event: "mark:progress",
15429
15569
  id: String(Date.now())
15430
15570
  });
@@ -15442,7 +15582,7 @@ function registerAnnotateHighlightsStream(router, jobQueue) {
15442
15582
  try {
15443
15583
  const result = event.payload.result;
15444
15584
  await writeTypedSSE(stream, {
15445
- data: JSON.stringify({
15585
+ data: {
15446
15586
  motivation: "highlighting",
15447
15587
  status: "complete",
15448
15588
  resourceId: resourceId(id),
@@ -15450,7 +15590,7 @@ function registerAnnotateHighlightsStream(router, jobQueue) {
15450
15590
  foundCount: result?.highlightsFound,
15451
15591
  createdCount: result?.highlightsCreated,
15452
15592
  message: result?.highlightsCreated !== void 0 ? `Complete! Created ${result.highlightsCreated} highlights` : "Highlight detection complete!"
15453
- }),
15593
+ },
15454
15594
  event: "mark:assist-finished",
15455
15595
  id: String(Date.now())
15456
15596
  });
@@ -15467,11 +15607,10 @@ function registerAnnotateHighlightsStream(router, jobQueue) {
15467
15607
  logger2.info("Detection failed", { error: event.payload.error });
15468
15608
  try {
15469
15609
  await writeTypedSSE(stream, {
15470
- data: JSON.stringify({
15471
- status: "error",
15610
+ data: {
15472
15611
  resourceId: resourceId(id),
15473
15612
  message: event.payload.error || "Highlight detection failed"
15474
- }),
15613
+ },
15475
15614
  event: "mark:assist-failed",
15476
15615
  id: String(Date.now())
15477
15616
  });
@@ -15601,11 +15740,11 @@ function registerAnnotateAssessmentsStream(router, jobQueue) {
15601
15740
  logger2.info("Detection started");
15602
15741
  try {
15603
15742
  await writeTypedSSE(stream, {
15604
- data: JSON.stringify({
15743
+ data: {
15605
15744
  status: "started",
15606
15745
  resourceId: resourceId(id),
15607
15746
  message: "Starting detection..."
15608
- }),
15747
+ },
15609
15748
  event: "mark:progress",
15610
15749
  id: String(Date.now())
15611
15750
  });
@@ -15621,13 +15760,12 @@ function registerAnnotateAssessmentsStream(router, jobQueue) {
15621
15760
  logger2.info("Detection progress", { progress });
15622
15761
  try {
15623
15762
  await writeTypedSSE(stream, {
15624
- data: JSON.stringify({
15763
+ data: {
15625
15764
  status: progress.status || "analyzing",
15626
15765
  resourceId: resourceId(id),
15627
- stage: progress.status === "analyzing" || progress.status === "creating" ? progress.status : void 0,
15628
15766
  percentage: progress.percentage,
15629
15767
  message: progress.message || "Processing..."
15630
- }),
15768
+ },
15631
15769
  event: "mark:progress",
15632
15770
  id: String(Date.now())
15633
15771
  });
@@ -15649,7 +15787,7 @@ function registerAnnotateAssessmentsStream(router, jobQueue) {
15649
15787
  try {
15650
15788
  const result = event.payload.result;
15651
15789
  await writeTypedSSE(stream, {
15652
- data: JSON.stringify({
15790
+ data: {
15653
15791
  motivation: "assessing",
15654
15792
  status: "complete",
15655
15793
  resourceId: resourceId(id),
@@ -15657,7 +15795,7 @@ function registerAnnotateAssessmentsStream(router, jobQueue) {
15657
15795
  foundCount: result?.assessmentsFound,
15658
15796
  createdCount: result?.assessmentsCreated,
15659
15797
  message: result?.assessmentsCreated !== void 0 ? `Complete! Created ${result.assessmentsCreated} assessments` : "Assessment detection complete!"
15660
- }),
15798
+ },
15661
15799
  event: "mark:assist-finished",
15662
15800
  id: String(Date.now())
15663
15801
  });
@@ -15674,11 +15812,10 @@ function registerAnnotateAssessmentsStream(router, jobQueue) {
15674
15812
  logger2.info("Detection failed", { error: event.payload.error });
15675
15813
  try {
15676
15814
  await writeTypedSSE(stream, {
15677
- data: JSON.stringify({
15678
- status: "error",
15815
+ data: {
15679
15816
  resourceId: resourceId(id),
15680
15817
  message: event.payload.error || "Assessment detection failed"
15681
- }),
15818
+ },
15682
15819
  event: "mark:assist-failed",
15683
15820
  id: String(Date.now())
15684
15821
  });
@@ -15808,11 +15945,11 @@ function registerAnnotateCommentsStream(router, jobQueue) {
15808
15945
  logger2.info("Detection started");
15809
15946
  try {
15810
15947
  await writeTypedSSE(stream, {
15811
- data: JSON.stringify({
15948
+ data: {
15812
15949
  status: "started",
15813
15950
  resourceId: resourceId(id),
15814
15951
  message: "Starting detection..."
15815
- }),
15952
+ },
15816
15953
  event: "mark:progress",
15817
15954
  id: String(Date.now())
15818
15955
  });
@@ -15828,13 +15965,12 @@ function registerAnnotateCommentsStream(router, jobQueue) {
15828
15965
  logger2.info("Detection progress", { progress });
15829
15966
  try {
15830
15967
  await writeTypedSSE(stream, {
15831
- data: JSON.stringify({
15968
+ data: {
15832
15969
  status: progress.status || "analyzing",
15833
15970
  resourceId: resourceId(id),
15834
- stage: progress.status === "analyzing" || progress.status === "creating" ? progress.status : void 0,
15835
15971
  percentage: progress.percentage,
15836
15972
  message: progress.message || "Processing..."
15837
- }),
15973
+ },
15838
15974
  event: "mark:progress",
15839
15975
  id: String(Date.now())
15840
15976
  });
@@ -15852,7 +15988,7 @@ function registerAnnotateCommentsStream(router, jobQueue) {
15852
15988
  try {
15853
15989
  const result = event.payload.result;
15854
15990
  await writeTypedSSE(stream, {
15855
- data: JSON.stringify({
15991
+ data: {
15856
15992
  motivation: "commenting",
15857
15993
  status: "complete",
15858
15994
  resourceId: resourceId(id),
@@ -15860,7 +15996,7 @@ function registerAnnotateCommentsStream(router, jobQueue) {
15860
15996
  foundCount: result?.commentsFound,
15861
15997
  createdCount: result?.commentsCreated,
15862
15998
  message: result?.commentsCreated !== void 0 ? `Complete! Created ${result.commentsCreated} comments` : "Comment detection complete!"
15863
- }),
15999
+ },
15864
16000
  event: "mark:assist-finished",
15865
16001
  id: String(Date.now())
15866
16002
  });
@@ -15877,11 +16013,10 @@ function registerAnnotateCommentsStream(router, jobQueue) {
15877
16013
  logger2.info("Detection failed", { error: event.payload.error });
15878
16014
  try {
15879
16015
  await writeTypedSSE(stream, {
15880
- data: JSON.stringify({
15881
- status: "error",
16016
+ data: {
15882
16017
  resourceId: resourceId(id),
15883
16018
  message: event.payload.error || "Comment detection failed"
15884
- }),
16019
+ },
15885
16020
  event: "mark:assist-failed",
15886
16021
  id: String(Date.now())
15887
16022
  });
@@ -16185,12 +16320,12 @@ function registerAnnotateTagsStream(router, jobQueue) {
16185
16320
  logger2.info("Detection started");
16186
16321
  try {
16187
16322
  await writeTypedSSE(stream, {
16188
- data: JSON.stringify({
16323
+ data: {
16189
16324
  status: "started",
16190
16325
  resourceId: resourceId(id),
16191
16326
  totalCategories: categories.length,
16192
16327
  message: "Starting detection..."
16193
- }),
16328
+ },
16194
16329
  event: "mark:progress",
16195
16330
  id: String(Date.now())
16196
16331
  });
@@ -16206,16 +16341,15 @@ function registerAnnotateTagsStream(router, jobQueue) {
16206
16341
  logger2.info("Detection progress", { progress });
16207
16342
  try {
16208
16343
  await writeTypedSSE(stream, {
16209
- data: JSON.stringify({
16344
+ data: {
16210
16345
  status: progress.status || "analyzing",
16211
16346
  resourceId: resourceId(id),
16212
- stage: progress.status === "analyzing" || progress.status === "creating" ? progress.status : void 0,
16213
16347
  percentage: progress.percentage,
16214
16348
  currentCategory: progress.currentCategory,
16215
16349
  processedCategories: progress.processedCategories,
16216
16350
  totalCategories: progress.totalCategories,
16217
16351
  message: progress.message || "Processing..."
16218
- }),
16352
+ },
16219
16353
  event: "mark:progress",
16220
16354
  id: String(Date.now())
16221
16355
  });
@@ -16233,7 +16367,7 @@ function registerAnnotateTagsStream(router, jobQueue) {
16233
16367
  try {
16234
16368
  const result = event.payload.result;
16235
16369
  await writeTypedSSE(stream, {
16236
- data: JSON.stringify({
16370
+ data: {
16237
16371
  motivation: "tagging",
16238
16372
  status: "complete",
16239
16373
  resourceId: resourceId(id),
@@ -16242,7 +16376,7 @@ function registerAnnotateTagsStream(router, jobQueue) {
16242
16376
  createdCount: result?.tagsCreated,
16243
16377
  byCategory: result?.byCategory,
16244
16378
  message: result?.tagsCreated !== void 0 ? `Complete! Created ${result.tagsCreated} tags` : "Tag detection complete!"
16245
- }),
16379
+ },
16246
16380
  event: "mark:assist-finished",
16247
16381
  id: String(Date.now())
16248
16382
  });
@@ -16259,11 +16393,10 @@ function registerAnnotateTagsStream(router, jobQueue) {
16259
16393
  logger2.info("Detection failed", { error: event.payload.error });
16260
16394
  try {
16261
16395
  await writeTypedSSE(stream, {
16262
- data: JSON.stringify({
16263
- status: "error",
16396
+ data: {
16264
16397
  resourceId: resourceId(id),
16265
16398
  message: event.payload.error || "Tag detection failed"
16266
- }),
16399
+ },
16267
16400
  event: "mark:assist-failed",
16268
16401
  id: String(Date.now())
16269
16402
  });
@@ -16337,118 +16470,122 @@ function registerGetReferencedBy(router) {
16337
16470
  });
16338
16471
  }
16339
16472
  init_logger();
16340
- function registerBindSearchStream(router) {
16341
- router.post("/resources/:id/bind-search-stream", async (c) => {
16342
- const { id } = c.req.param();
16343
- const logger2 = getLogger().child({
16344
- component: "bind-search-stream",
16345
- resourceId: id
16346
- });
16347
- const user = c.get("user");
16348
- if (!user) {
16349
- throw new HTTPException(401, { message: "Authentication required" });
16350
- }
16351
- const body = await c.req.json();
16352
- const { referenceId, context, limit, useSemanticScoring } = body;
16353
- if (!referenceId || !context) {
16354
- throw new HTTPException(400, { message: "referenceId and context are required" });
16355
- }
16356
- const eventBus2 = c.get("eventBus");
16357
- const { knowledgeSystem: { kb } } = c.get("makeMeaning");
16358
- const resource = await ResourceContext.getResourceMetadata(resourceId(id), kb);
16359
- if (!resource) {
16360
- throw new HTTPException(404, { message: "Resource not found" });
16361
- }
16362
- const correlationId = crypto.randomUUID();
16363
- logger2.info("Starting bind search stream", { referenceId, correlationId });
16364
- c.header("X-Accel-Buffering", "no");
16365
- c.header("Cache-Control", "no-cache, no-transform");
16366
- return streamSSE(c, async (stream) => {
16367
- let isStreamClosed = false;
16368
- const subscriptions = [];
16369
- let closeStreamCallback = null;
16370
- const streamPromise = new Promise((resolve) => {
16371
- closeStreamCallback = resolve;
16473
+ function registerMatchSearchStream(router) {
16474
+ router.post(
16475
+ "/resources/:id/match-search-stream",
16476
+ validateRequestBody("MatchSearchStreamRequest"),
16477
+ async (c) => {
16478
+ const { id } = c.req.param();
16479
+ const logger2 = getLogger().child({
16480
+ component: "match-search-stream",
16481
+ resourceId: id
16372
16482
  });
16373
- const cleanup = () => {
16374
- if (isStreamClosed) return;
16375
- isStreamClosed = true;
16376
- subscriptions.forEach((sub) => sub.unsubscribe());
16377
- if (closeStreamCallback) closeStreamCallback();
16378
- };
16379
- try {
16380
- subscriptions.push(
16381
- eventBus2.get("match:search-results").subscribe(async (event) => {
16382
- if (event.correlationId !== correlationId) return;
16383
- if (isStreamClosed) return;
16384
- logger2.info("Bind search completed", {
16385
- referenceId,
16386
- resultCount: event.results.length
16387
- });
16388
- try {
16389
- await writeTypedSSE(stream, {
16390
- data: JSON.stringify({
16391
- referenceId: event.referenceId,
16392
- results: event.results
16393
- }),
16394
- event: "match:search-results",
16395
- id: String(Date.now())
16396
- });
16397
- } catch (error) {
16398
- logger2.warn("Client disconnected during results");
16399
- }
16400
- cleanup();
16401
- })
16402
- );
16403
- subscriptions.push(
16404
- eventBus2.get("match:search-failed").subscribe(async (event) => {
16405
- if (event.correlationId !== correlationId) return;
16406
- if (isStreamClosed) return;
16407
- logger2.error("Bind search failed", { referenceId, error: event.error });
16408
- try {
16409
- await writeTypedSSE(stream, {
16410
- data: JSON.stringify({
16411
- referenceId: event.referenceId,
16412
- error: event.error.message
16413
- }),
16414
- event: "match:search-failed",
16415
- id: String(Date.now())
16416
- });
16417
- } catch (error) {
16418
- logger2.warn("Client disconnected during error");
16419
- }
16420
- cleanup();
16421
- })
16422
- );
16423
- eventBus2.get("match:search-requested").next({
16424
- correlationId,
16425
- referenceId,
16426
- context,
16427
- limit,
16428
- useSemanticScoring
16429
- });
16430
- c.req.raw.signal.addEventListener("abort", () => {
16431
- logger2.info("Client disconnected from bind search stream");
16432
- cleanup();
16483
+ const user = c.get("user");
16484
+ if (!user) {
16485
+ throw new HTTPException(401, { message: "Authentication required" });
16486
+ }
16487
+ const body = c.get("validatedBody");
16488
+ const { referenceId, context, limit, useSemanticScoring } = body;
16489
+ const correlationId = body.correlationId ?? crypto.randomUUID();
16490
+ const eventBus2 = c.get("eventBus");
16491
+ const { knowledgeSystem: { kb } } = c.get("makeMeaning");
16492
+ const resource = await ResourceContext.getResourceMetadata(resourceId(id), kb);
16493
+ if (!resource) {
16494
+ throw new HTTPException(404, { message: "Resource not found" });
16495
+ }
16496
+ logger2.info("Starting match search stream", { referenceId, correlationId });
16497
+ c.header("X-Accel-Buffering", "no");
16498
+ c.header("Cache-Control", "no-cache, no-transform");
16499
+ return streamSSE(c, async (stream) => {
16500
+ let isStreamClosed = false;
16501
+ const subscriptions = [];
16502
+ let closeStreamCallback = null;
16503
+ const streamPromise = new Promise((resolve) => {
16504
+ closeStreamCallback = resolve;
16433
16505
  });
16434
- } catch (error) {
16506
+ const cleanup = () => {
16507
+ if (isStreamClosed) return;
16508
+ isStreamClosed = true;
16509
+ subscriptions.forEach((sub) => sub.unsubscribe());
16510
+ if (closeStreamCallback) closeStreamCallback();
16511
+ };
16435
16512
  try {
16436
- await writeTypedSSE(stream, {
16437
- data: JSON.stringify({
16438
- referenceId,
16439
- error: error instanceof Error ? error.message : "Search failed"
16440
- }),
16441
- event: "match:search-failed",
16442
- id: String(Date.now())
16513
+ subscriptions.push(
16514
+ eventBus2.get("match:search-results").subscribe(async (event) => {
16515
+ if (event.correlationId !== correlationId) return;
16516
+ if (isStreamClosed) return;
16517
+ logger2.info("Match search completed", {
16518
+ referenceId,
16519
+ resultCount: event.response.length
16520
+ });
16521
+ try {
16522
+ await writeTypedSSE(stream, {
16523
+ data: {
16524
+ correlationId: event.correlationId,
16525
+ referenceId: event.referenceId,
16526
+ response: event.response
16527
+ },
16528
+ event: "match:search-results",
16529
+ id: String(Date.now())
16530
+ });
16531
+ } catch {
16532
+ logger2.warn("Client disconnected during results");
16533
+ }
16534
+ cleanup();
16535
+ })
16536
+ );
16537
+ subscriptions.push(
16538
+ eventBus2.get("match:search-failed").subscribe(async (event) => {
16539
+ if (event.correlationId !== correlationId) return;
16540
+ if (isStreamClosed) return;
16541
+ logger2.error("Match search failed", { referenceId, error: event.error });
16542
+ try {
16543
+ await writeTypedSSE(stream, {
16544
+ data: {
16545
+ correlationId: event.correlationId,
16546
+ referenceId: event.referenceId,
16547
+ error: event.error
16548
+ },
16549
+ event: "match:search-failed",
16550
+ id: String(Date.now())
16551
+ });
16552
+ } catch {
16553
+ logger2.warn("Client disconnected during error");
16554
+ }
16555
+ cleanup();
16556
+ })
16557
+ );
16558
+ eventBus2.get("match:search-requested").next({
16559
+ correlationId,
16560
+ referenceId,
16561
+ context,
16562
+ limit,
16563
+ useSemanticScoring
16564
+ });
16565
+ c.req.raw.signal.addEventListener("abort", () => {
16566
+ logger2.info("Client disconnected from match search stream");
16567
+ cleanup();
16443
16568
  });
16444
- } catch (sseError) {
16445
- logger2.warn("Could not send error to client");
16569
+ } catch (error) {
16570
+ try {
16571
+ await writeTypedSSE(stream, {
16572
+ data: {
16573
+ correlationId,
16574
+ referenceId,
16575
+ error: error instanceof Error ? error.message : "Search failed"
16576
+ },
16577
+ event: "match:search-failed",
16578
+ id: String(Date.now())
16579
+ });
16580
+ } catch {
16581
+ logger2.warn("Could not send error to client");
16582
+ }
16583
+ cleanup();
16446
16584
  }
16447
- cleanup();
16448
- }
16449
- return streamPromise;
16450
- });
16451
- });
16585
+ return streamPromise;
16586
+ });
16587
+ }
16588
+ );
16452
16589
  }
16453
16590
  function registerTokenRoutes(router) {
16454
16591
  router.get("/api/clone-tokens/:token", async (c) => {
@@ -16928,13 +17065,13 @@ function registerYieldResourceStream(router, jobQueue) {
16928
17065
  logger2.info("Generation started");
16929
17066
  try {
16930
17067
  await writeTypedSSE(stream, {
16931
- data: JSON.stringify({
17068
+ data: {
16932
17069
  status: "started",
16933
17070
  referenceId: reference.id,
16934
17071
  resourceName,
16935
17072
  percentage: 0,
16936
17073
  message: "Starting..."
16937
- }),
17074
+ },
16938
17075
  event: "yield:progress",
16939
17076
  id: String(Date.now())
16940
17077
  });
@@ -16950,13 +17087,13 @@ function registerYieldResourceStream(router, jobQueue) {
16950
17087
  logger2.info("Generation progress", { progress });
16951
17088
  try {
16952
17089
  await writeTypedSSE(stream, {
16953
- data: JSON.stringify({
17090
+ data: {
16954
17091
  status: progress.status,
16955
17092
  referenceId: reference.id,
16956
17093
  resourceName,
16957
17094
  percentage: progress.percentage || 0,
16958
17095
  message: progress.message || `${progress.status}...`
16959
- }),
17096
+ },
16960
17097
  event: "yield:progress",
16961
17098
  id: String(Date.now())
16962
17099
  });
@@ -16972,7 +17109,7 @@ function registerYieldResourceStream(router, jobQueue) {
16972
17109
  logger2.info("Generation completed");
16973
17110
  try {
16974
17111
  await writeTypedSSE(stream, {
16975
- data: JSON.stringify({
17112
+ data: {
16976
17113
  status: "complete",
16977
17114
  referenceId: reference.id,
16978
17115
  resourceName,
@@ -16980,7 +17117,7 @@ function registerYieldResourceStream(router, jobQueue) {
16980
17117
  sourceResourceId: resourceIdParam,
16981
17118
  percentage: 100,
16982
17119
  message: "Draft resource created! Ready for review."
16983
- }),
17120
+ },
16984
17121
  event: "yield:finished",
16985
17122
  id: String(Date.now())
16986
17123
  });
@@ -17012,12 +17149,12 @@ function registerYieldResourceStream(router, jobQueue) {
17012
17149
  } catch (error) {
17013
17150
  try {
17014
17151
  await writeTypedSSE(stream, {
17015
- data: JSON.stringify({
17152
+ data: {
17016
17153
  status: "error",
17017
17154
  referenceId: reference.id,
17018
17155
  percentage: 0,
17019
17156
  message: error instanceof Error ? error.message : "Generation failed"
17020
- }),
17157
+ },
17021
17158
  event: "yield:failed",
17022
17159
  id: String(Date.now())
17023
17160
  });
@@ -17050,12 +17187,7 @@ function registerGatherAnnotationStream(router) {
17050
17187
  if (!user) {
17051
17188
  throw new HTTPException(401, { message: "Authentication required" });
17052
17189
  }
17053
- eventBus2.get("gather:requested").next({
17054
- annotationId: annotationId(annotationIdParam),
17055
- resourceId: resourceId(resourceIdParam),
17056
- options: { includeSourceContext: true, includeTargetContext: true, contextWindow }
17057
- });
17058
- logger2.info("Emitted gather:requested", { annotationId: annotationIdParam, contextWindow });
17190
+ const correlationId = body.correlationId ?? crypto.randomUUID();
17059
17191
  c.header("X-Accel-Buffering", "no");
17060
17192
  c.header("Cache-Control", "no-cache, no-transform");
17061
17193
  return streamSSE(c, async (stream) => {
@@ -17075,7 +17207,7 @@ function registerGatherAnnotationStream(router) {
17075
17207
  if (isStreamClosed) return;
17076
17208
  try {
17077
17209
  await writeTypedSSE(stream, {
17078
- data: JSON.stringify({ message: event.message, percentage: event.percentage }),
17210
+ data: { message: event.message, percentage: event.percentage },
17079
17211
  event: "gather:annotation-progress",
17080
17212
  id: String(Date.now())
17081
17213
  });
@@ -17086,11 +17218,11 @@ function registerGatherAnnotationStream(router) {
17086
17218
  );
17087
17219
  subscriptions.push(
17088
17220
  eventBus2.get("gather:complete").subscribe(async (event) => {
17089
- if (event.annotationId !== annotationIdParam) return;
17221
+ if (event.correlationId !== correlationId) return;
17090
17222
  if (isStreamClosed) return;
17091
17223
  try {
17092
17224
  await writeTypedSSE(stream, {
17093
- data: JSON.stringify({ annotationId: event.annotationId, response: event.response }),
17225
+ data: { correlationId: event.correlationId, annotationId: event.annotationId, response: event.response },
17094
17226
  event: "gather:annotation-finished",
17095
17227
  id: String(Date.now())
17096
17228
  });
@@ -17101,11 +17233,11 @@ function registerGatherAnnotationStream(router) {
17101
17233
  );
17102
17234
  subscriptions.push(
17103
17235
  eventBus2.get("gather:failed").subscribe(async (event) => {
17104
- if (event.annotationId !== annotationIdParam) return;
17236
+ if (event.correlationId !== correlationId) return;
17105
17237
  if (isStreamClosed) return;
17106
17238
  try {
17107
17239
  await writeTypedSSE(stream, {
17108
- data: JSON.stringify({ annotationId: event.annotationId, error: event.error }),
17240
+ data: { correlationId: event.correlationId, annotationId: event.annotationId, error: event.error },
17109
17241
  event: "gather:failed",
17110
17242
  id: String(Date.now())
17111
17243
  });
@@ -17114,6 +17246,13 @@ function registerGatherAnnotationStream(router) {
17114
17246
  cleanup();
17115
17247
  })
17116
17248
  );
17249
+ eventBus2.get("gather:requested").next({
17250
+ correlationId,
17251
+ annotationId: annotationId(annotationIdParam),
17252
+ resourceId: resourceId(resourceIdParam),
17253
+ options: { includeSourceContext: true, includeTargetContext: true, contextWindow }
17254
+ });
17255
+ logger2.info("Emitted gather:requested", { annotationId: annotationIdParam, correlationId, contextWindow });
17117
17256
  keepAliveInterval = setInterval(async () => {
17118
17257
  if (isStreamClosed) {
17119
17258
  clearInterval(keepAliveInterval);
@@ -17137,14 +17276,14 @@ function registerGatherAnnotationStream(router) {
17137
17276
  }
17138
17277
  function registerGetAnnotationHistory(router) {
17139
17278
  router.get("/resources/:resourceId/annotations/:annotationId/history", async (c) => {
17140
- const { resourceId: resourceId20, annotationId: annotationId6 } = c.req.param();
17279
+ const { resourceId: resourceId21, annotationId: annotationId7 } = c.req.param();
17141
17280
  const eventBus2 = c.get("eventBus");
17142
17281
  const correlationId = crypto.randomUUID();
17143
17282
  try {
17144
17283
  const response = await eventBusRequest(
17145
17284
  eventBus2,
17146
17285
  "browse:annotation-history-requested",
17147
- { correlationId, resourceId: resourceId(resourceId20), annotationId: annotationId(annotationId6) },
17286
+ { correlationId, resourceId: resourceId(resourceId21), annotationId: annotationId(annotationId7) },
17148
17287
  "browse:annotation-history-result",
17149
17288
  "browse:annotation-history-failed"
17150
17289
  );
@@ -17178,7 +17317,7 @@ function createResourcesRouter(jobQueue) {
17178
17317
  registerAnnotateCommentsStream(resourcesRouter2, jobQueue);
17179
17318
  registerAnnotateTagsStream(resourcesRouter2, jobQueue);
17180
17319
  registerGetReferencedBy(resourcesRouter2);
17181
- registerBindSearchStream(resourcesRouter2);
17320
+ registerMatchSearchStream(resourcesRouter2);
17182
17321
  registerGetResourceAnnotations(resourcesRouter2);
17183
17322
  registerCreateAnnotation(resourcesRouter2);
17184
17323
  registerGetAnnotation(resourcesRouter2);
@@ -17208,12 +17347,6 @@ function registerGetAnnotationUri(router) {
17208
17347
  if (!resourceIdParam) {
17209
17348
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17210
17349
  }
17211
- if (prefersHtml(c)) {
17212
- const frontendUrl = getFrontendUrl();
17213
- const normalizedBase = frontendUrl.endsWith("/") ? frontendUrl.slice(0, -1) : frontendUrl;
17214
- const redirectUrl = `${normalizedBase}/annotations/${id}?resourceId=${resourceIdParam}`;
17215
- return c.redirect(redirectUrl, 302);
17216
- }
17217
17350
  const projection = await AnnotationContext.getResourceAnnotations(resourceId(resourceIdParam), kb);
17218
17351
  const annotation = projection.annotations.find((a) => a.id === id);
17219
17352
  if (!annotation) {
@@ -17239,8 +17372,8 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17239
17372
  const { id } = c.req.param();
17240
17373
  const query = c.req.query();
17241
17374
  const { knowledgeSystem: { kb } } = c.get("makeMeaning");
17242
- const resourceId20 = query.resourceId;
17243
- if (!resourceId20) {
17375
+ const resourceId21 = query.resourceId;
17376
+ if (!resourceId21) {
17244
17377
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17245
17378
  }
17246
17379
  const contextBefore = query.contextBefore ? Number(query.contextBefore) : 100;
@@ -17254,7 +17387,7 @@ operationsRouter.get("/api/annotations/:id/context", async (c) => {
17254
17387
  try {
17255
17388
  const response = await AnnotationContext.getAnnotationContext(
17256
17389
  annotationId(id),
17257
- resourceId(resourceId20),
17390
+ resourceId(resourceId21),
17258
17391
  contextBefore,
17259
17392
  contextAfter,
17260
17393
  kb
@@ -17280,14 +17413,14 @@ operationsRouter.get("/api/annotations/:id/summary", async (c) => {
17280
17413
  const { id } = c.req.param();
17281
17414
  const query = c.req.query();
17282
17415
  const { knowledgeSystem: { gatherer } } = c.get("makeMeaning");
17283
- const resourceId20 = query.resourceId;
17284
- if (!resourceId20) {
17416
+ const resourceId21 = query.resourceId;
17417
+ if (!resourceId21) {
17285
17418
  throw new HTTPException(400, { message: "resourceId query parameter is required" });
17286
17419
  }
17287
17420
  try {
17288
17421
  const response = await gatherer.generateAnnotationSummary(
17289
17422
  annotationId(id),
17290
- resourceId(resourceId20)
17423
+ resourceId(resourceId21)
17291
17424
  );
17292
17425
  return c.json(response);
17293
17426
  } catch (error) {
@@ -17774,6 +17907,7 @@ app.use("*", async (c, next) => {
17774
17907
  c.set("makeMeaning", makeMeaning);
17775
17908
  await next();
17776
17909
  });
17910
+ app.route("/", rootRouter);
17777
17911
  app.route("/", healthRouter);
17778
17912
  app.route("/", authRouter);
17779
17913
  app.route("/", statusRouter);