@namzu/sdk 0.4.2 → 0.4.4

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 (310) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/advisory/context.test.d.ts +16 -0
  3. package/dist/advisory/context.test.d.ts.map +1 -0
  4. package/dist/advisory/context.test.js +92 -0
  5. package/dist/advisory/context.test.js.map +1 -0
  6. package/dist/advisory/evaluator.test.d.ts +34 -0
  7. package/dist/advisory/evaluator.test.d.ts.map +1 -0
  8. package/dist/advisory/evaluator.test.js +172 -0
  9. package/dist/advisory/evaluator.test.js.map +1 -0
  10. package/dist/advisory/executor.test.d.ts +35 -0
  11. package/dist/advisory/executor.test.d.ts.map +1 -0
  12. package/dist/advisory/executor.test.js +233 -0
  13. package/dist/advisory/executor.test.js.map +1 -0
  14. package/dist/advisory/registry.test.d.ts +16 -0
  15. package/dist/advisory/registry.test.d.ts.map +1 -0
  16. package/dist/advisory/registry.test.js +62 -0
  17. package/dist/advisory/registry.test.js.map +1 -0
  18. package/dist/bridge/a2a/agent-card.test.d.ts +24 -0
  19. package/dist/bridge/a2a/agent-card.test.d.ts.map +1 -0
  20. package/dist/bridge/a2a/agent-card.test.js +118 -0
  21. package/dist/bridge/a2a/agent-card.test.js.map +1 -0
  22. package/dist/bridge/a2a/mapper.test.d.ts +29 -0
  23. package/dist/bridge/a2a/mapper.test.d.ts.map +1 -0
  24. package/dist/bridge/a2a/mapper.test.js +265 -0
  25. package/dist/bridge/a2a/mapper.test.js.map +1 -0
  26. package/dist/bridge/a2a/message.test.d.ts +20 -0
  27. package/dist/bridge/a2a/message.test.d.ts.map +1 -0
  28. package/dist/bridge/a2a/message.test.js +116 -0
  29. package/dist/bridge/a2a/message.test.js.map +1 -0
  30. package/dist/bridge/a2a/task.test.d.ts +29 -0
  31. package/dist/bridge/a2a/task.test.d.ts.map +1 -0
  32. package/dist/bridge/a2a/task.test.js +198 -0
  33. package/dist/bridge/a2a/task.test.js.map +1 -0
  34. package/dist/bridge/mcp/connector/adapter.test.d.ts +27 -0
  35. package/dist/bridge/mcp/connector/adapter.test.d.ts.map +1 -0
  36. package/dist/bridge/mcp/connector/adapter.test.js +203 -0
  37. package/dist/bridge/mcp/connector/adapter.test.js.map +1 -0
  38. package/dist/bridge/sse/mapper.test.d.ts +27 -0
  39. package/dist/bridge/sse/mapper.test.d.ts.map +1 -0
  40. package/dist/bridge/sse/mapper.test.js +271 -0
  41. package/dist/bridge/sse/mapper.test.js.map +1 -0
  42. package/dist/bridge/tools/connector/adapter.d.ts +2 -2
  43. package/dist/bridge/tools/connector/adapter.test.d.ts +28 -0
  44. package/dist/bridge/tools/connector/adapter.test.d.ts.map +1 -0
  45. package/dist/bridge/tools/connector/adapter.test.js +182 -0
  46. package/dist/bridge/tools/connector/adapter.test.js.map +1 -0
  47. package/dist/bridge/tools/connector/definitions.test.d.ts +23 -0
  48. package/dist/bridge/tools/connector/definitions.test.d.ts.map +1 -0
  49. package/dist/bridge/tools/connector/definitions.test.js +158 -0
  50. package/dist/bridge/tools/connector/definitions.test.js.map +1 -0
  51. package/dist/bridge/tools/connector/router.test.d.ts +21 -0
  52. package/dist/bridge/tools/connector/router.test.d.ts.map +1 -0
  53. package/dist/bridge/tools/connector/router.test.js +139 -0
  54. package/dist/bridge/tools/connector/router.test.js.map +1 -0
  55. package/dist/bus/breaker.test.d.ts +41 -0
  56. package/dist/bus/breaker.test.d.ts.map +1 -0
  57. package/dist/bus/breaker.test.js +242 -0
  58. package/dist/bus/breaker.test.js.map +1 -0
  59. package/dist/bus/index.d.ts +3 -1
  60. package/dist/bus/index.d.ts.map +1 -1
  61. package/dist/bus/index.js +18 -11
  62. package/dist/bus/index.js.map +1 -1
  63. package/dist/bus/index.test.d.ts +25 -0
  64. package/dist/bus/index.test.d.ts.map +1 -0
  65. package/dist/bus/index.test.js +151 -0
  66. package/dist/bus/index.test.js.map +1 -0
  67. package/dist/bus/lock.test.d.ts +44 -0
  68. package/dist/bus/lock.test.d.ts.map +1 -0
  69. package/dist/bus/lock.test.js +226 -0
  70. package/dist/bus/lock.test.js.map +1 -0
  71. package/dist/bus/ownership.test.d.ts +26 -0
  72. package/dist/bus/ownership.test.d.ts.map +1 -0
  73. package/dist/bus/ownership.test.js +205 -0
  74. package/dist/bus/ownership.test.js.map +1 -0
  75. package/dist/config/runtime.d.ts +28 -28
  76. package/dist/connector/BaseConnector.test.d.ts +21 -0
  77. package/dist/connector/BaseConnector.test.d.ts.map +1 -0
  78. package/dist/connector/BaseConnector.test.js +108 -0
  79. package/dist/connector/BaseConnector.test.js.map +1 -0
  80. package/dist/connector/builtins/http.test.d.ts +30 -0
  81. package/dist/connector/builtins/http.test.d.ts.map +1 -0
  82. package/dist/connector/builtins/http.test.js +232 -0
  83. package/dist/connector/builtins/http.test.js.map +1 -0
  84. package/dist/connector/builtins/webhook.test.d.ts +20 -0
  85. package/dist/connector/builtins/webhook.test.d.ts.map +1 -0
  86. package/dist/connector/builtins/webhook.test.js +113 -0
  87. package/dist/connector/builtins/webhook.test.js.map +1 -0
  88. package/dist/connector/execution/factory.test.d.ts +16 -0
  89. package/dist/connector/execution/factory.test.d.ts.map +1 -0
  90. package/dist/connector/execution/factory.test.js +64 -0
  91. package/dist/connector/execution/factory.test.js.map +1 -0
  92. package/dist/connector/execution/remote.test.d.ts +16 -0
  93. package/dist/connector/execution/remote.test.d.ts.map +1 -0
  94. package/dist/connector/execution/remote.test.js +53 -0
  95. package/dist/connector/execution/remote.test.js.map +1 -0
  96. package/dist/connector/mcp/adapter.test.d.ts +34 -0
  97. package/dist/connector/mcp/adapter.test.d.ts.map +1 -0
  98. package/dist/connector/mcp/adapter.test.js +199 -0
  99. package/dist/connector/mcp/adapter.test.js.map +1 -0
  100. package/dist/probe/context.d.ts +8 -0
  101. package/dist/probe/context.d.ts.map +1 -0
  102. package/dist/probe/context.js +7 -0
  103. package/dist/probe/context.js.map +1 -0
  104. package/dist/probe/errors.d.ts +12 -0
  105. package/dist/probe/errors.d.ts.map +1 -0
  106. package/dist/probe/errors.js +21 -0
  107. package/dist/probe/errors.js.map +1 -0
  108. package/dist/probe/index.d.ts +5 -0
  109. package/dist/probe/index.d.ts.map +1 -0
  110. package/dist/probe/index.js +4 -0
  111. package/dist/probe/index.js.map +1 -0
  112. package/dist/probe/registry.d.ts +24 -0
  113. package/dist/probe/registry.d.ts.map +1 -0
  114. package/dist/probe/registry.js +228 -0
  115. package/dist/probe/registry.js.map +1 -0
  116. package/dist/probe/registry.test.d.ts +7 -0
  117. package/dist/probe/registry.test.d.ts.map +1 -0
  118. package/dist/probe/registry.test.js +310 -0
  119. package/dist/probe/registry.test.js.map +1 -0
  120. package/dist/provider/instrumentation.d.ts +9 -0
  121. package/dist/provider/instrumentation.d.ts.map +1 -0
  122. package/dist/provider/instrumentation.js +104 -0
  123. package/dist/provider/instrumentation.js.map +1 -0
  124. package/dist/provider/instrumentation.test.d.ts +2 -0
  125. package/dist/provider/instrumentation.test.d.ts.map +1 -0
  126. package/dist/provider/instrumentation.test.js +152 -0
  127. package/dist/provider/instrumentation.test.js.map +1 -0
  128. package/dist/public-runtime.d.ts +5 -0
  129. package/dist/public-runtime.d.ts.map +1 -1
  130. package/dist/public-runtime.js +4 -0
  131. package/dist/public-runtime.js.map +1 -1
  132. package/dist/public-types.d.ts +3 -0
  133. package/dist/public-types.d.ts.map +1 -1
  134. package/dist/rag/chunking.test.d.ts +20 -0
  135. package/dist/rag/chunking.test.d.ts.map +1 -0
  136. package/dist/rag/chunking.test.js +92 -0
  137. package/dist/rag/chunking.test.js.map +1 -0
  138. package/dist/rag/context-assembler.test.d.ts +19 -0
  139. package/dist/rag/context-assembler.test.d.ts.map +1 -0
  140. package/dist/rag/context-assembler.test.js +98 -0
  141. package/dist/rag/context-assembler.test.js.map +1 -0
  142. package/dist/rag/embedding.test.d.ts +19 -0
  143. package/dist/rag/embedding.test.d.ts.map +1 -0
  144. package/dist/rag/embedding.test.js +115 -0
  145. package/dist/rag/embedding.test.js.map +1 -0
  146. package/dist/rag/ingestion.test.d.ts +22 -0
  147. package/dist/rag/ingestion.test.d.ts.map +1 -0
  148. package/dist/rag/ingestion.test.js +99 -0
  149. package/dist/rag/ingestion.test.js.map +1 -0
  150. package/dist/rag/knowledge-base.test.d.ts +17 -0
  151. package/dist/rag/knowledge-base.test.d.ts.map +1 -0
  152. package/dist/rag/knowledge-base.test.js +77 -0
  153. package/dist/rag/knowledge-base.test.js.map +1 -0
  154. package/dist/rag/rag-tool.test.d.ts +21 -0
  155. package/dist/rag/rag-tool.test.d.ts.map +1 -0
  156. package/dist/rag/rag-tool.test.js +149 -0
  157. package/dist/rag/rag-tool.test.js.map +1 -0
  158. package/dist/rag/retriever.test.d.ts +26 -0
  159. package/dist/rag/retriever.test.d.ts.map +1 -0
  160. package/dist/rag/retriever.test.js +180 -0
  161. package/dist/rag/retriever.test.js.map +1 -0
  162. package/dist/rag/vector-store.test.d.ts +38 -0
  163. package/dist/rag/vector-store.test.d.ts.map +1 -0
  164. package/dist/rag/vector-store.test.js +175 -0
  165. package/dist/rag/vector-store.test.js.map +1 -0
  166. package/dist/registry/ManagedRegistry.test.d.ts +21 -0
  167. package/dist/registry/ManagedRegistry.test.d.ts.map +1 -0
  168. package/dist/registry/ManagedRegistry.test.js +98 -0
  169. package/dist/registry/ManagedRegistry.test.js.map +1 -0
  170. package/dist/registry/Registry.test.d.ts +18 -0
  171. package/dist/registry/Registry.test.d.ts.map +1 -0
  172. package/dist/registry/Registry.test.js +79 -0
  173. package/dist/registry/Registry.test.js.map +1 -0
  174. package/dist/registry/agent/definitions.test.d.ts +15 -0
  175. package/dist/registry/agent/definitions.test.d.ts.map +1 -0
  176. package/dist/registry/agent/definitions.test.js +84 -0
  177. package/dist/registry/agent/definitions.test.js.map +1 -0
  178. package/dist/registry/connector/definitions.test.d.ts +13 -0
  179. package/dist/registry/connector/definitions.test.d.ts.map +1 -0
  180. package/dist/registry/connector/definitions.test.js +41 -0
  181. package/dist/registry/connector/definitions.test.js.map +1 -0
  182. package/dist/registry/connector/scoped.test.d.ts +21 -0
  183. package/dist/registry/connector/scoped.test.d.ts.map +1 -0
  184. package/dist/registry/connector/scoped.test.js +115 -0
  185. package/dist/registry/connector/scoped.test.js.map +1 -0
  186. package/dist/registry/plugin/index.test.d.ts +12 -0
  187. package/dist/registry/plugin/index.test.d.ts.map +1 -0
  188. package/dist/registry/plugin/index.test.js +69 -0
  189. package/dist/registry/plugin/index.test.js.map +1 -0
  190. package/dist/registry/tool/execute.test.d.ts +42 -0
  191. package/dist/registry/tool/execute.test.d.ts.map +1 -0
  192. package/dist/registry/tool/execute.test.js +281 -0
  193. package/dist/registry/tool/execute.test.js.map +1 -0
  194. package/dist/runtime/query/events.d.ts +3 -1
  195. package/dist/runtime/query/events.d.ts.map +1 -1
  196. package/dist/runtime/query/events.js +6 -1
  197. package/dist/runtime/query/events.js.map +1 -1
  198. package/dist/runtime/query/executor.d.ts +3 -1
  199. package/dist/runtime/query/executor.d.ts.map +1 -1
  200. package/dist/runtime/query/executor.js +30 -1
  201. package/dist/runtime/query/executor.js.map +1 -1
  202. package/dist/runtime/query/iteration/phases/advisory.test.d.ts +42 -0
  203. package/dist/runtime/query/iteration/phases/advisory.test.d.ts.map +1 -0
  204. package/dist/runtime/query/iteration/phases/advisory.test.js +334 -0
  205. package/dist/runtime/query/iteration/phases/advisory.test.js.map +1 -0
  206. package/dist/test-setup.d.ts +22 -0
  207. package/dist/test-setup.d.ts.map +1 -0
  208. package/dist/test-setup.js +23 -0
  209. package/dist/test-setup.js.map +1 -0
  210. package/dist/types/bus/index.d.ts +46 -2
  211. package/dist/types/bus/index.d.ts.map +1 -1
  212. package/dist/types/doctor/check.d.ts +41 -0
  213. package/dist/types/doctor/check.d.ts.map +1 -0
  214. package/dist/types/doctor/check.js +2 -0
  215. package/dist/types/doctor/check.js.map +1 -0
  216. package/dist/types/doctor/index.d.ts +2 -0
  217. package/dist/types/doctor/index.d.ts.map +1 -0
  218. package/dist/types/doctor/index.js +2 -0
  219. package/dist/types/doctor/index.js.map +1 -0
  220. package/dist/types/probe/event-kind.d.ts +6 -0
  221. package/dist/types/probe/event-kind.d.ts.map +1 -0
  222. package/dist/types/probe/event-kind.js +2 -0
  223. package/dist/types/probe/event-kind.js.map +1 -0
  224. package/dist/types/probe/event-of.d.ts +5 -0
  225. package/dist/types/probe/event-of.d.ts.map +1 -0
  226. package/dist/types/probe/event-of.js +2 -0
  227. package/dist/types/probe/event-of.js.map +1 -0
  228. package/dist/types/probe/index.d.ts +4 -0
  229. package/dist/types/probe/index.d.ts.map +1 -0
  230. package/dist/types/probe/index.js +2 -0
  231. package/dist/types/probe/index.js.map +1 -0
  232. package/dist/types/probe/registry.d.ts +27 -0
  233. package/dist/types/probe/registry.d.ts.map +1 -0
  234. package/dist/types/probe/registry.js +2 -0
  235. package/dist/types/probe/registry.js.map +1 -0
  236. package/dist/utils/logger.d.ts +1 -1
  237. package/dist/utils/logger.d.ts.map +1 -1
  238. package/dist/utils/logger.js +5 -0
  239. package/dist/utils/logger.js.map +1 -1
  240. package/dist/vault/instrumentation.d.ts +11 -0
  241. package/dist/vault/instrumentation.d.ts.map +1 -0
  242. package/dist/vault/instrumentation.js +32 -0
  243. package/dist/vault/instrumentation.js.map +1 -0
  244. package/dist/vault/instrumentation.test.d.ts +2 -0
  245. package/dist/vault/instrumentation.test.d.ts.map +1 -0
  246. package/dist/vault/instrumentation.test.js +80 -0
  247. package/dist/vault/instrumentation.test.js.map +1 -0
  248. package/package.json +4 -1
  249. package/src/advisory/context.test.ts +109 -0
  250. package/src/advisory/evaluator.test.ts +192 -0
  251. package/src/advisory/executor.test.ts +272 -0
  252. package/src/advisory/registry.test.ts +75 -0
  253. package/src/bridge/a2a/agent-card.test.ts +140 -0
  254. package/src/bridge/a2a/mapper.test.ts +293 -0
  255. package/src/bridge/a2a/message.test.ts +138 -0
  256. package/src/bridge/a2a/task.test.ts +235 -0
  257. package/src/bridge/mcp/connector/adapter.test.ts +230 -0
  258. package/src/bridge/sse/mapper.test.ts +422 -0
  259. package/src/bridge/tools/connector/adapter.test.ts +224 -0
  260. package/src/bridge/tools/connector/definitions.test.ts +183 -0
  261. package/src/bridge/tools/connector/router.test.ts +159 -0
  262. package/src/bus/breaker.test.ts +274 -0
  263. package/src/bus/index.test.ts +183 -0
  264. package/src/bus/index.ts +21 -10
  265. package/src/bus/lock.test.ts +265 -0
  266. package/src/bus/ownership.test.ts +243 -0
  267. package/src/connector/BaseConnector.test.ts +130 -0
  268. package/src/connector/builtins/http.test.ts +290 -0
  269. package/src/connector/builtins/webhook.test.ts +138 -0
  270. package/src/connector/execution/factory.test.ts +75 -0
  271. package/src/connector/execution/remote.test.ts +63 -0
  272. package/src/connector/mcp/adapter.test.ts +249 -0
  273. package/src/probe/context.ts +14 -0
  274. package/src/probe/errors.ts +27 -0
  275. package/src/probe/index.ts +4 -0
  276. package/src/probe/registry.test.ts +480 -0
  277. package/src/probe/registry.ts +276 -0
  278. package/src/provider/instrumentation.test.ts +192 -0
  279. package/src/provider/instrumentation.ts +139 -0
  280. package/src/public-runtime.ts +17 -0
  281. package/src/public-types.ts +3 -0
  282. package/src/rag/chunking.test.ts +107 -0
  283. package/src/rag/context-assembler.test.ts +114 -0
  284. package/src/rag/embedding.test.ts +130 -0
  285. package/src/rag/ingestion.test.ts +114 -0
  286. package/src/rag/knowledge-base.test.ts +106 -0
  287. package/src/rag/rag-tool.test.ts +167 -0
  288. package/src/rag/retriever.test.ts +210 -0
  289. package/src/rag/vector-store.test.ts +196 -0
  290. package/src/registry/ManagedRegistry.test.ts +118 -0
  291. package/src/registry/Registry.test.ts +91 -0
  292. package/src/registry/agent/definitions.test.ts +100 -0
  293. package/src/registry/connector/definitions.test.ts +51 -0
  294. package/src/registry/connector/scoped.test.ts +129 -0
  295. package/src/registry/plugin/index.test.ts +85 -0
  296. package/src/registry/tool/execute.test.ts +330 -0
  297. package/src/runtime/query/events.ts +6 -1
  298. package/src/runtime/query/executor.ts +34 -0
  299. package/src/runtime/query/iteration/phases/advisory.test.ts +412 -0
  300. package/src/test-setup.ts +24 -0
  301. package/src/types/bus/index.ts +54 -2
  302. package/src/types/doctor/check.ts +53 -0
  303. package/src/types/doctor/index.ts +9 -0
  304. package/src/types/probe/event-kind.ts +8 -0
  305. package/src/types/probe/event-of.ts +3 -0
  306. package/src/types/probe/index.ts +11 -0
  307. package/src/types/probe/registry.ts +36 -0
  308. package/src/utils/logger.ts +6 -1
  309. package/src/vault/instrumentation.test.ts +98 -0
  310. package/src/vault/instrumentation.ts +56 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 4):
3
+ *
4
+ * - `TextChunker.chunk(content, config)` dispatches by
5
+ * `config.strategy`. Unknown strategy hits an exhaustive-check
6
+ * throw (unreachable via types).
7
+ * - `fixed` strategy: slides a window of `chunkSize` by
8
+ * `chunkSize − chunkOverlap` (min 1) and emits trimmed non-empty
9
+ * slices. Indices are 0-based and contiguous.
10
+ * - `sentence` / `paragraph` strategies split by their separator
11
+ * sets and then merge small parts until the budget fills.
12
+ * - `recursive` strategy: short content returns as a single chunk;
13
+ * otherwise it splits by the first separator that produces >1
14
+ * parts, merges, and recurses into parts that still exceed
15
+ * `chunkSize`. Falls back to `fixed` when no separator splits.
16
+ * - Overlap is applied in `mergeSmallParts` via
17
+ * `current.slice(current.length - chunkOverlap) + part`.
18
+ */
19
+
20
+ import { describe, expect, it } from 'vitest'
21
+
22
+ import type { ChunkingConfig } from '../types/rag/index.js'
23
+
24
+ import { TextChunker } from './chunking.js'
25
+
26
+ const chunker = new TextChunker()
27
+
28
+ describe('TextChunker — fixed', () => {
29
+ const fixed: ChunkingConfig = { strategy: 'fixed', chunkSize: 10, chunkOverlap: 2 }
30
+
31
+ it('emits trimmed non-empty slices with contiguous indices', () => {
32
+ const result = chunker.chunk('0123456789abcdefghij', fixed)
33
+ expect(result).toHaveLength(3)
34
+ expect(result.map((c) => c.index)).toEqual([0, 1, 2])
35
+ })
36
+
37
+ it('skips whitespace-only slices', () => {
38
+ const result = chunker.chunk('abc def', {
39
+ strategy: 'fixed',
40
+ chunkSize: 5,
41
+ chunkOverlap: 0,
42
+ })
43
+ expect(result.map((c) => c.content)).not.toContain('')
44
+ })
45
+
46
+ it('clamps step to at least 1 when overlap >= chunkSize', () => {
47
+ const result = chunker.chunk('abcdefghij', {
48
+ strategy: 'fixed',
49
+ chunkSize: 3,
50
+ chunkOverlap: 3,
51
+ })
52
+ // step = max(1, 3-3) = 1; eagerly emits many overlapping slices
53
+ expect(result.length).toBeGreaterThan(1)
54
+ })
55
+ })
56
+
57
+ describe('TextChunker — sentence', () => {
58
+ it('splits by sentence separators and merges up to the budget', () => {
59
+ const result = chunker.chunk('First sentence. Second sentence. Third sentence.', {
60
+ strategy: 'sentence',
61
+ chunkSize: 100,
62
+ chunkOverlap: 0,
63
+ })
64
+ expect(result.length).toBeGreaterThanOrEqual(1)
65
+ expect(result[0]?.content).toContain('First sentence')
66
+ })
67
+ })
68
+
69
+ describe('TextChunker — paragraph', () => {
70
+ it('splits by paragraph separators', () => {
71
+ const result = chunker.chunk('para one\n\npara two\n\npara three', {
72
+ strategy: 'paragraph',
73
+ chunkSize: 200,
74
+ chunkOverlap: 0,
75
+ })
76
+ expect(result.length).toBeGreaterThanOrEqual(1)
77
+ })
78
+ })
79
+
80
+ describe('TextChunker — recursive', () => {
81
+ it('short content fits into a single chunk', () => {
82
+ const result = chunker.chunk('tiny', {
83
+ strategy: 'recursive',
84
+ chunkSize: 100,
85
+ chunkOverlap: 0,
86
+ })
87
+ expect(result).toEqual([{ content: 'tiny', index: 0 }])
88
+ })
89
+
90
+ it('long content recursively splits to stay within chunkSize', () => {
91
+ const content = 'paragraph one. more text.\n\nparagraph two. more.'
92
+ const result = chunker.chunk(content, {
93
+ strategy: 'recursive',
94
+ chunkSize: 30,
95
+ chunkOverlap: 0,
96
+ })
97
+ for (const c of result) {
98
+ expect(c.content.length).toBeLessThanOrEqual(30)
99
+ }
100
+ })
101
+
102
+ it('empty or whitespace-only content yields empty result', () => {
103
+ expect(chunker.chunk(' ', { strategy: 'recursive', chunkSize: 10, chunkOverlap: 0 })).toEqual(
104
+ [],
105
+ )
106
+ })
107
+ })
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 4):
3
+ *
4
+ * - `assembleRAGContext([], cfg)` returns `{content: '', sources: [],
5
+ * tokenCount: 0}` immediately.
6
+ * - Token estimation: `Math.ceil(text.length / 4)`.
7
+ * - `headerTemplate` is prepended before any chunks.
8
+ * - Chunks are included in order while their accumulated token count
9
+ * stays below `maxTokens`; the first chunk that would overflow is
10
+ * dropped along with every subsequent chunk (early break).
11
+ * - `includeMetadata: true` prefixes each chunk with a bracketed
12
+ * metadata line (`Source: …`, `Title: …`, `Relevance: XX.X%`).
13
+ * - `sources[]` captures a truncated preview (first 200 chars) of
14
+ * each INCLUDED chunk; skipped chunks are NOT present.
15
+ * - `content` is the joined parts with `config.separator`;
16
+ * `tokenCount` is re-estimated from the joined content.
17
+ */
18
+
19
+ import { describe, expect, it } from 'vitest'
20
+
21
+ import type { ChunkId, DocumentId, KnowledgeBaseId, TenantId } from '../types/ids/index.js'
22
+ import type { VectorSearchResult } from '../types/rag/index.js'
23
+
24
+ import { assembleRAGContext } from './context-assembler.js'
25
+
26
+ function result(
27
+ content: string,
28
+ score = 0.9,
29
+ meta: Record<string, unknown> = {},
30
+ ): VectorSearchResult {
31
+ return {
32
+ chunk: {
33
+ id: `c_${content.slice(0, 3)}` as ChunkId,
34
+ documentId: 'doc_1' as DocumentId,
35
+ knowledgeBaseId: 'kb_1' as KnowledgeBaseId,
36
+ tenantId: 't_1' as TenantId,
37
+ content,
38
+ index: 0,
39
+ tokenCount: 0,
40
+ metadata: meta,
41
+ createdAt: 0,
42
+ },
43
+ score,
44
+ }
45
+ }
46
+
47
+ describe('assembleRAGContext', () => {
48
+ it('returns empty for empty input', () => {
49
+ expect(assembleRAGContext([])).toEqual({ content: '', sources: [], tokenCount: 0 })
50
+ })
51
+
52
+ it('joins non-empty chunks with the configured separator', () => {
53
+ const ctx = assembleRAGContext([result('alpha'), result('beta')], {
54
+ separator: ' | ',
55
+ maxTokens: 1000,
56
+ includeMetadata: false,
57
+ headerTemplate: undefined,
58
+ })
59
+ expect(ctx.content).toBe('alpha | beta')
60
+ })
61
+
62
+ it('includes a headerTemplate before chunks when provided', () => {
63
+ const ctx = assembleRAGContext([result('body')], {
64
+ separator: '\n',
65
+ maxTokens: 1000,
66
+ includeMetadata: false,
67
+ headerTemplate: '### Knowledge',
68
+ })
69
+ expect(ctx.content.startsWith('### Knowledge\n')).toBe(true)
70
+ })
71
+
72
+ it('early-breaks once a chunk would overflow maxTokens (and drops subsequent)', () => {
73
+ const long = 'a'.repeat(200) // ~50 tokens per the /4 estimate
74
+ const ctx = assembleRAGContext([result('tiny'), result(long), result('also-tiny')], {
75
+ separator: '\n',
76
+ maxTokens: 20,
77
+ includeMetadata: false,
78
+ headerTemplate: undefined,
79
+ })
80
+ expect(ctx.content).toBe('tiny')
81
+ // third chunk was NOT included even though it would fit — the loop breaks on first overflow.
82
+ expect(ctx.sources.map((s) => s.chunk.slice(0, 10))).toEqual(['tiny'])
83
+ })
84
+
85
+ it('includeMetadata prefixes entries with bracketed metadata', () => {
86
+ const ctx = assembleRAGContext([result('body', 0.7567, { source: 'repo', title: 'README' })], {
87
+ separator: '\n',
88
+ maxTokens: 1000,
89
+ includeMetadata: true,
90
+ })
91
+ expect(ctx.content).toContain('[Source: repo | Title: README | Relevance: 75.7%]')
92
+ })
93
+
94
+ it('sources[] carries the first 200 chars of each included chunk', () => {
95
+ const long = 'x'.repeat(500)
96
+ const ctx = assembleRAGContext([result(long)], {
97
+ separator: '\n',
98
+ maxTokens: 10000,
99
+ includeMetadata: false,
100
+ })
101
+ expect(ctx.sources[0]?.chunk).toHaveLength(200)
102
+ })
103
+
104
+ it('tokenCount is derived from the final joined content', () => {
105
+ const ctx = assembleRAGContext([result('abcd'), result('efgh')], {
106
+ separator: '',
107
+ maxTokens: 1000,
108
+ includeMetadata: false,
109
+ headerTemplate: undefined,
110
+ })
111
+ // 'abcdefgh' → Math.ceil(8 / 4) = 2
112
+ expect(ctx.tokenCount).toBe(2)
113
+ })
114
+ })
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 4):
3
+ *
4
+ * - `OpenRouterEmbeddingProvider`:
5
+ * - Defaults: dimensions = 1536; baseUrl = openrouter.ai/api/v1;
6
+ * batchSize = 64.
7
+ * - `embed(texts)` batches into `batchSize` slices and concatenates
8
+ * results in input order.
9
+ * - Each HTTP call posts `{model, input, dimensions}` to
10
+ * `${baseUrl}/embeddings` with the Bearer authorization header.
11
+ * - The API response is sorted by `index` ascending before extracting
12
+ * `.embedding`, so results match input order even if the server
13
+ * re-orders.
14
+ * - `embedQuery(query)` returns the first result from `embed([query])`;
15
+ * throws when the response is empty.
16
+ * - `!response.ok` → throws with `Embedding API error (<status>): <body>`.
17
+ */
18
+
19
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
20
+
21
+ import { OpenRouterEmbeddingProvider } from './embedding.js'
22
+
23
+ describe('OpenRouterEmbeddingProvider', () => {
24
+ let fetchMock: ReturnType<typeof vi.fn>
25
+
26
+ beforeEach(() => {
27
+ fetchMock = vi.fn()
28
+ global.fetch = fetchMock as unknown as typeof fetch
29
+ })
30
+
31
+ afterEach(() => {
32
+ vi.restoreAllMocks()
33
+ })
34
+
35
+ it('carries model / dimensions defaults + batchSize', () => {
36
+ const p = new OpenRouterEmbeddingProvider({ apiKey: 'k', model: 'm' })
37
+ expect(p.model).toBe('m')
38
+ expect(p.dimensions).toBe(1536)
39
+ })
40
+
41
+ it('honors overrides for dimensions + baseUrl + batchSize', async () => {
42
+ fetchMock.mockResolvedValue({
43
+ ok: true,
44
+ json: async () => ({ data: [{ index: 0, embedding: [1, 2, 3] }] }),
45
+ })
46
+ const p = new OpenRouterEmbeddingProvider({
47
+ apiKey: 'k',
48
+ model: 'm',
49
+ dimensions: 256,
50
+ baseUrl: 'https://custom.example/api',
51
+ })
52
+ await p.embed(['x'])
53
+
54
+ expect(fetchMock).toHaveBeenCalledWith(
55
+ 'https://custom.example/api/embeddings',
56
+ expect.objectContaining({
57
+ method: 'POST',
58
+ headers: expect.objectContaining({
59
+ Authorization: 'Bearer k',
60
+ 'Content-Type': 'application/json',
61
+ }),
62
+ }),
63
+ )
64
+ const body = JSON.parse((fetchMock.mock.calls[0]?.[1] as { body: string }).body)
65
+ expect(body).toEqual({ model: 'm', input: ['x'], dimensions: 256 })
66
+ expect(p.dimensions).toBe(256)
67
+ })
68
+
69
+ it('batches into batchSize slices', async () => {
70
+ fetchMock.mockResolvedValue({
71
+ ok: true,
72
+ json: async () => ({
73
+ data: [
74
+ { index: 0, embedding: [1] },
75
+ { index: 1, embedding: [2] },
76
+ ],
77
+ }),
78
+ })
79
+ const p = new OpenRouterEmbeddingProvider({
80
+ apiKey: 'k',
81
+ model: 'm',
82
+ batchSize: 2,
83
+ })
84
+ await p.embed(['a', 'b', 'c', 'd'])
85
+ expect(fetchMock).toHaveBeenCalledTimes(2)
86
+ })
87
+
88
+ it('sorts response by index before extracting embeddings', async () => {
89
+ fetchMock.mockResolvedValue({
90
+ ok: true,
91
+ json: async () => ({
92
+ data: [
93
+ { index: 2, embedding: [3] },
94
+ { index: 0, embedding: [1] },
95
+ { index: 1, embedding: [2] },
96
+ ],
97
+ }),
98
+ })
99
+ const p = new OpenRouterEmbeddingProvider({ apiKey: 'k', model: 'm' })
100
+ expect(await p.embed(['a', 'b', 'c'])).toEqual([[1], [2], [3]])
101
+ })
102
+
103
+ it('embedQuery returns the first result', async () => {
104
+ fetchMock.mockResolvedValue({
105
+ ok: true,
106
+ json: async () => ({ data: [{ index: 0, embedding: [9, 9] }] }),
107
+ })
108
+ const p = new OpenRouterEmbeddingProvider({ apiKey: 'k', model: 'm' })
109
+ expect(await p.embedQuery('hi')).toEqual([9, 9])
110
+ })
111
+
112
+ it('embedQuery throws when the response is empty', async () => {
113
+ fetchMock.mockResolvedValue({
114
+ ok: true,
115
+ json: async () => ({ data: [] }),
116
+ })
117
+ const p = new OpenRouterEmbeddingProvider({ apiKey: 'k', model: 'm' })
118
+ await expect(p.embedQuery('hi')).rejects.toThrow(/no results/)
119
+ })
120
+
121
+ it('throws on non-OK HTTP response', async () => {
122
+ fetchMock.mockResolvedValue({
123
+ ok: false,
124
+ status: 503,
125
+ text: async () => 'service unavailable',
126
+ })
127
+ const p = new OpenRouterEmbeddingProvider({ apiKey: 'k', model: 'm' })
128
+ await expect(p.embed(['hi'])).rejects.toThrow(/503.*service unavailable/)
129
+ })
130
+ })
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 4):
3
+ *
4
+ * - `DefaultIngestionPipeline.ingest(content, metadata, scope, kbId)`:
5
+ * - Generates a fresh `documentId` per call.
6
+ * - Chunks via `TextChunker.chunk(content, chunkingConfig)`.
7
+ * - Returns zero-chunk result when the chunker emits nothing.
8
+ * - Calls `embeddingProvider.embed([...chunkTexts])` exactly once
9
+ * with every chunk in order.
10
+ * - Each resulting `Chunk` carries:
11
+ * - a fresh `id`,
12
+ * - the generated `documentId`,
13
+ * - the passed `knowledgeBaseId`,
14
+ * - `scope.tenantId`,
15
+ * - the original `chunkContent` + `chunkIndex` metadata,
16
+ * - `tokenCount = Math.ceil(content.length / 4)`.
17
+ * - Calls `vectorStore.upsert(chunks)` exactly once.
18
+ * - Totals `tokenCount` across chunks and reports `durationMs`.
19
+ * - `remove(documentId)` delegates to `vectorStore.deleteByDocument`.
20
+ */
21
+
22
+ import { describe, expect, it, vi } from 'vitest'
23
+ import { z } from 'zod'
24
+
25
+ import type { DocumentId, KnowledgeBaseId, TenantId } from '../types/ids/index.js'
26
+ import type { Chunk, EmbeddingProvider, TenantScope, VectorStore } from '../types/rag/index.js'
27
+
28
+ import { DefaultIngestionPipeline } from './ingestion.js'
29
+
30
+ const KB = 'kb_1' as KnowledgeBaseId
31
+ const TENANT = 't_1' as TenantId
32
+ const scope: TenantScope = { tenantId: TENANT }
33
+
34
+ function makeVectorStore(): VectorStore {
35
+ return {
36
+ upsert: vi.fn<(chunks: Chunk[]) => Promise<void>>(),
37
+ search: vi.fn(),
38
+ delete: vi.fn(),
39
+ deleteByDocument: vi.fn(),
40
+ deleteByKnowledgeBase: vi.fn(),
41
+ } as unknown as VectorStore
42
+ }
43
+
44
+ function makeEmbedder(): EmbeddingProvider {
45
+ return {
46
+ id: 'mock',
47
+ model: 'x',
48
+ dimensions: 3,
49
+ embed: vi.fn(async (texts: string[]) => texts.map(() => [1, 0, 0])),
50
+ embedQuery: vi.fn(async () => [1, 0, 0]),
51
+ }
52
+ }
53
+
54
+ describe('DefaultIngestionPipeline — ingest', () => {
55
+ it('returns zero-chunk result when the chunker emits nothing', async () => {
56
+ const vs = makeVectorStore()
57
+ const emb = makeEmbedder()
58
+ // chunkSize 10, no content at all → chunker returns []
59
+ const pipeline = new DefaultIngestionPipeline(vs, emb, {
60
+ strategy: 'fixed',
61
+ chunkSize: 10,
62
+ chunkOverlap: 0,
63
+ })
64
+ const result = await pipeline.ingest(' ', {}, scope, KB)
65
+ expect(result.chunkCount).toBe(0)
66
+ expect(result.totalTokens).toBe(0)
67
+ expect(vs.upsert).not.toHaveBeenCalled()
68
+ })
69
+
70
+ it('chunks content, embeds every chunk once, upserts once', async () => {
71
+ const vs = makeVectorStore()
72
+ const emb = makeEmbedder()
73
+ const pipeline = new DefaultIngestionPipeline(vs, emb, {
74
+ strategy: 'fixed',
75
+ chunkSize: 10,
76
+ chunkOverlap: 0,
77
+ })
78
+
79
+ const result = await pipeline.ingest('a'.repeat(25), { source: 'repo' }, scope, KB)
80
+
81
+ expect(emb.embed).toHaveBeenCalledTimes(1)
82
+ expect(vs.upsert).toHaveBeenCalledTimes(1)
83
+ expect(result.chunkCount).toBeGreaterThan(1)
84
+
85
+ const upsertedChunks = vi.mocked(vs.upsert).mock.calls[0]?.[0] ?? []
86
+ for (const c of upsertedChunks) {
87
+ expect(c.documentId).toBe(result.documentId)
88
+ expect(c.knowledgeBaseId).toBe(KB)
89
+ expect(c.tenantId).toBe(TENANT)
90
+ expect(c.metadata.source).toBe('repo')
91
+ expect(c.metadata.chunkIndex).toBeDefined()
92
+ expect(c.tokenCount).toBe(Math.ceil(c.content.length / 4))
93
+ }
94
+ })
95
+
96
+ it('generates a fresh documentId per ingest call', async () => {
97
+ const pipeline = new DefaultIngestionPipeline(makeVectorStore(), makeEmbedder())
98
+ const a = await pipeline.ingest('alpha', {}, scope, KB)
99
+ const b = await pipeline.ingest('beta', {}, scope, KB)
100
+ expect(a.documentId).not.toBe(b.documentId)
101
+ })
102
+ })
103
+
104
+ describe('DefaultIngestionPipeline — remove', () => {
105
+ it('delegates to vectorStore.deleteByDocument', async () => {
106
+ const vs = makeVectorStore()
107
+ const pipeline = new DefaultIngestionPipeline(vs, makeEmbedder())
108
+ await pipeline.remove('doc_9' as DocumentId)
109
+ expect(vs.deleteByDocument).toHaveBeenCalledWith('doc_9')
110
+ })
111
+ })
112
+
113
+ // Avoid the unused zod import — used in type coverage indirectly.
114
+ void z
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Current-code invariants asserted (2026-04-21, ses_006 Phase 4):
3
+ *
4
+ * - `DefaultKnowledgeBase` is a composition layer: it builds an
5
+ * ingestion pipeline + a retriever from its config and delegates
6
+ * `ingest` / `query` / `remove` / `clear` to them.
7
+ * - `id` is set from `config.id` if provided, else generated once
8
+ * via `generateKnowledgeBaseId` — never regenerated later.
9
+ * - `ingest(content, metadata)` invokes the ingestion pipeline with
10
+ * the configured scope + kb id; returns whatever the pipeline
11
+ * returns.
12
+ * - `query(query)` invokes the retriever with scope + kb id.
13
+ * - `clear()` calls `vectorStore.deleteByKnowledgeBase(id, tenantId)`.
14
+ * - `remove(documentId)` calls `vectorStore.deleteByDocument`.
15
+ */
16
+
17
+ import { describe, expect, it, vi } from 'vitest'
18
+
19
+ import type { DocumentId, KnowledgeBaseId, TenantId } from '../types/ids/index.js'
20
+ import type { EmbeddingProvider, VectorStore } from '../types/rag/index.js'
21
+
22
+ import { DefaultKnowledgeBase } from './knowledge-base.js'
23
+
24
+ const TENANT = 't_1' as TenantId
25
+
26
+ function makeVectorStore(): VectorStore {
27
+ return {
28
+ upsert: vi.fn(),
29
+ search: vi.fn(async () => []),
30
+ delete: vi.fn(),
31
+ deleteByDocument: vi.fn(),
32
+ deleteByKnowledgeBase: vi.fn(),
33
+ }
34
+ }
35
+
36
+ function makeEmbedder(): EmbeddingProvider {
37
+ return {
38
+ id: 'mock',
39
+ model: 'x',
40
+ dimensions: 3,
41
+ embed: vi.fn(async (texts: string[]) => texts.map(() => [1, 0, 0])),
42
+ embedQuery: vi.fn(async () => [1, 0, 0]),
43
+ }
44
+ }
45
+
46
+ describe('DefaultKnowledgeBase', () => {
47
+ it('uses the id provided in config when set', () => {
48
+ const kb = new DefaultKnowledgeBase(
49
+ { id: 'kb_fixed' as KnowledgeBaseId, name: 'kb', tenantId: TENANT },
50
+ makeVectorStore(),
51
+ makeEmbedder(),
52
+ )
53
+ expect(kb.id).toBe('kb_fixed')
54
+ expect(kb.config.id).toBe('kb_fixed')
55
+ })
56
+
57
+ it('generates an id when none is provided', () => {
58
+ const kb = new DefaultKnowledgeBase(
59
+ { name: 'kb', tenantId: TENANT },
60
+ makeVectorStore(),
61
+ makeEmbedder(),
62
+ )
63
+ expect(kb.id).toMatch(/^kb_/)
64
+ })
65
+
66
+ it('ingest delegates to the ingestion pipeline and carries metadata through', async () => {
67
+ const vs = makeVectorStore()
68
+ const kb = new DefaultKnowledgeBase(
69
+ { id: 'kb_fixed' as KnowledgeBaseId, name: 'kb', tenantId: TENANT },
70
+ vs,
71
+ makeEmbedder(),
72
+ )
73
+ const result = await kb.ingest('hello world', { source: 'readme' })
74
+ expect(result.documentId).toMatch(/^doc_/)
75
+ expect(vs.upsert).toHaveBeenCalled()
76
+ const chunks = vi.mocked(vs.upsert).mock.calls[0]?.[0] ?? []
77
+ expect(chunks[0]?.knowledgeBaseId).toBe('kb_fixed')
78
+ expect(chunks[0]?.tenantId).toBe(TENANT)
79
+ })
80
+
81
+ it('remove delegates to vectorStore.deleteByDocument', async () => {
82
+ const vs = makeVectorStore()
83
+ const kb = new DefaultKnowledgeBase({ name: 'kb', tenantId: TENANT }, vs, makeEmbedder())
84
+ await kb.remove('doc_1' as DocumentId)
85
+ expect(vs.deleteByDocument).toHaveBeenCalledWith('doc_1')
86
+ })
87
+
88
+ it('clear delegates to vectorStore.deleteByKnowledgeBase with id + tenantId', async () => {
89
+ const vs = makeVectorStore()
90
+ const kb = new DefaultKnowledgeBase(
91
+ { id: 'kb_fixed' as KnowledgeBaseId, name: 'kb', tenantId: TENANT },
92
+ vs,
93
+ makeEmbedder(),
94
+ )
95
+ await kb.clear()
96
+ expect(vs.deleteByKnowledgeBase).toHaveBeenCalledWith('kb_fixed', TENANT)
97
+ })
98
+
99
+ it('query delegates to retriever', async () => {
100
+ const vs = makeVectorStore()
101
+ const kb = new DefaultKnowledgeBase({ name: 'kb', tenantId: TENANT }, vs, makeEmbedder())
102
+ const out = await kb.query({ text: 'hi' })
103
+ expect(out.mode).toBeDefined()
104
+ expect(vs.search).toHaveBeenCalled()
105
+ })
106
+ })