@gmickel/gno 1.18.0 → 1.19.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.
Files changed (129) hide show
  1. package/README.md +5 -3
  2. package/assets/skill/SKILL.md +27 -0
  3. package/package.json +2 -1
  4. package/spec/AGENTS.md +83 -0
  5. package/spec/CLAUDE.md +83 -0
  6. package/spec/bench-fixture.schema.json +137 -0
  7. package/spec/cli.md +2894 -0
  8. package/spec/db/schema.sql +442 -0
  9. package/spec/evals-agentic.md +510 -0
  10. package/spec/evals.md +1106 -0
  11. package/spec/mcp.md +2229 -0
  12. package/spec/output-schemas/activation-verification.schema.json +515 -0
  13. package/spec/output-schemas/ask.schema.json +366 -0
  14. package/spec/output-schemas/backlinks.schema.json +131 -0
  15. package/spec/output-schemas/bench-result.schema.json +120 -0
  16. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  17. package/spec/output-schemas/collection-list.schema.json +45 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  20. package/spec/output-schemas/context-list.schema.json +21 -0
  21. package/spec/output-schemas/doctor.schema.json +313 -0
  22. package/spec/output-schemas/error.schema.json +30 -0
  23. package/spec/output-schemas/expansion.schema.json +37 -0
  24. package/spec/output-schemas/get.schema.json +140 -0
  25. package/spec/output-schemas/graph-query.schema.json +99 -0
  26. package/spec/output-schemas/graph.schema.json +371 -0
  27. package/spec/output-schemas/links-list.schema.json +186 -0
  28. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  29. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  30. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  31. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  32. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  33. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  34. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  35. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  36. package/spec/output-schemas/models-list.schema.json +93 -0
  37. package/spec/output-schemas/multi-get.schema.json +103 -0
  38. package/spec/output-schemas/process-status.schema.json +119 -0
  39. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  40. package/spec/output-schemas/resident-status.schema.json +154 -0
  41. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  42. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  43. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  44. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  45. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  46. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  47. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  48. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  49. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  50. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  51. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  52. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  53. package/spec/output-schemas/search-result.schema.json +154 -0
  54. package/spec/output-schemas/search-results.schema.json +338 -0
  55. package/spec/output-schemas/similar.schema.json +84 -0
  56. package/spec/output-schemas/status.schema.json +676 -0
  57. package/spec/output-schemas/tags-list.schema.json +48 -0
  58. package/src/app/context-runtime-types.ts +3 -0
  59. package/src/app/context-runtime.ts +15 -1
  60. package/src/cli/commands/ask.ts +106 -36
  61. package/src/cli/commands/context-build.ts +56 -9
  62. package/src/cli/commands/get.ts +64 -3
  63. package/src/cli/commands/query.ts +62 -23
  64. package/src/cli/commands/replay.ts +140 -0
  65. package/src/cli/commands/search.ts +48 -3
  66. package/src/cli/commands/shared.ts +3 -1
  67. package/src/cli/commands/trace.ts +200 -0
  68. package/src/cli/commands/vsearch.ts +75 -53
  69. package/src/cli/program.ts +255 -0
  70. package/src/config/index.ts +9 -0
  71. package/src/config/retrieval-traces.ts +56 -0
  72. package/src/config/types.ts +4 -0
  73. package/src/core/context-compiler.ts +11 -4
  74. package/src/core/retrieval-qrels.ts +405 -0
  75. package/src/core/retrieval-replay-candidate.ts +368 -0
  76. package/src/core/retrieval-replay-types.ts +109 -0
  77. package/src/core/retrieval-replay-validation.ts +89 -0
  78. package/src/core/retrieval-replay.ts +441 -0
  79. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  80. package/src/core/retrieval-trace-export.ts +113 -0
  81. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  82. package/src/core/retrieval-trace-filters.ts +19 -0
  83. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  84. package/src/core/retrieval-trace-management-types.ts +132 -0
  85. package/src/core/retrieval-trace-management.ts +422 -0
  86. package/src/core/retrieval-trace-request.ts +141 -0
  87. package/src/core/retrieval-trace-session.ts +494 -0
  88. package/src/core/retrieval-trace.ts +472 -0
  89. package/src/mcp/tools/context.ts +59 -8
  90. package/src/mcp/tools/get.ts +35 -1
  91. package/src/mcp/tools/index.ts +74 -0
  92. package/src/mcp/tools/query.ts +95 -64
  93. package/src/mcp/tools/search.ts +36 -13
  94. package/src/mcp/tools/trace.ts +143 -0
  95. package/src/mcp/tools/vsearch.ts +71 -38
  96. package/src/pipeline/answer.ts +167 -26
  97. package/src/pipeline/graph-retrieval.ts +15 -1
  98. package/src/pipeline/hybrid.ts +151 -43
  99. package/src/pipeline/search.ts +36 -3
  100. package/src/pipeline/trace-metadata.ts +47 -0
  101. package/src/pipeline/types.ts +43 -0
  102. package/src/pipeline/vsearch.ts +101 -38
  103. package/src/sdk/client.ts +380 -71
  104. package/src/sdk/documents.ts +48 -1
  105. package/src/sdk/index.ts +17 -0
  106. package/src/sdk/types.ts +28 -0
  107. package/src/serve/context-capsule.ts +67 -8
  108. package/src/serve/public/app.tsx +12 -1
  109. package/src/serve/public/globals.built.css +1 -1
  110. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  111. package/src/serve/public/pages/Dashboard.tsx +10 -0
  112. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  113. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  114. package/src/serve/retrieval-trace.ts +28 -0
  115. package/src/serve/routes/api.ts +366 -72
  116. package/src/serve/routes/traces.ts +156 -0
  117. package/src/serve/server.ts +87 -2
  118. package/src/store/index.ts +31 -0
  119. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  120. package/src/store/migrations/index.ts +2 -0
  121. package/src/store/retrieval-trace-codec.ts +384 -0
  122. package/src/store/sqlite/adapter.ts +153 -1
  123. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  124. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  125. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  126. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  127. package/src/store/types.ts +297 -0
  128. package/src/store/vector/sqlite-vec.ts +76 -1
  129. package/src/store/vector/types.ts +1 -1
@@ -94,6 +94,14 @@ import {
94
94
  resolveNotePreset,
95
95
  type NotePresetId,
96
96
  } from "../../core/note-presets";
97
+ import {
98
+ retrievalTraceFilters,
99
+ startRetrievalTraceRequest,
100
+ } from "../../core/retrieval-trace-request";
101
+ import {
102
+ evidenceFromExactDocument,
103
+ RetrievalTraceSession,
104
+ } from "../../core/retrieval-trace-session";
97
105
  import { extractSections } from "../../core/sections";
98
106
  import { normalizeStructuredQueryInput } from "../../core/structured-query";
99
107
  import {
@@ -118,7 +126,7 @@ import {
118
126
  } from "../../llm/registry";
119
127
  import {
120
128
  generateGroundedAnswer,
121
- processAnswerResult,
129
+ processAnswerResultWithTrace,
122
130
  } from "../../pipeline/answer";
123
131
  import { diagnoseQueryTarget } from "../../pipeline/diagnose";
124
132
  import { searchHybrid } from "../../pipeline/hybrid";
@@ -145,6 +153,10 @@ import {
145
153
  } from "../context";
146
154
  import { analyzeImportPath } from "../import-preview";
147
155
  import { getActiveJob, getJobStatus, startJob } from "../jobs";
156
+ import {
157
+ requestRetrievalTraceId,
158
+ withRetrievalTraceHeader,
159
+ } from "../retrieval-trace";
148
160
  import { buildAppStatus, type StatusBuildDeps } from "../status";
149
161
 
150
162
  /** Mutable context holder for hot-reloading presets */
@@ -460,6 +472,65 @@ function errorResponse(
460
472
  );
461
473
  }
462
474
 
475
+ interface RestTraceStart {
476
+ session: RetrievalTraceSession | null;
477
+ error: Response | null;
478
+ }
479
+
480
+ async function startRestTrace(
481
+ ctx: ServerContext,
482
+ input: {
483
+ query: string;
484
+ goal?: string;
485
+ filters?: Record<string, unknown>;
486
+ pipeline: string;
487
+ modelUris?: string[];
488
+ }
489
+ ): Promise<RestTraceStart> {
490
+ const started = await startRetrievalTraceRequest({
491
+ store: ctx.store,
492
+ config: ctx.config,
493
+ query: input.query,
494
+ goal: input.goal,
495
+ filters: input.filters,
496
+ pipeline: input.pipeline,
497
+ indexName: ctx.indexName,
498
+ modelUris: input.modelUris,
499
+ });
500
+ return started.ok
501
+ ? { session: started.value, error: null }
502
+ : {
503
+ session: null,
504
+ error: errorResponse(
505
+ "RUNTIME",
506
+ `Retrieval trace start failed: ${started.error.message}`,
507
+ 500
508
+ ),
509
+ };
510
+ }
511
+
512
+ async function finishRestTrace(
513
+ request: Request,
514
+ session: RetrievalTraceSession | null,
515
+ status: "completed" | "partial" | "failed",
516
+ response: Response
517
+ ): Promise<Response> {
518
+ if (!session) return response;
519
+ const terminalStatus = request.signal.aborted ? "cancelled" : status;
520
+ const finished = await session.finish(terminalStatus);
521
+ if (!finished.ok) {
522
+ return withRetrievalTraceHeader(
523
+ errorResponse(
524
+ "RUNTIME",
525
+ `Retrieval trace finalization failed: ${finished.error.message}`,
526
+ 500
527
+ ),
528
+ session
529
+ );
530
+ }
531
+ return withRetrievalTraceHeader(response, session);
532
+ }
533
+
463
534
  function jobConflictResponse(jobResult: StartJobError): Response {
464
535
  return errorResponse("CONFLICT", jobResult.error, 409, {
465
536
  activeJobId: jobResult.activeJobId,
@@ -1639,7 +1710,8 @@ export async function handleDocsAutocomplete(
1639
1710
  export async function handleDoc(
1640
1711
  store: SqliteAdapter,
1641
1712
  config: Config,
1642
- url: URL
1713
+ url: URL,
1714
+ request?: Request
1643
1715
  ): Promise<Response> {
1644
1716
  const uri = url.searchParams.get("uri");
1645
1717
  if (!uri) {
@@ -1679,7 +1751,7 @@ export async function handleDoc(
1679
1751
  });
1680
1752
  const source = await buildSourceMeta(config.collections, doc);
1681
1753
 
1682
- return jsonResponse({
1754
+ const responseData = {
1683
1755
  docid: doc.docid,
1684
1756
  uri: doc.uri,
1685
1757
  title: doc.title,
@@ -1690,7 +1762,52 @@ export async function handleDoc(
1690
1762
  tags,
1691
1763
  source,
1692
1764
  capabilities,
1765
+ };
1766
+ const continuationTraceId = request
1767
+ ? requestRetrievalTraceId(request)
1768
+ : undefined;
1769
+ if (!continuationTraceId) return jsonResponse(responseData);
1770
+
1771
+ const resumed = await RetrievalTraceSession.resume({
1772
+ store,
1773
+ config: config.retrievalTraces,
1774
+ traceId: continuationTraceId,
1693
1775
  });
1776
+ if (!resumed.ok) {
1777
+ return errorResponse("RUNTIME", resumed.error.message, 500);
1778
+ }
1779
+ const traceSession = resumed.value;
1780
+ if (!traceSession) return jsonResponse(responseData);
1781
+
1782
+ if (content !== null) {
1783
+ const endLine = content.split("\n").length;
1784
+ const evidence = evidenceFromExactDocument({
1785
+ docid: doc.docid,
1786
+ uri: doc.uri,
1787
+ sourceHash: doc.sourceHash,
1788
+ mirrorHash: doc.mirrorHash ?? undefined,
1789
+ content,
1790
+ startLine: 1,
1791
+ endLine,
1792
+ });
1793
+ if (evidence) {
1794
+ const got = await traceSession.recordEvidence("get", [evidence]);
1795
+ if (!got.ok) {
1796
+ return withRetrievalTraceHeader(
1797
+ errorResponse("RUNTIME", got.error.message, 500),
1798
+ traceSession
1799
+ );
1800
+ }
1801
+ const opened = await traceSession.recordEvidence("open", [evidence]);
1802
+ if (!opened.ok) {
1803
+ return withRetrievalTraceHeader(
1804
+ errorResponse("RUNTIME", opened.error.message, 500),
1805
+ traceSession
1806
+ );
1807
+ }
1808
+ }
1809
+ }
1810
+ return withRetrievalTraceHeader(jsonResponse(responseData), traceSession);
1694
1811
  }
1695
1812
 
1696
1813
  /**
@@ -3372,9 +3489,13 @@ export async function handleCreateDoc(
3372
3489
  * Returns search results.
3373
3490
  */
3374
3491
  export async function handleSearch(
3375
- store: SqliteAdapter,
3492
+ contextOrStore: ServerContext | SqliteAdapter,
3376
3493
  req: Request
3377
3494
  ): Promise<Response> {
3495
+ const context =
3496
+ "store" in contextOrStore ? contextOrStore : (null as ServerContext | null);
3497
+ const store =
3498
+ "store" in contextOrStore ? contextOrStore.store : contextOrStore;
3378
3499
  let body: SearchRequestBody;
3379
3500
  try {
3380
3501
  body = (await req.json()) as SearchRequestBody;
@@ -3486,13 +3607,30 @@ export async function handleSearch(
3486
3607
  author,
3487
3608
  };
3488
3609
 
3489
- const result = await searchBm25(store, rawQuery, options);
3610
+ const trace = context
3611
+ ? await startRestTrace(context, {
3612
+ query: rawQuery,
3613
+ filters: retrievalTraceFilters(options),
3614
+ pipeline: "bm25",
3615
+ })
3616
+ : { session: null, error: null };
3617
+ if (trace.error) return trace.error;
3618
+
3619
+ const result = await searchBm25(store, rawQuery, {
3620
+ ...options,
3621
+ traceSession: trace.session ?? undefined,
3622
+ });
3490
3623
 
3491
3624
  if (!result.ok) {
3492
- return errorResponse("RUNTIME", result.error.message, 500);
3625
+ return finishRestTrace(
3626
+ req,
3627
+ trace.session,
3628
+ "failed",
3629
+ errorResponse("RUNTIME", result.error.message, 500)
3630
+ );
3493
3631
  }
3494
3632
 
3495
- return jsonResponse(result.value);
3633
+ return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
3496
3634
  }
3497
3635
 
3498
3636
  /**
@@ -3626,6 +3764,41 @@ export async function handleQuery(
3626
3764
  : undefined;
3627
3765
  const author = body.author?.trim() || undefined;
3628
3766
 
3767
+ const queryOptions = {
3768
+ limit: Math.min(body.limit ?? 20, 50),
3769
+ minScore: body.minScore,
3770
+ collection: body.collection,
3771
+ lang: body.lang,
3772
+ intent: body.intent?.trim() || undefined,
3773
+ candidateLimit:
3774
+ body.candidateLimit !== undefined
3775
+ ? Math.min(body.candidateLimit, 100)
3776
+ : undefined,
3777
+ exclude,
3778
+ queryModes: normalizedQueryModes,
3779
+ noExpand: body.noExpand,
3780
+ noRerank: body.noRerank,
3781
+ graph: body.graph === true,
3782
+ noGraph: body.noGraph,
3783
+ tagsAll,
3784
+ tagsAny,
3785
+ since: body.since,
3786
+ until: body.until,
3787
+ categories,
3788
+ author,
3789
+ };
3790
+ const trace = await startRestTrace(ctx, {
3791
+ query: normalizedQuery,
3792
+ filters: retrievalTraceFilters(queryOptions),
3793
+ pipeline: "hybrid",
3794
+ modelUris: [
3795
+ ctx.embedPort?.modelUri,
3796
+ ctx.expandPort?.modelUri,
3797
+ ctx.rerankPort?.modelUri,
3798
+ ].filter((value): value is string => Boolean(value)),
3799
+ });
3800
+ if (trace.error) return trace.error;
3801
+
3629
3802
  const result = await searchHybrid(
3630
3803
  {
3631
3804
  store: ctx.store,
@@ -3637,35 +3810,21 @@ export async function handleQuery(
3637
3810
  },
3638
3811
  normalizedQuery,
3639
3812
  {
3640
- limit: Math.min(body.limit ?? 20, 50),
3641
- minScore: body.minScore,
3642
- collection: body.collection,
3643
- lang: body.lang,
3644
- intent: body.intent?.trim() || undefined,
3645
- candidateLimit:
3646
- body.candidateLimit !== undefined
3647
- ? Math.min(body.candidateLimit, 100)
3648
- : undefined,
3649
- exclude,
3650
- queryModes: normalizedQueryModes,
3651
- noExpand: body.noExpand,
3652
- noRerank: body.noRerank,
3653
- graph: body.graph === true,
3654
- noGraph: body.noGraph,
3655
- tagsAll,
3656
- tagsAny,
3657
- since: body.since,
3658
- until: body.until,
3659
- categories,
3660
- author,
3813
+ ...queryOptions,
3814
+ traceSession: trace.session ?? undefined,
3661
3815
  }
3662
3816
  );
3663
3817
 
3664
3818
  if (!result.ok) {
3665
- return errorResponse("RUNTIME", result.error.message, 500);
3819
+ return finishRestTrace(
3820
+ req,
3821
+ trace.session,
3822
+ "failed",
3823
+ errorResponse("RUNTIME", result.error.message, 500)
3824
+ );
3666
3825
  }
3667
3826
 
3668
- return jsonResponse(result.value);
3827
+ return withRetrievalTraceHeader(jsonResponse(result.value), trace.session);
3669
3828
  }
3670
3829
 
3671
3830
  /**
@@ -3878,15 +4037,6 @@ export async function handleAsk(
3878
4037
  return errorResponse("VALIDATION", "Query cannot be empty");
3879
4038
  }
3880
4039
 
3881
- // Check if answer generation is available
3882
- if (!ctx.capabilities.answer) {
3883
- return errorResponse(
3884
- "UNAVAILABLE",
3885
- "Answer generation not available. No answer model loaded.",
3886
- 503
3887
- );
3888
- }
3889
-
3890
4040
  // Parse tag filters
3891
4041
  let tagsAll: string[] | undefined;
3892
4042
  let tagsAny: string[] | undefined;
@@ -3973,6 +4123,64 @@ export async function handleAsk(
3973
4123
  const author = body.author?.trim() || undefined;
3974
4124
 
3975
4125
  const limit = Math.min(body.limit ?? 5, 20);
4126
+ const askOptions = {
4127
+ limit,
4128
+ collection: body.collection,
4129
+ lang: body.lang,
4130
+ intent: body.intent?.trim() || undefined,
4131
+ noExpand: body.noExpand,
4132
+ noRerank: body.noRerank,
4133
+ candidateLimit:
4134
+ body.candidateLimit !== undefined
4135
+ ? Math.min(body.candidateLimit, 100)
4136
+ : undefined,
4137
+ exclude,
4138
+ queryModes: normalizedQueryModes,
4139
+ tagsAll,
4140
+ tagsAny,
4141
+ since: body.since,
4142
+ until: body.until,
4143
+ categories,
4144
+ author,
4145
+ };
4146
+ const trace = await startRestTrace(ctx, {
4147
+ query: normalizedQuery,
4148
+ filters: retrievalTraceFilters(askOptions),
4149
+ pipeline: "ask",
4150
+ modelUris: [
4151
+ ctx.embedPort?.modelUri,
4152
+ ctx.expandPort?.modelUri,
4153
+ ctx.answerPort?.modelUri,
4154
+ ctx.rerankPort?.modelUri,
4155
+ ].filter((value): value is string => Boolean(value)),
4156
+ });
4157
+ if (trace.error) return trace.error;
4158
+
4159
+ if (!ctx.capabilities.answer) {
4160
+ const unavailable = await trace.session?.recordCapability(
4161
+ "answer_generation",
4162
+ "unavailable",
4163
+ "model_unavailable"
4164
+ );
4165
+ if (unavailable && !unavailable.ok) {
4166
+ return finishRestTrace(
4167
+ req,
4168
+ trace.session,
4169
+ "failed",
4170
+ errorResponse("RUNTIME", unavailable.error.message, 500)
4171
+ );
4172
+ }
4173
+ return finishRestTrace(
4174
+ req,
4175
+ trace.session,
4176
+ "failed",
4177
+ errorResponse(
4178
+ "UNAVAILABLE",
4179
+ "Answer generation not available. No answer model loaded.",
4180
+ 503
4181
+ )
4182
+ );
4183
+ }
3976
4184
 
3977
4185
  // Run hybrid search first
3978
4186
  const searchResult = await searchHybrid(
@@ -3986,29 +4194,18 @@ export async function handleAsk(
3986
4194
  },
3987
4195
  normalizedQuery,
3988
4196
  {
3989
- limit,
3990
- collection: body.collection,
3991
- lang: body.lang,
3992
- intent: body.intent?.trim() || undefined,
3993
- noExpand: body.noExpand,
3994
- noRerank: body.noRerank,
3995
- candidateLimit:
3996
- body.candidateLimit !== undefined
3997
- ? Math.min(body.candidateLimit, 100)
3998
- : undefined,
3999
- exclude,
4000
- queryModes: normalizedQueryModes,
4001
- tagsAll,
4002
- tagsAny,
4003
- since: body.since,
4004
- until: body.until,
4005
- categories,
4006
- author,
4197
+ ...askOptions,
4198
+ traceSession: trace.session ?? undefined,
4007
4199
  }
4008
4200
  );
4009
4201
 
4010
4202
  if (!searchResult.ok) {
4011
- return errorResponse("RUNTIME", searchResult.error.message, 500);
4203
+ return finishRestTrace(
4204
+ req,
4205
+ trace.session,
4206
+ "failed",
4207
+ errorResponse("RUNTIME", searchResult.error.message, 500)
4208
+ );
4012
4209
  }
4013
4210
 
4014
4211
  const results = searchResult.value.results;
@@ -4020,20 +4217,93 @@ export async function handleAsk(
4020
4217
  let answerGenerated = false;
4021
4218
 
4022
4219
  if (ctx.answerPort) {
4023
- const maxTokens = body.maxAnswerTokens ?? 512;
4024
- const rawResult = await generateGroundedAnswer(
4025
- { genPort: ctx.answerPort, store: ctx.store },
4026
- normalizedQuery,
4027
- results,
4028
- maxTokens
4220
+ const attempted = await trace.session?.recordCapability(
4221
+ "answer_generation",
4222
+ "attempted"
4029
4223
  );
4224
+ if (attempted && !attempted.ok) {
4225
+ return finishRestTrace(
4226
+ req,
4227
+ trace.session,
4228
+ "failed",
4229
+ errorResponse("RUNTIME", attempted.error.message, 500)
4230
+ );
4231
+ }
4232
+ const maxTokens = body.maxAnswerTokens ?? 512;
4233
+ let rawResult: Awaited<ReturnType<typeof generateGroundedAnswer>>;
4234
+ try {
4235
+ rawResult = await generateGroundedAnswer(
4236
+ { genPort: ctx.answerPort, store: ctx.store },
4237
+ normalizedQuery,
4238
+ results,
4239
+ maxTokens
4240
+ );
4241
+ } catch (error) {
4242
+ await trace.session?.recordCapability(
4243
+ "answer_generation",
4244
+ "failed",
4245
+ "generation_failed"
4246
+ );
4247
+ return finishRestTrace(
4248
+ req,
4249
+ trace.session,
4250
+ "failed",
4251
+ errorResponse(
4252
+ "RUNTIME",
4253
+ error instanceof Error ? error.message : String(error),
4254
+ 500
4255
+ )
4256
+ );
4257
+ }
4030
4258
 
4031
4259
  if (rawResult) {
4032
- const processed = processAnswerResult(rawResult);
4033
- answer = processed.answer;
4034
- citations = processed.citations;
4035
- answerContext = processed.answerContext;
4036
- answerGenerated = true;
4260
+ try {
4261
+ const processed = await processAnswerResultWithTrace(
4262
+ rawResult,
4263
+ trace.session ?? undefined
4264
+ );
4265
+ answer = processed.answer;
4266
+ citations = processed.citations;
4267
+ answerContext = processed.answerContext;
4268
+ answerGenerated = true;
4269
+ const used = await trace.session?.recordCapability(
4270
+ "answer_generation",
4271
+ "used"
4272
+ );
4273
+ if (used && !used.ok) {
4274
+ throw new Error(`Trace recording failed: ${used.error.message}`);
4275
+ }
4276
+ } catch (error) {
4277
+ await trace.session?.recordCapability(
4278
+ "answer_generation",
4279
+ "failed",
4280
+ "generation_failed"
4281
+ );
4282
+ return finishRestTrace(
4283
+ req,
4284
+ trace.session,
4285
+ "failed",
4286
+ errorResponse(
4287
+ "RUNTIME",
4288
+ error instanceof Error ? error.message : String(error),
4289
+ 500
4290
+ )
4291
+ );
4292
+ }
4293
+ } else {
4294
+ const failed = await trace.session?.recordCapability(
4295
+ "answer_generation",
4296
+ "failed",
4297
+ "generation_failed"
4298
+ );
4299
+ if (failed && !failed.ok) {
4300
+ return finishRestTrace(
4301
+ req,
4302
+ trace.session,
4303
+ "failed",
4304
+ errorResponse("RUNTIME", failed.error.message, 500)
4305
+ );
4306
+ }
4037
4307
  }
4038
4308
  }
4039
4309
 
@@ -4058,7 +4328,13 @@ export async function handleAsk(
4058
4328
  },
4059
4329
  };
4060
4330
 
4061
- return jsonResponse(askResult);
4331
+ const complete = answerGenerated && (citations?.length ?? 0) > 0;
4332
+ return finishRestTrace(
4333
+ req,
4334
+ trace.session,
4335
+ complete ? "completed" : "partial",
4336
+ jsonResponse(askResult)
4337
+ );
4062
4338
  }
4063
4339
 
4064
4340
  // ─────────────────────────────────────────────────────────────────────────────
@@ -4671,7 +4947,7 @@ export async function routeApi(
4671
4947
  }
4672
4948
 
4673
4949
  if (path === "/api/doc") {
4674
- return handleDoc(store, config, url);
4950
+ return handleDoc(store, config, url, req);
4675
4951
  }
4676
4952
 
4677
4953
  if (path === "/api/doc-asset") {
@@ -4679,7 +4955,25 @@ export async function routeApi(
4679
4955
  }
4680
4956
 
4681
4957
  if (path === "/api/search" && req.method === "POST") {
4682
- return handleSearch(store, req);
4958
+ return handleSearch(
4959
+ {
4960
+ store,
4961
+ config,
4962
+ indexName: "default",
4963
+ vectorIndex: null,
4964
+ embedPort: null,
4965
+ expandPort: null,
4966
+ answerPort: null,
4967
+ rerankPort: null,
4968
+ capabilities: {
4969
+ bm25: true,
4970
+ vector: false,
4971
+ hybrid: false,
4972
+ answer: false,
4973
+ },
4974
+ },
4975
+ req
4976
+ );
4683
4977
  }
4684
4978
 
4685
4979
  // Unknown API route