@diyor28/context 1.0.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 (280) hide show
  1. package/README.md +270 -0
  2. package/dist/__tests__/attachment-selector.test.d.ts +11 -0
  3. package/dist/__tests__/attachment-selector.test.d.ts.map +1 -0
  4. package/dist/__tests__/attachment-selector.test.js +449 -0
  5. package/dist/__tests__/attachment-selector.test.js.map +1 -0
  6. package/dist/__tests__/cache-breakpoints.test.d.ts +11 -0
  7. package/dist/__tests__/cache-breakpoints.test.d.ts.map +1 -0
  8. package/dist/__tests__/cache-breakpoints.test.js +398 -0
  9. package/dist/__tests__/cache-breakpoints.test.js.map +1 -0
  10. package/dist/__tests__/codecs.test.d.ts +7 -0
  11. package/dist/__tests__/codecs.test.d.ts.map +1 -0
  12. package/dist/__tests__/codecs.test.js +331 -0
  13. package/dist/__tests__/codecs.test.js.map +1 -0
  14. package/dist/__tests__/compactor.test.d.ts +11 -0
  15. package/dist/__tests__/compactor.test.d.ts.map +1 -0
  16. package/dist/__tests__/compactor.test.js +519 -0
  17. package/dist/__tests__/compactor.test.js.map +1 -0
  18. package/dist/__tests__/context-graph.test.d.ts +7 -0
  19. package/dist/__tests__/context-graph.test.d.ts.map +1 -0
  20. package/dist/__tests__/context-graph.test.js +262 -0
  21. package/dist/__tests__/context-graph.test.js.map +1 -0
  22. package/dist/__tests__/hash.test.d.ts +7 -0
  23. package/dist/__tests__/hash.test.d.ts.map +1 -0
  24. package/dist/__tests__/hash.test.js +228 -0
  25. package/dist/__tests__/hash.test.js.map +1 -0
  26. package/dist/__tests__/integration.test.d.ts +15 -0
  27. package/dist/__tests__/integration.test.d.ts.map +1 -0
  28. package/dist/__tests__/integration.test.js +728 -0
  29. package/dist/__tests__/integration.test.js.map +1 -0
  30. package/dist/__tests__/kind-order.test.d.ts +7 -0
  31. package/dist/__tests__/kind-order.test.d.ts.map +1 -0
  32. package/dist/__tests__/kind-order.test.js +243 -0
  33. package/dist/__tests__/kind-order.test.js.map +1 -0
  34. package/dist/__tests__/phase2-integration.test.d.ts +5 -0
  35. package/dist/__tests__/phase2-integration.test.d.ts.map +1 -0
  36. package/dist/__tests__/phase2-integration.test.js +222 -0
  37. package/dist/__tests__/phase2-integration.test.js.map +1 -0
  38. package/dist/__tests__/queries.test.d.ts +7 -0
  39. package/dist/__tests__/queries.test.d.ts.map +1 -0
  40. package/dist/__tests__/queries.test.js +254 -0
  41. package/dist/__tests__/queries.test.js.map +1 -0
  42. package/dist/__tests__/token-estimator.test.d.ts +7 -0
  43. package/dist/__tests__/token-estimator.test.d.ts.map +1 -0
  44. package/dist/__tests__/token-estimator.test.js +267 -0
  45. package/dist/__tests__/token-estimator.test.js.map +1 -0
  46. package/dist/adapters/anthropic-estimator.d.ts +38 -0
  47. package/dist/adapters/anthropic-estimator.d.ts.map +1 -0
  48. package/dist/adapters/anthropic-estimator.js +108 -0
  49. package/dist/adapters/anthropic-estimator.js.map +1 -0
  50. package/dist/adapters/attachment-resolver.d.ts +96 -0
  51. package/dist/adapters/attachment-resolver.d.ts.map +1 -0
  52. package/dist/adapters/attachment-resolver.js +176 -0
  53. package/dist/adapters/attachment-resolver.js.map +1 -0
  54. package/dist/adapters/attachment-selector.d.ts +59 -0
  55. package/dist/adapters/attachment-selector.d.ts.map +1 -0
  56. package/dist/adapters/attachment-selector.js +163 -0
  57. package/dist/adapters/attachment-selector.js.map +1 -0
  58. package/dist/adapters/gemini-estimator.d.ts +27 -0
  59. package/dist/adapters/gemini-estimator.d.ts.map +1 -0
  60. package/dist/adapters/gemini-estimator.js +80 -0
  61. package/dist/adapters/gemini-estimator.js.map +1 -0
  62. package/dist/adapters/index.d.ts +12 -0
  63. package/dist/adapters/index.d.ts.map +1 -0
  64. package/dist/adapters/index.js +28 -0
  65. package/dist/adapters/index.js.map +1 -0
  66. package/dist/adapters/memory-store.d.ts +139 -0
  67. package/dist/adapters/memory-store.d.ts.map +1 -0
  68. package/dist/adapters/memory-store.js +187 -0
  69. package/dist/adapters/memory-store.js.map +1 -0
  70. package/dist/adapters/openai-estimator.d.ts +35 -0
  71. package/dist/adapters/openai-estimator.d.ts.map +1 -0
  72. package/dist/adapters/openai-estimator.js +89 -0
  73. package/dist/adapters/openai-estimator.js.map +1 -0
  74. package/dist/adapters/summarizer.d.ts +121 -0
  75. package/dist/adapters/summarizer.d.ts.map +1 -0
  76. package/dist/adapters/summarizer.js +121 -0
  77. package/dist/adapters/summarizer.js.map +1 -0
  78. package/dist/adapters/token-estimator.d.ts +63 -0
  79. package/dist/adapters/token-estimator.d.ts.map +1 -0
  80. package/dist/adapters/token-estimator.js +37 -0
  81. package/dist/adapters/token-estimator.js.map +1 -0
  82. package/dist/builder/context-builder.d.ts +186 -0
  83. package/dist/builder/context-builder.d.ts.map +1 -0
  84. package/dist/builder/context-builder.js +305 -0
  85. package/dist/builder/context-builder.js.map +1 -0
  86. package/dist/builder/context-fork.d.ts +166 -0
  87. package/dist/builder/context-fork.d.ts.map +1 -0
  88. package/dist/builder/context-fork.js +282 -0
  89. package/dist/builder/context-fork.js.map +1 -0
  90. package/dist/builder/index.d.ts +6 -0
  91. package/dist/builder/index.d.ts.map +1 -0
  92. package/dist/builder/index.js +22 -0
  93. package/dist/builder/index.js.map +1 -0
  94. package/dist/codecs/base.d.ts +18 -0
  95. package/dist/codecs/base.d.ts.map +1 -0
  96. package/dist/codecs/base.js +39 -0
  97. package/dist/codecs/base.js.map +1 -0
  98. package/dist/codecs/conversation-history.codec.d.ts +81 -0
  99. package/dist/codecs/conversation-history.codec.d.ts.map +1 -0
  100. package/dist/codecs/conversation-history.codec.js +89 -0
  101. package/dist/codecs/conversation-history.codec.js.map +1 -0
  102. package/dist/codecs/index.d.ts +31 -0
  103. package/dist/codecs/index.d.ts.map +1 -0
  104. package/dist/codecs/index.js +71 -0
  105. package/dist/codecs/index.js.map +1 -0
  106. package/dist/codecs/redacted-stub.codec.d.ts +32 -0
  107. package/dist/codecs/redacted-stub.codec.d.ts.map +1 -0
  108. package/dist/codecs/redacted-stub.codec.js +64 -0
  109. package/dist/codecs/redacted-stub.codec.js.map +1 -0
  110. package/dist/codecs/structured-reference.codec.d.ts +40 -0
  111. package/dist/codecs/structured-reference.codec.d.ts.map +1 -0
  112. package/dist/codecs/structured-reference.codec.js +81 -0
  113. package/dist/codecs/structured-reference.codec.js.map +1 -0
  114. package/dist/codecs/system-rules.codec.d.ts +32 -0
  115. package/dist/codecs/system-rules.codec.d.ts.map +1 -0
  116. package/dist/codecs/system-rules.codec.js +62 -0
  117. package/dist/codecs/system-rules.codec.js.map +1 -0
  118. package/dist/codecs/tool-output.codec.d.ts +66 -0
  119. package/dist/codecs/tool-output.codec.d.ts.map +1 -0
  120. package/dist/codecs/tool-output.codec.js +95 -0
  121. package/dist/codecs/tool-output.codec.js.map +1 -0
  122. package/dist/codecs/tool-schema.codec.d.ts +36 -0
  123. package/dist/codecs/tool-schema.codec.d.ts.map +1 -0
  124. package/dist/codecs/tool-schema.codec.js +74 -0
  125. package/dist/codecs/tool-schema.codec.js.map +1 -0
  126. package/dist/codecs/unsafe-text.codec.d.ts +28 -0
  127. package/dist/codecs/unsafe-text.codec.d.ts.map +1 -0
  128. package/dist/codecs/unsafe-text.codec.js +63 -0
  129. package/dist/codecs/unsafe-text.codec.js.map +1 -0
  130. package/dist/graph/context-graph.d.ts +121 -0
  131. package/dist/graph/context-graph.d.ts.map +1 -0
  132. package/dist/graph/context-graph.js +166 -0
  133. package/dist/graph/context-graph.js.map +1 -0
  134. package/dist/graph/index.d.ts +8 -0
  135. package/dist/graph/index.d.ts.map +1 -0
  136. package/dist/graph/index.js +24 -0
  137. package/dist/graph/index.js.map +1 -0
  138. package/dist/graph/kind-order.d.ts +60 -0
  139. package/dist/graph/kind-order.d.ts.map +1 -0
  140. package/dist/graph/kind-order.js +113 -0
  141. package/dist/graph/kind-order.js.map +1 -0
  142. package/dist/graph/queries.d.ts +68 -0
  143. package/dist/graph/queries.d.ts.map +1 -0
  144. package/dist/graph/queries.js +240 -0
  145. package/dist/graph/queries.js.map +1 -0
  146. package/dist/graph/views.d.ts +90 -0
  147. package/dist/graph/views.d.ts.map +1 -0
  148. package/dist/graph/views.js +173 -0
  149. package/dist/graph/views.js.map +1 -0
  150. package/dist/index.d.ts +16 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +40 -0
  153. package/dist/index.js.map +1 -0
  154. package/dist/pipeline/compactor.d.ts +128 -0
  155. package/dist/pipeline/compactor.d.ts.map +1 -0
  156. package/dist/pipeline/compactor.js +346 -0
  157. package/dist/pipeline/compactor.js.map +1 -0
  158. package/dist/pipeline/index.d.ts +6 -0
  159. package/dist/pipeline/index.d.ts.map +1 -0
  160. package/dist/pipeline/index.js +22 -0
  161. package/dist/pipeline/index.js.map +1 -0
  162. package/dist/pipeline/summarizer.d.ts +18 -0
  163. package/dist/pipeline/summarizer.d.ts.map +1 -0
  164. package/dist/pipeline/summarizer.js +68 -0
  165. package/dist/pipeline/summarizer.js.map +1 -0
  166. package/dist/policies/default-policy.d.ts +29 -0
  167. package/dist/policies/default-policy.d.ts.map +1 -0
  168. package/dist/policies/default-policy.js +58 -0
  169. package/dist/policies/default-policy.js.map +1 -0
  170. package/dist/policies/index.d.ts +5 -0
  171. package/dist/policies/index.d.ts.map +1 -0
  172. package/dist/policies/index.js +21 -0
  173. package/dist/policies/index.js.map +1 -0
  174. package/dist/providers/anthropic-compiler.d.ts +58 -0
  175. package/dist/providers/anthropic-compiler.d.ts.map +1 -0
  176. package/dist/providers/anthropic-compiler.js +182 -0
  177. package/dist/providers/anthropic-compiler.js.map +1 -0
  178. package/dist/providers/capabilities.d.ts +54 -0
  179. package/dist/providers/capabilities.d.ts.map +1 -0
  180. package/dist/providers/capabilities.js +87 -0
  181. package/dist/providers/capabilities.js.map +1 -0
  182. package/dist/providers/gemini-compiler.d.ts +51 -0
  183. package/dist/providers/gemini-compiler.d.ts.map +1 -0
  184. package/dist/providers/gemini-compiler.js +206 -0
  185. package/dist/providers/gemini-compiler.js.map +1 -0
  186. package/dist/providers/index.d.ts +8 -0
  187. package/dist/providers/index.d.ts.map +1 -0
  188. package/dist/providers/index.js +24 -0
  189. package/dist/providers/index.js.map +1 -0
  190. package/dist/providers/openai-compiler.d.ts +46 -0
  191. package/dist/providers/openai-compiler.d.ts.map +1 -0
  192. package/dist/providers/openai-compiler.js +149 -0
  193. package/dist/providers/openai-compiler.js.map +1 -0
  194. package/dist/types/attachment.d.ts +62 -0
  195. package/dist/types/attachment.d.ts.map +1 -0
  196. package/dist/types/attachment.js +6 -0
  197. package/dist/types/attachment.js.map +1 -0
  198. package/dist/types/block.d.ts +61 -0
  199. package/dist/types/block.d.ts.map +1 -0
  200. package/dist/types/block.js +8 -0
  201. package/dist/types/block.js.map +1 -0
  202. package/dist/types/codec.d.ts +58 -0
  203. package/dist/types/codec.d.ts.map +1 -0
  204. package/dist/types/codec.js +6 -0
  205. package/dist/types/codec.js.map +1 -0
  206. package/dist/types/compiled.d.ts +91 -0
  207. package/dist/types/compiled.d.ts.map +1 -0
  208. package/dist/types/compiled.js +6 -0
  209. package/dist/types/compiled.js.map +1 -0
  210. package/dist/types/hash.d.ts +24 -0
  211. package/dist/types/hash.d.ts.map +1 -0
  212. package/dist/types/hash.js +49 -0
  213. package/dist/types/hash.js.map +1 -0
  214. package/dist/types/index.d.ts +10 -0
  215. package/dist/types/index.d.ts.map +1 -0
  216. package/dist/types/index.js +26 -0
  217. package/dist/types/index.js.map +1 -0
  218. package/dist/types/policy.d.ts +128 -0
  219. package/dist/types/policy.d.ts.map +1 -0
  220. package/dist/types/policy.js +55 -0
  221. package/dist/types/policy.js.map +1 -0
  222. package/package.json +55 -0
  223. package/postcss.config.js +4 -0
  224. package/src/__tests__/attachment-selector.test.ts +559 -0
  225. package/src/__tests__/cache-breakpoints.test.ts +566 -0
  226. package/src/__tests__/codecs.test.ts +417 -0
  227. package/src/__tests__/compactor.test.ts +608 -0
  228. package/src/__tests__/context-graph.test.ts +383 -0
  229. package/src/__tests__/hash.test.ts +274 -0
  230. package/src/__tests__/integration.test.ts +866 -0
  231. package/src/__tests__/kind-order.test.ts +312 -0
  232. package/src/__tests__/phase2-integration.test.ts +253 -0
  233. package/src/__tests__/queries.test.ts +387 -0
  234. package/src/__tests__/token-estimator.test.ts +326 -0
  235. package/src/adapters/anthropic-estimator.ts +125 -0
  236. package/src/adapters/attachment-resolver.ts +295 -0
  237. package/src/adapters/attachment-selector.ts +218 -0
  238. package/src/adapters/gemini-estimator.ts +93 -0
  239. package/src/adapters/index.ts +12 -0
  240. package/src/adapters/memory-store.ts +299 -0
  241. package/src/adapters/openai-estimator.ts +105 -0
  242. package/src/adapters/summarizer.ts +250 -0
  243. package/src/adapters/token-estimator.ts +74 -0
  244. package/src/builder/context-builder.ts +467 -0
  245. package/src/builder/context-fork.ts +471 -0
  246. package/src/builder/index.ts +6 -0
  247. package/src/codecs/base.ts +36 -0
  248. package/src/codecs/conversation-history.codec.ts +108 -0
  249. package/src/codecs/index.ts +57 -0
  250. package/src/codecs/redacted-stub.codec.ts +76 -0
  251. package/src/codecs/structured-reference.codec.ts +96 -0
  252. package/src/codecs/system-rules.codec.ts +74 -0
  253. package/src/codecs/tool-output.codec.ts +109 -0
  254. package/src/codecs/tool-schema.codec.ts +87 -0
  255. package/src/codecs/unsafe-text.codec.ts +74 -0
  256. package/src/graph/context-graph.ts +205 -0
  257. package/src/graph/index.ts +8 -0
  258. package/src/graph/kind-order.ts +125 -0
  259. package/src/graph/queries.ts +306 -0
  260. package/src/graph/views.ts +255 -0
  261. package/src/index.ts +31 -0
  262. package/src/pipeline/compactor.ts +563 -0
  263. package/src/pipeline/index.ts +6 -0
  264. package/src/pipeline/summarizer.ts +76 -0
  265. package/src/policies/default-policy.ts +69 -0
  266. package/src/policies/index.ts +5 -0
  267. package/src/providers/anthropic-compiler.ts +294 -0
  268. package/src/providers/capabilities.ts +144 -0
  269. package/src/providers/gemini-compiler.ts +272 -0
  270. package/src/providers/index.ts +8 -0
  271. package/src/providers/openai-compiler.ts +191 -0
  272. package/src/types/attachment.ts +86 -0
  273. package/src/types/block.ts +84 -0
  274. package/src/types/codec.ts +68 -0
  275. package/src/types/compiled.ts +109 -0
  276. package/src/types/hash.ts +58 -0
  277. package/src/types/index.ts +10 -0
  278. package/src/types/policy.ts +194 -0
  279. package/tsconfig.json +21 -0
  280. package/vitest.config.ts +21 -0
@@ -0,0 +1,417 @@
1
+ /**
2
+ * Unit tests for built-in codecs.
3
+ *
4
+ * Tests canonicalization, hash computation, validation, and rendering for all codecs.
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import {
9
+ SystemRulesCodec,
10
+ ToolSchemaCodec,
11
+ StructuredReferenceCodec,
12
+ ConversationHistoryCodec,
13
+ ToolOutputCodec,
14
+ RedactedStubCodec,
15
+ UnsafeTextCodec,
16
+ BUILT_IN_CODECS,
17
+ getCodec,
18
+ registerCodec,
19
+ } from '../codecs/index.js';
20
+ import type { ContextBlock } from '../types/block.js';
21
+ import type { BlockCodec } from '../types/codec.js';
22
+
23
+ describe('Codecs', () => {
24
+ describe('SystemRulesCodec', () => {
25
+ it('should canonicalize payload deterministically', () => {
26
+ const payload1 = { text: ' System rules ', priority: 1, cacheable: true };
27
+ const payload2 = { text: 'System rules', cacheable: true, priority: 1 };
28
+
29
+ const canon1 = SystemRulesCodec.canonicalize(payload1);
30
+ const canon2 = SystemRulesCodec.canonicalize(payload2);
31
+
32
+ expect(canon1).toEqual(canon2);
33
+ });
34
+
35
+ it('should produce consistent hash for same input', () => {
36
+ const payload = { text: 'Test rules', priority: 1 };
37
+
38
+ const canon1 = SystemRulesCodec.canonicalize(payload);
39
+ const canon2 = SystemRulesCodec.canonicalize(payload);
40
+
41
+ const hash1 = SystemRulesCodec.hash(canon1);
42
+ const hash2 = SystemRulesCodec.hash(canon2);
43
+
44
+ expect(hash1).toBe(hash2);
45
+ });
46
+
47
+ it('should validate correct payload', () => {
48
+ const payload = { text: 'Valid rules' };
49
+
50
+ const validated = SystemRulesCodec.validate(payload);
51
+
52
+ expect(validated).toEqual({ text: 'Valid rules' });
53
+ });
54
+
55
+ it('should reject invalid payload', () => {
56
+ const invalid = { text: 123 }; // text must be string
57
+
58
+ expect(() => SystemRulesCodec.validate(invalid)).toThrow();
59
+ });
60
+
61
+ it('should render for all providers', () => {
62
+ const block: ContextBlock<any> = {
63
+ blockHash: 'test',
64
+ meta: {
65
+ kind: 'pinned',
66
+ sensitivity: 'public',
67
+ codecId: 'system-rules',
68
+ codecVersion: '1.0.0',
69
+ createdAt: Date.now(),
70
+ },
71
+ payload: { text: 'System rules', cacheable: true },
72
+ };
73
+
74
+ const rendered = SystemRulesCodec.render(block);
75
+
76
+ expect(rendered.anthropic).toBeDefined();
77
+ expect(rendered.openai).toBeDefined();
78
+ expect(rendered.gemini).toBeDefined();
79
+
80
+ // Anthropic includes cache control
81
+ expect(rendered.anthropic).toHaveProperty('cache_control');
82
+ // OpenAI is a system message
83
+ expect(rendered.openai).toHaveProperty('role', 'system');
84
+ // Gemini is a string
85
+ expect(typeof rendered.gemini).toBe('string');
86
+ });
87
+ });
88
+
89
+ describe('ToolSchemaCodec', () => {
90
+ it('should canonicalize tool schema deterministically', () => {
91
+ const payload1 = {
92
+ name: 'test_tool',
93
+ description: 'A test tool',
94
+ inputSchema: { type: 'object', properties: { arg: { type: 'string' } } },
95
+ };
96
+ const payload2 = {
97
+ name: 'test_tool',
98
+ description: 'A test tool',
99
+ inputSchema: { properties: { arg: { type: 'string' } }, type: 'object' },
100
+ };
101
+
102
+ const canon1 = ToolSchemaCodec.canonicalize(payload1);
103
+ const canon2 = ToolSchemaCodec.canonicalize(payload2);
104
+
105
+ // Should be equal after key sorting
106
+ expect(canon1).toEqual(canon2);
107
+ });
108
+
109
+ it('should validate tool schema', () => {
110
+ const payload = {
111
+ name: 'test',
112
+ description: 'A test tool',
113
+ inputSchema: { type: 'object' },
114
+ };
115
+
116
+ const validated = ToolSchemaCodec.validate(payload);
117
+
118
+ expect(validated.name).toBe('test');
119
+ expect(validated.description).toBe('A test tool');
120
+ });
121
+
122
+ it('should render tool schemas for all providers', () => {
123
+ const block: ContextBlock<any> = {
124
+ blockHash: 'test',
125
+ meta: {
126
+ kind: 'reference',
127
+ sensitivity: 'public',
128
+ codecId: 'tool-schema',
129
+ codecVersion: '1.0.0',
130
+ createdAt: Date.now(),
131
+ },
132
+ payload: {
133
+ name: 'test_tool',
134
+ description: 'Test',
135
+ inputSchema: { type: 'object' },
136
+ },
137
+ };
138
+
139
+ const rendered = ToolSchemaCodec.render(block);
140
+
141
+ expect(rendered.anthropic).toBeDefined();
142
+ expect(rendered.openai).toBeDefined();
143
+ expect(rendered.gemini).toBeDefined();
144
+ });
145
+ });
146
+
147
+ describe('StructuredReferenceCodec', () => {
148
+ it('should canonicalize structured reference', () => {
149
+ const payload = {
150
+ title: 'Test File',
151
+ content: ' content ',
152
+ mimeType: 'text/plain',
153
+ sourceUrl: 'file:///test.txt',
154
+ };
155
+
156
+ const canon = StructuredReferenceCodec.canonicalize(payload);
157
+
158
+ expect(canon).toHaveProperty('title', 'Test File');
159
+ expect(canon).toHaveProperty('content', ' content '); // Content not trimmed
160
+ });
161
+
162
+ it('should validate structured reference', () => {
163
+ const payload = {
164
+ title: 'Test',
165
+ content: 'Test content',
166
+ mimeType: 'text/plain',
167
+ };
168
+
169
+ const validated = StructuredReferenceCodec.validate(payload);
170
+
171
+ expect(validated.title).toBe('Test');
172
+ });
173
+ });
174
+
175
+ describe('ConversationHistoryCodec', () => {
176
+ it('should canonicalize conversation history', () => {
177
+ const payload = {
178
+ messages: [
179
+ { role: 'user' as const, content: 'Hello' },
180
+ { role: 'assistant' as const, content: 'Hi' },
181
+ ],
182
+ };
183
+
184
+ const canon = ConversationHistoryCodec.canonicalize(payload);
185
+
186
+ expect(canon).toHaveProperty('messages');
187
+ expect(Array.isArray((canon as any).messages)).toBe(true);
188
+ });
189
+
190
+ it('should validate conversation history', () => {
191
+ const payload = {
192
+ messages: [{ role: 'user' as const, content: 'Test' }],
193
+ };
194
+
195
+ const validated = ConversationHistoryCodec.validate(payload);
196
+
197
+ expect(validated.messages).toHaveLength(1);
198
+ });
199
+
200
+ it('should render conversation history for all providers', () => {
201
+ const block: ContextBlock<any> = {
202
+ blockHash: 'test',
203
+ meta: {
204
+ kind: 'history',
205
+ sensitivity: 'public',
206
+ codecId: 'conversation-history',
207
+ codecVersion: '1.0.0',
208
+ createdAt: Date.now(),
209
+ },
210
+ payload: {
211
+ messages: [
212
+ { role: 'user' as const, content: 'Hello' },
213
+ { role: 'assistant' as const, content: 'Hi there!' },
214
+ ],
215
+ },
216
+ };
217
+
218
+ const rendered = ConversationHistoryCodec.render(block);
219
+
220
+ expect(rendered.anthropic).toBeDefined();
221
+ expect(rendered.openai).toBeDefined();
222
+ expect(rendered.gemini).toBeDefined();
223
+
224
+ // Should render as message arrays
225
+ expect(Array.isArray(rendered.anthropic)).toBe(true);
226
+ expect(Array.isArray(rendered.openai)).toBe(true);
227
+ expect(Array.isArray(rendered.gemini)).toBe(true);
228
+ });
229
+ });
230
+
231
+ describe('ToolOutputCodec', () => {
232
+ it('should canonicalize tool output', () => {
233
+ const payload = {
234
+ toolName: 'test_tool',
235
+ toolCallId: 'call_123',
236
+ output: { success: true as const, result: 'success' },
237
+ durationMs: 100,
238
+ };
239
+
240
+ const canon = ToolOutputCodec.canonicalize(payload);
241
+
242
+ expect(canon).toHaveProperty('toolName', 'test_tool');
243
+ expect(canon).toHaveProperty('output');
244
+ });
245
+
246
+ it('should validate tool output', () => {
247
+ const payload = {
248
+ toolName: 'test',
249
+ toolCallId: 'call_123',
250
+ output: { success: true as const, result: 'result' },
251
+ };
252
+
253
+ const validated = ToolOutputCodec.validate(payload);
254
+
255
+ expect(validated.toolName).toBe('test');
256
+ });
257
+
258
+ it('should compact large tool outputs', () => {
259
+ const largeOutput = 'x'.repeat(10000);
260
+ const payload = {
261
+ toolName: 'test',
262
+ toolCallId: 'call_123',
263
+ output: { success: true as const, result: largeOutput },
264
+ };
265
+
266
+ const canon = ToolOutputCodec.canonicalize(payload);
267
+
268
+ // Canon should have output property
269
+ expect((canon as any).output).toBeDefined();
270
+ });
271
+ });
272
+
273
+ describe('RedactedStubCodec', () => {
274
+ it('should canonicalize redacted stub', () => {
275
+ const payload = {
276
+ originalBlockHash: 'hash123',
277
+ reason: 'Sensitivity redaction',
278
+ placeholder: '[REDACTED]',
279
+ };
280
+
281
+ const canon = RedactedStubCodec.canonicalize(payload);
282
+
283
+ expect(canon).toHaveProperty('originalBlockHash', 'hash123');
284
+ expect(canon).toHaveProperty('reason');
285
+ });
286
+
287
+ it('should validate redacted stub', () => {
288
+ const payload = {
289
+ originalBlockHash: 'hash123',
290
+ reason: 'Test',
291
+ };
292
+
293
+ const validated = RedactedStubCodec.validate(payload);
294
+
295
+ expect(validated.originalBlockHash).toBe('hash123');
296
+ });
297
+
298
+ it('should render redacted stub for all providers', () => {
299
+ const block: ContextBlock<any> = {
300
+ blockHash: 'test',
301
+ meta: {
302
+ kind: 'memory',
303
+ sensitivity: 'public',
304
+ codecId: 'redacted-stub',
305
+ codecVersion: '1.0.0',
306
+ createdAt: Date.now(),
307
+ },
308
+ payload: {
309
+ originalBlockHash: 'hash123',
310
+ reason: 'Redacted',
311
+ },
312
+ };
313
+
314
+ const rendered = RedactedStubCodec.render(block);
315
+
316
+ // Should render as placeholder text
317
+ expect(rendered.anthropic).toBeDefined();
318
+ expect(rendered.openai).toBeDefined();
319
+ expect(rendered.gemini).toBeDefined();
320
+ });
321
+ });
322
+
323
+ describe('UnsafeTextCodec', () => {
324
+ it('should canonicalize unsafe text', () => {
325
+ const payload = {
326
+ text: ' Test text ',
327
+ };
328
+
329
+ const canon = UnsafeTextCodec.canonicalize(payload);
330
+
331
+ expect(canon).toHaveProperty('text', 'Test text'); // trimmed
332
+ });
333
+
334
+ it('should validate unsafe text', () => {
335
+ const payload = {
336
+ text: 'Test',
337
+ };
338
+
339
+ const validated = UnsafeTextCodec.validate(payload);
340
+
341
+ expect(validated.text).toBe('Test');
342
+ });
343
+
344
+ it('should render unsafe text for all providers', () => {
345
+ const block: ContextBlock<any> = {
346
+ blockHash: 'test',
347
+ meta: {
348
+ kind: 'turn',
349
+ sensitivity: 'public',
350
+ codecId: 'unsafe-text',
351
+ codecVersion: '1.0.0',
352
+ createdAt: Date.now(),
353
+ },
354
+ payload: {
355
+ text: 'User message',
356
+ },
357
+ };
358
+
359
+ const rendered = UnsafeTextCodec.render(block);
360
+
361
+ expect(rendered.anthropic).toBeDefined();
362
+ expect(rendered.openai).toBeDefined();
363
+ expect(rendered.gemini).toBeDefined();
364
+ });
365
+ });
366
+
367
+ describe('Codec Registry', () => {
368
+ it('should retrieve built-in codecs', () => {
369
+ expect(getCodec('system-rules')).toBe(SystemRulesCodec);
370
+ expect(getCodec('tool-schema')).toBe(ToolSchemaCodec);
371
+ expect(getCodec('structured-reference')).toBe(StructuredReferenceCodec);
372
+ expect(getCodec('conversation-history')).toBe(ConversationHistoryCodec);
373
+ expect(getCodec('tool-output')).toBe(ToolOutputCodec);
374
+ expect(getCodec('redacted-stub')).toBe(RedactedStubCodec);
375
+ expect(getCodec('unsafe-text')).toBe(UnsafeTextCodec);
376
+ });
377
+
378
+ it('should return undefined for unknown codec', () => {
379
+ expect(getCodec('non-existent')).toBeUndefined();
380
+ });
381
+
382
+ it('should register custom codec', () => {
383
+ const customCodec: BlockCodec<string> = {
384
+ codecId: 'custom-test',
385
+ version: '1.0.0',
386
+ payloadSchema: null as any,
387
+ canonicalize: (payload) => payload,
388
+ hash: (canon) => 'test-hash',
389
+ validate: (payload) => payload as string,
390
+ render: () => ({
391
+ anthropic: 'test',
392
+ openai: 'test',
393
+ gemini: 'test',
394
+ }),
395
+ };
396
+
397
+ registerCodec(customCodec);
398
+
399
+ expect(getCodec('custom-test')).toBe(customCodec);
400
+ });
401
+
402
+ it('should throw on duplicate codec registration', () => {
403
+ expect(() => registerCodec(SystemRulesCodec)).toThrow('already registered');
404
+ });
405
+
406
+ it('should have all built-in codecs in registry', () => {
407
+ expect(Object.keys(BUILT_IN_CODECS)).toHaveLength(8);
408
+ expect(BUILT_IN_CODECS['system-rules']).toBeDefined();
409
+ expect(BUILT_IN_CODECS['tool-schema']).toBeDefined();
410
+ expect(BUILT_IN_CODECS['structured-reference']).toBeDefined();
411
+ expect(BUILT_IN_CODECS['conversation-history']).toBeDefined();
412
+ expect(BUILT_IN_CODECS['tool-output']).toBeDefined();
413
+ expect(BUILT_IN_CODECS['redacted-stub']).toBeDefined();
414
+ expect(BUILT_IN_CODECS['unsafe-text']).toBeDefined();
415
+ });
416
+ });
417
+ });