@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,728 @@
1
+ "use strict";
2
+ /**
3
+ * Integration tests for @foundry/context library.
4
+ *
5
+ * Tests end-to-end behavior including:
6
+ * - Pipeline purity (compilation determinism)
7
+ * - Compaction provenance
8
+ * - Fork sensitivity redaction
9
+ * - Execution hash reproducibility
10
+ * - Token estimation confidence
11
+ * - Cache breakpoint resolution
12
+ * - Tool output pruning
13
+ * - Attachment budget enforcement
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ const vitest_1 = require("vitest");
17
+ const context_builder_js_1 = require("../builder/context-builder.js");
18
+ const context_fork_js_1 = require("../builder/context-fork.js");
19
+ const compactor_js_1 = require("../pipeline/compactor.js");
20
+ const anthropic_compiler_js_1 = require("../providers/anthropic-compiler.js");
21
+ const token_estimator_js_1 = require("../adapters/token-estimator.js");
22
+ const system_rules_codec_js_1 = require("../codecs/system-rules.codec.js");
23
+ const redacted_stub_codec_js_1 = require("../codecs/redacted-stub.codec.js");
24
+ const attachment_selector_js_1 = require("../adapters/attachment-selector.js");
25
+ const zod_1 = require("zod");
26
+ // Mock token estimator for tests
27
+ class MockTokenEstimator {
28
+ async estimate(blocks) {
29
+ const totalTokens = blocks.reduce((sum, block) => {
30
+ const text = (0, token_estimator_js_1.serializeBlockForEstimation)(block);
31
+ return sum + (0, token_estimator_js_1.heuristicTokenCount)(text);
32
+ }, 0);
33
+ return { tokens: totalTokens, confidence: 'low' };
34
+ }
35
+ async estimateBlock(block) {
36
+ const text = (0, token_estimator_js_1.serializeBlockForEstimation)(block);
37
+ return { tokens: (0, token_estimator_js_1.heuristicTokenCount)(text), confidence: 'low' };
38
+ }
39
+ }
40
+ (0, vitest_1.describe)('Integration Tests', () => {
41
+ (0, vitest_1.describe)('Pipeline purity', () => {
42
+ (0, vitest_1.it)('should produce identical output for repeated compilation with same policy/provider', async () => {
43
+ // Build context with multiple blocks
44
+ const builder = new context_builder_js_1.ContextBuilder();
45
+ builder
46
+ .system({ text: 'You are a helpful assistant.' })
47
+ .history([
48
+ { role: 'user', content: 'Hello' },
49
+ { role: 'assistant', content: 'Hi there!' },
50
+ ])
51
+ .turn('What is the capital of France?');
52
+ const policy = {
53
+ provider: 'anthropic',
54
+ modelId: 'claude-sonnet-4-5',
55
+ contextWindow: 200000,
56
+ completionReserve: 8000,
57
+ overflowStrategy: 'truncate',
58
+ kindPriorities: [],
59
+ sensitivity: {
60
+ maxSensitivity: 'public',
61
+ redactRestricted: true,
62
+ },
63
+ };
64
+ // Create codec registry
65
+ const codecRegistry = new Map();
66
+ codecRegistry.set('system-rules', system_rules_codec_js_1.SystemRulesCodec);
67
+ codecRegistry.set('conversation-history', {
68
+ codecId: 'conversation-history',
69
+ version: '1.0.0',
70
+ payloadSchema: {},
71
+ canonicalize: (p) => p,
72
+ hash: () => 'history-hash',
73
+ render: (block) => ({
74
+ anthropic: block.payload.messages.map((m) => ({
75
+ role: m.role,
76
+ content: m.content,
77
+ })),
78
+ openai: {},
79
+ gemini: {},
80
+ }),
81
+ validate: (p) => p,
82
+ });
83
+ codecRegistry.set('user-turn', {
84
+ codecId: 'user-turn',
85
+ version: '1.0.0',
86
+ payloadSchema: {},
87
+ canonicalize: (p) => p,
88
+ hash: () => 'turn-hash',
89
+ render: (block) => ({
90
+ anthropic: { role: 'user', content: block.payload.text },
91
+ openai: {},
92
+ gemini: {},
93
+ }),
94
+ validate: (p) => p,
95
+ });
96
+ // Compile twice
97
+ const graph = builder.getGraph();
98
+ const view1 = await graph.createView({});
99
+ const view2 = await graph.createView({});
100
+ const compiled1 = (0, anthropic_compiler_js_1.compileAnthropicContext)([...view1.blocks], policy, { codecRegistry });
101
+ const compiled2 = (0, anthropic_compiler_js_1.compileAnthropicContext)([...view2.blocks], policy, { codecRegistry });
102
+ // Verify identical outputs
103
+ (0, vitest_1.expect)(compiled1.messages).toEqual(compiled2.messages);
104
+ (0, vitest_1.expect)(compiled1.system).toEqual(compiled2.system);
105
+ (0, vitest_1.expect)(compiled1.modelId).toBe(compiled2.modelId);
106
+ (0, vitest_1.expect)(compiled1.provider).toBe(compiled2.provider);
107
+ // Verify stable prefix hash
108
+ (0, vitest_1.expect)(view1.stablePrefixHash).toBe(view2.stablePrefixHash);
109
+ });
110
+ (0, vitest_1.it)('should produce different output when blocks change', async () => {
111
+ const builder1 = new context_builder_js_1.ContextBuilder();
112
+ builder1.system({ text: 'You are a helpful assistant.' });
113
+ const builder2 = new context_builder_js_1.ContextBuilder();
114
+ builder2.system({ text: 'You are a coding expert.' });
115
+ const view1 = await builder1.getGraph().createView({});
116
+ const view2 = await builder2.getGraph().createView({});
117
+ // Verify different hashes
118
+ (0, vitest_1.expect)(view1.stablePrefixHash).not.toBe(view2.stablePrefixHash);
119
+ });
120
+ });
121
+ (0, vitest_1.describe)('Compaction provenance', () => {
122
+ (0, vitest_1.it)('should produce valid provenance for compacted blocks', async () => {
123
+ const builder = new context_builder_js_1.ContextBuilder();
124
+ // Add tool output blocks with large content
125
+ for (let i = 0; i < 5; i++) {
126
+ const toolBlock = {
127
+ blockHash: `tool-hash-${i}`,
128
+ meta: {
129
+ kind: 'tool_output',
130
+ sensitivity: 'public',
131
+ codecId: 'tool-output',
132
+ codecVersion: '1.0.0',
133
+ createdAt: Math.floor(Date.now() / 1000) + i,
134
+ },
135
+ payload: {
136
+ tool_name: 'bash',
137
+ output: 'x'.repeat(1000), // Large output
138
+ status: 'success',
139
+ },
140
+ };
141
+ builder.getGraph().addBlock(toolBlock);
142
+ }
143
+ const view = await builder.getGraph().createView({});
144
+ // Compact with tool output pruning
145
+ const compactionConfig = {
146
+ steps: ['tool_output_prune'],
147
+ toolOutputPruning: {
148
+ maxRawTailChars: 100,
149
+ preserveErrorTail: true,
150
+ maxOutputsPerTool: 3,
151
+ },
152
+ };
153
+ const estimator = new MockTokenEstimator();
154
+ const result = await (0, compactor_js_1.compactView)(view, compactionConfig, estimator);
155
+ // Verify compaction happened
156
+ (0, vitest_1.expect)(result.blocks.length).toBeLessThanOrEqual(3);
157
+ (0, vitest_1.expect)(result.removedBlocks.length).toBeGreaterThan(0);
158
+ // Verify replaced blocks have provenance tags
159
+ const compactedBlocks = result.blocks.filter((b) => b.meta.tags?.some((tag) => tag.startsWith('compacted:')));
160
+ (0, vitest_1.expect)(compactedBlocks.length).toBeGreaterThan(0);
161
+ for (const block of compactedBlocks) {
162
+ // Verify compaction tag
163
+ (0, vitest_1.expect)(block.meta.tags).toContain('compacted:tool_output_prune');
164
+ // Verify source indicates compaction
165
+ (0, vitest_1.expect)(block.meta.source).toContain(':compacted');
166
+ // Verify truncated payload
167
+ if (block.payload && typeof block.payload === 'object' && 'output' in block.payload) {
168
+ const output = block.payload.output;
169
+ (0, vitest_1.expect)(output.length).toBeLessThanOrEqual(100 + 50); // Max tail + truncation marker
170
+ }
171
+ }
172
+ });
173
+ (0, vitest_1.it)('should track deduplication correctly', async () => {
174
+ // NOTE: ContextGraph.addBlock is idempotent, so we need to create
175
+ // duplicates in the view directly to test deduplication
176
+ const duplicateBlock = {
177
+ blockHash: 'duplicate-hash',
178
+ meta: {
179
+ kind: 'memory',
180
+ sensitivity: 'public',
181
+ codecId: 'test',
182
+ codecVersion: '1.0.0',
183
+ createdAt: Math.floor(Date.now() / 1000),
184
+ },
185
+ payload: 'duplicate content',
186
+ };
187
+ // Create view with duplicates manually (since graph is idempotent)
188
+ const view = {
189
+ blocks: [duplicateBlock, { ...duplicateBlock }, { ...duplicateBlock }],
190
+ stablePrefixHash: 'test-hash',
191
+ createdAt: Math.floor(Date.now() / 1000),
192
+ };
193
+ // Compact with deduplication
194
+ const compactionConfig = {
195
+ steps: ['dedupe'],
196
+ };
197
+ const estimator = new MockTokenEstimator();
198
+ const result = await (0, compactor_js_1.compactView)(view, compactionConfig, estimator);
199
+ // Verify only one instance remains
200
+ const duplicates = result.blocks.filter((b) => b.blockHash === 'duplicate-hash');
201
+ (0, vitest_1.expect)(duplicates.length).toBe(1);
202
+ // Verify 2 blocks were removed
203
+ (0, vitest_1.expect)(result.removedBlocks.length).toBe(2);
204
+ // Verify report
205
+ (0, vitest_1.expect)(result.report.stepsApplied).toContain('dedupe');
206
+ const dedupeReport = result.report.stepReports.find((r) => r.step === 'dedupe');
207
+ (0, vitest_1.expect)(dedupeReport?.blocksRemoved).toBe(2);
208
+ (0, vitest_1.expect)(dedupeReport?.lossy).toBe(false);
209
+ });
210
+ });
211
+ (0, vitest_1.describe)('Fork sensitivity redaction', () => {
212
+ (0, vitest_1.it)('should replace sensitive blocks with RedactedStubs', async () => {
213
+ const builder = new context_builder_js_1.ContextBuilder();
214
+ // Add public block
215
+ builder.system({ text: 'Public system prompt' }, { sensitivity: 'public' });
216
+ // Add internal blocks
217
+ const internalBlock = {
218
+ blockHash: 'internal-hash',
219
+ meta: {
220
+ kind: 'memory',
221
+ sensitivity: 'internal',
222
+ codecId: 'test',
223
+ codecVersion: '1.0.0',
224
+ createdAt: Math.floor(Date.now() / 1000),
225
+ tags: ['secret'],
226
+ },
227
+ payload: 'Internal company information',
228
+ };
229
+ builder.getGraph().addBlock(internalBlock);
230
+ // Add restricted block
231
+ const restrictedBlock = {
232
+ blockHash: 'restricted-hash',
233
+ meta: {
234
+ kind: 'state',
235
+ sensitivity: 'restricted',
236
+ codecId: 'test',
237
+ codecVersion: '1.0.0',
238
+ createdAt: Math.floor(Date.now() / 1000),
239
+ },
240
+ payload: 'Highly sensitive data',
241
+ };
242
+ builder.getGraph().addBlock(restrictedBlock);
243
+ const view = await builder.getGraph().createView({});
244
+ // Filter by 'public' sensitivity
245
+ const filteredBlocks = (0, context_fork_js_1.filterBySensitivity)(view, 'public');
246
+ // Verify public block is unchanged
247
+ const publicBlocks = filteredBlocks.filter((b) => b.meta.codecId === 'system-rules');
248
+ (0, vitest_1.expect)(publicBlocks.length).toBe(1);
249
+ (0, vitest_1.expect)(publicBlocks[0].meta.sensitivity).toBe('public');
250
+ // Verify internal/restricted blocks are redacted
251
+ const redactedBlocks = filteredBlocks.filter((b) => b.meta.codecId === redacted_stub_codec_js_1.RedactedStubCodec.codecId);
252
+ (0, vitest_1.expect)(redactedBlocks.length).toBe(2);
253
+ for (const block of redactedBlocks) {
254
+ (0, vitest_1.expect)(block.meta.sensitivity).toBe('public');
255
+ (0, vitest_1.expect)(block.payload).toHaveProperty('originalBlockHash');
256
+ (0, vitest_1.expect)(block.payload).toHaveProperty('reason');
257
+ (0, vitest_1.expect)(block.payload.reason).toContain('exceeds maximum');
258
+ }
259
+ });
260
+ (0, vitest_1.it)('should allow internal blocks when maxSensitivity is internal', async () => {
261
+ const builder = new context_builder_js_1.ContextBuilder();
262
+ builder.system({ text: 'Public' }, { sensitivity: 'public' });
263
+ const internalBlock = {
264
+ blockHash: 'internal-hash',
265
+ meta: {
266
+ kind: 'memory',
267
+ sensitivity: 'internal',
268
+ codecId: 'test',
269
+ codecVersion: '1.0.0',
270
+ createdAt: Math.floor(Date.now() / 1000),
271
+ },
272
+ payload: 'Internal',
273
+ };
274
+ builder.getGraph().addBlock(internalBlock);
275
+ const view = await builder.getGraph().createView({});
276
+ const filteredBlocks = (0, context_fork_js_1.filterBySensitivity)(view, 'internal');
277
+ // Verify both blocks are kept
278
+ const redactedBlocks = filteredBlocks.filter((b) => b.meta.codecId === redacted_stub_codec_js_1.RedactedStubCodec.codecId);
279
+ (0, vitest_1.expect)(redactedBlocks.length).toBe(0);
280
+ });
281
+ });
282
+ (0, vitest_1.describe)('Execution hash reproducibility', () => {
283
+ (0, vitest_1.it)('should produce identical executionHash for same inputs', () => {
284
+ const model = { provider: 'anthropic', model: 'claude-sonnet-4-5' };
285
+ const viewHash = 'view-hash-123';
286
+ const instruction = 'Analyze this code';
287
+ const schema = zod_1.z.object({ result: zod_1.z.string() });
288
+ const schemaHash = (0, context_fork_js_1.computeSchemaHash)(schema);
289
+ const hash1 = (0, context_fork_js_1.computeExecutionHash)(model, viewHash, instruction, schemaHash);
290
+ const hash2 = (0, context_fork_js_1.computeExecutionHash)(model, viewHash, instruction, schemaHash);
291
+ (0, vitest_1.expect)(hash1).toBe(hash2);
292
+ (0, vitest_1.expect)(hash1).toMatch(/^[a-f0-9]{64}$/); // Valid SHA-256 hex
293
+ });
294
+ (0, vitest_1.it)('should produce different executionHash when inputs change', () => {
295
+ const model1 = { provider: 'anthropic', model: 'claude-sonnet-4-5' };
296
+ const model2 = { provider: 'anthropic', model: 'claude-opus-4-5' };
297
+ const viewHash = 'view-hash-123';
298
+ const instruction = 'Analyze this code';
299
+ const schema = zod_1.z.object({ result: zod_1.z.string() });
300
+ const schemaHash = (0, context_fork_js_1.computeSchemaHash)(schema);
301
+ const hash1 = (0, context_fork_js_1.computeExecutionHash)(model1, viewHash, instruction, schemaHash);
302
+ const hash2 = (0, context_fork_js_1.computeExecutionHash)(model2, viewHash, instruction, schemaHash);
303
+ (0, vitest_1.expect)(hash1).not.toBe(hash2);
304
+ });
305
+ (0, vitest_1.it)('should include toolset version in hash computation', () => {
306
+ const model = { provider: 'anthropic', model: 'claude-sonnet-4-5' };
307
+ const viewHash = 'view-hash-123';
308
+ const instruction = 'Analyze this code';
309
+ const schema = zod_1.z.object({ result: zod_1.z.string() });
310
+ const schemaHash = (0, context_fork_js_1.computeSchemaHash)(schema);
311
+ const hashWithoutToolset = (0, context_fork_js_1.computeExecutionHash)(model, viewHash, instruction, schemaHash);
312
+ const hashWithToolset = (0, context_fork_js_1.computeExecutionHash)(model, viewHash, instruction, schemaHash, 'v1.2.3');
313
+ (0, vitest_1.expect)(hashWithoutToolset).not.toBe(hashWithToolset);
314
+ });
315
+ });
316
+ (0, vitest_1.describe)('Token estimation confidence', () => {
317
+ (0, vitest_1.it)('should apply safety multiplier for low-confidence estimates', () => {
318
+ const text = 'a'.repeat(100); // 100 chars
319
+ const heuristicTokens = (0, token_estimator_js_1.heuristicTokenCount)(text);
320
+ // Verify multiplier is applied
321
+ const baseEstimate = Math.ceil(100 / 4); // 25 tokens
322
+ const expectedWithMultiplier = Math.ceil(baseEstimate * token_estimator_js_1.LOW_CONFIDENCE_MULTIPLIER);
323
+ (0, vitest_1.expect)(heuristicTokens).toBe(expectedWithMultiplier);
324
+ (0, vitest_1.expect)(heuristicTokens).toBeGreaterThan(baseEstimate);
325
+ });
326
+ (0, vitest_1.it)('should use 1.2x safety multiplier', () => {
327
+ (0, vitest_1.expect)(token_estimator_js_1.LOW_CONFIDENCE_MULTIPLIER).toBe(1.2);
328
+ });
329
+ (0, vitest_1.it)('should estimate tokens consistently for same input', () => {
330
+ const text = 'Test input for token estimation';
331
+ const estimate1 = (0, token_estimator_js_1.heuristicTokenCount)(text);
332
+ const estimate2 = (0, token_estimator_js_1.heuristicTokenCount)(text);
333
+ (0, vitest_1.expect)(estimate1).toBe(estimate2);
334
+ });
335
+ });
336
+ (0, vitest_1.describe)('Cache breakpoint resolution', () => {
337
+ (0, vitest_1.it)('should resolve cache breakpoint to "after last match"', async () => {
338
+ const builder = new context_builder_js_1.ContextBuilder();
339
+ // Add multiple system blocks
340
+ builder.system({ text: 'System 1' }, { tags: ['cacheable'] });
341
+ builder.system({ text: 'System 2' }, { tags: ['cacheable'] });
342
+ builder.system({ text: 'System 3' }, { tags: ['other'] });
343
+ builder.system({ text: 'System 4' }, { tags: ['cacheable'] });
344
+ const view = await builder.getGraph().createView({});
345
+ const policy = {
346
+ provider: 'anthropic',
347
+ modelId: 'claude-sonnet-4-5',
348
+ contextWindow: 200000,
349
+ completionReserve: 8000,
350
+ overflowStrategy: 'truncate',
351
+ kindPriorities: [],
352
+ sensitivity: {
353
+ maxSensitivity: 'public',
354
+ redactRestricted: true,
355
+ },
356
+ };
357
+ // Create codec registry
358
+ const codecRegistry = new Map();
359
+ codecRegistry.set('system-rules', system_rules_codec_js_1.SystemRulesCodec);
360
+ const cacheBreakpoint = {
361
+ tag: 'cacheable',
362
+ };
363
+ const compiled = (0, anthropic_compiler_js_1.compileAnthropicContext)([...view.blocks], policy, {
364
+ codecRegistry,
365
+ cacheBreakpoint,
366
+ });
367
+ // Verify cache_control is on the last cacheable block (index 3)
368
+ (0, vitest_1.expect)(compiled.system).toBeDefined();
369
+ if (compiled.system) {
370
+ const cacheControlBlocks = compiled.system.filter((msg) => msg.cache_control);
371
+ (0, vitest_1.expect)(cacheControlBlocks.length).toBe(1);
372
+ // Last cacheable block should have cache control
373
+ (0, vitest_1.expect)(compiled.system[3]).toHaveProperty('cache_control');
374
+ (0, vitest_1.expect)(compiled.system[3].cache_control).toEqual({ type: 'ephemeral' });
375
+ }
376
+ });
377
+ (0, vitest_1.it)('should handle no matching blocks gracefully', async () => {
378
+ const builder = new context_builder_js_1.ContextBuilder();
379
+ builder.system({ text: 'System 1' });
380
+ const view = await builder.getGraph().createView({});
381
+ const policy = {
382
+ provider: 'anthropic',
383
+ modelId: 'claude-sonnet-4-5',
384
+ contextWindow: 200000,
385
+ completionReserve: 8000,
386
+ overflowStrategy: 'truncate',
387
+ kindPriorities: [],
388
+ sensitivity: {
389
+ maxSensitivity: 'public',
390
+ redactRestricted: true,
391
+ },
392
+ };
393
+ const codecRegistry = new Map();
394
+ codecRegistry.set('system-rules', system_rules_codec_js_1.SystemRulesCodec);
395
+ const cacheBreakpoint = {
396
+ tag: 'nonexistent',
397
+ };
398
+ const compiled = (0, anthropic_compiler_js_1.compileAnthropicContext)([...view.blocks], policy, {
399
+ codecRegistry,
400
+ cacheBreakpoint,
401
+ });
402
+ // Verify no cache control when no blocks match
403
+ if (compiled.system) {
404
+ const cacheControlBlocks = compiled.system.filter((msg) => msg.cache_control);
405
+ (0, vitest_1.expect)(cacheControlBlocks.length).toBe(0);
406
+ }
407
+ });
408
+ });
409
+ (0, vitest_1.describe)('Tool output pruning', () => {
410
+ (0, vitest_1.it)('should enforce maxOutputsPerTool', async () => {
411
+ const builder = new context_builder_js_1.ContextBuilder();
412
+ // Add 10 tool outputs from same tool
413
+ for (let i = 0; i < 10; i++) {
414
+ const toolBlock = {
415
+ blockHash: `tool-hash-${i}`,
416
+ meta: {
417
+ kind: 'tool_output',
418
+ sensitivity: 'public',
419
+ codecId: 'tool-bash',
420
+ codecVersion: '1.0.0',
421
+ createdAt: Math.floor(Date.now() / 1000) + i,
422
+ },
423
+ payload: {
424
+ tool_name: 'bash',
425
+ output: `output ${i}`,
426
+ status: 'success',
427
+ },
428
+ };
429
+ builder.getGraph().addBlock(toolBlock);
430
+ }
431
+ const view = await builder.getGraph().createView({});
432
+ const compactionConfig = {
433
+ steps: ['tool_output_prune'],
434
+ toolOutputPruning: {
435
+ maxRawTailChars: 500,
436
+ preserveErrorTail: true,
437
+ maxOutputsPerTool: 3,
438
+ },
439
+ };
440
+ const estimator = new MockTokenEstimator();
441
+ const result = await (0, compactor_js_1.compactView)(view, compactionConfig, estimator);
442
+ // Verify only 3 tool outputs remain
443
+ const toolOutputs = result.blocks.filter((b) => b.meta.kind === 'tool_output');
444
+ (0, vitest_1.expect)(toolOutputs.length).toBe(3);
445
+ // Verify 7 were removed
446
+ (0, vitest_1.expect)(result.removedBlocks.length).toBe(7);
447
+ // Verify kept blocks are the most recent
448
+ const keptHashes = toolOutputs.map((b) => b.blockHash).sort();
449
+ (0, vitest_1.expect)(keptHashes).toContain('tool-hash-7');
450
+ (0, vitest_1.expect)(keptHashes).toContain('tool-hash-8');
451
+ (0, vitest_1.expect)(keptHashes).toContain('tool-hash-9');
452
+ });
453
+ (0, vitest_1.it)('should preserve error outputs even if over maxOutputsPerTool', async () => {
454
+ // NOTE: The current implementation of pruneToolOutputs keeps the most
455
+ // recent N outputs per tool, but doesn't preserve errors separately.
456
+ // This test verifies that large error outputs preserve the tail.
457
+ const view = {
458
+ blocks: [
459
+ {
460
+ blockHash: 'error-hash',
461
+ meta: {
462
+ kind: 'tool_output',
463
+ sensitivity: 'public',
464
+ codecId: 'tool-bash',
465
+ codecVersion: '1.0.0',
466
+ createdAt: Math.floor(Date.now() / 1000) + 3, // Most recent
467
+ },
468
+ payload: {
469
+ tool_name: 'bash',
470
+ output: 'x'.repeat(1000), // Large output
471
+ status: 'error',
472
+ error: true,
473
+ },
474
+ },
475
+ ],
476
+ stablePrefixHash: 'test-hash',
477
+ createdAt: Math.floor(Date.now() / 1000),
478
+ };
479
+ const compactionConfig = {
480
+ steps: ['tool_output_prune'],
481
+ toolOutputPruning: {
482
+ maxRawTailChars: 100,
483
+ preserveErrorTail: true,
484
+ maxOutputsPerTool: 3,
485
+ },
486
+ };
487
+ const estimator = new MockTokenEstimator();
488
+ const result = await (0, compactor_js_1.compactView)(view, compactionConfig, estimator);
489
+ // Error block should be kept (recent enough)
490
+ const errorBlocks = result.blocks.filter((b) => b.blockHash === 'error-hash');
491
+ (0, vitest_1.expect)(errorBlocks.length).toBe(1);
492
+ // Verify error tail is NOT truncated (preserveErrorTail = true)
493
+ const errorBlock = errorBlocks[0];
494
+ if (errorBlock.payload && typeof errorBlock.payload === 'object' && 'output' in errorBlock.payload) {
495
+ const output = errorBlock.payload.output;
496
+ (0, vitest_1.expect)(output.length).toBe(1000); // Not truncated
497
+ }
498
+ });
499
+ });
500
+ (0, vitest_1.describe)('Attachment budget enforcement', () => {
501
+ (0, vitest_1.it)('should select attachments within budget', async () => {
502
+ const attachments = [
503
+ {
504
+ attachmentId: 'att1',
505
+ filename: 'file1.txt',
506
+ mimeType: 'text/plain',
507
+ sizeBytes: 200,
508
+ storage: 'local',
509
+ storagePath: '/tmp/file1.txt',
510
+ createdAt: Math.floor(Date.now() / 1000),
511
+ text: 'a'.repeat(200), // ~50 tokens
512
+ purpose: 'evidence',
513
+ userMention: true,
514
+ rankScore: 0,
515
+ },
516
+ {
517
+ attachmentId: 'att2',
518
+ filename: 'file2.txt',
519
+ mimeType: 'text/plain',
520
+ sizeBytes: 200,
521
+ storage: 'local',
522
+ storagePath: '/tmp/file2.txt',
523
+ createdAt: Math.floor(Date.now() / 1000),
524
+ text: 'b'.repeat(200), // ~50 tokens
525
+ purpose: 'context',
526
+ userMention: false,
527
+ rankScore: 0,
528
+ },
529
+ {
530
+ attachmentId: 'att3',
531
+ filename: 'file3.txt',
532
+ mimeType: 'text/plain',
533
+ sizeBytes: 200,
534
+ storage: 'local',
535
+ storagePath: '/tmp/file3.txt',
536
+ createdAt: Math.floor(Date.now() / 1000),
537
+ text: 'c'.repeat(200), // ~50 tokens
538
+ purpose: 'input',
539
+ userMention: false,
540
+ rankScore: 0,
541
+ },
542
+ ];
543
+ const policy = {
544
+ maxTokensTotal: 100, // Only room for 2 attachments
545
+ selectionStrategy: {
546
+ rankBy: ['purpose', 'user_mention', 'recency'],
547
+ purposePriority: {
548
+ evidence: 1,
549
+ input: 2,
550
+ context: 3,
551
+ artifact: 4,
552
+ },
553
+ },
554
+ };
555
+ const selector = new attachment_selector_js_1.AttachmentSelector(policy, {});
556
+ const result = await selector.selectAttachments(attachments);
557
+ // Verify only 2 attachments selected (within budget)
558
+ (0, vitest_1.expect)(result.selected.length).toBe(2);
559
+ (0, vitest_1.expect)(result.tokensUsed).toBeLessThanOrEqual(100);
560
+ // Verify highest priority attachments selected
561
+ // evidence + input should be selected (user_mention breaks tie)
562
+ const selectedFilenames = result.selected.map((a) => a.filename);
563
+ (0, vitest_1.expect)(selectedFilenames).toContain('file1.txt'); // evidence + user mention
564
+ (0, vitest_1.expect)(selectedFilenames).toContain('file3.txt'); // input
565
+ // Verify excluded
566
+ (0, vitest_1.expect)(result.excluded.length).toBe(1);
567
+ (0, vitest_1.expect)(result.excluded[0].attachmentId).toBe('att2');
568
+ });
569
+ (0, vitest_1.it)('should respect maxTokensTotal strictly', async () => {
570
+ const largeAttachments = [
571
+ {
572
+ attachmentId: 'large1',
573
+ filename: 'large1.txt',
574
+ mimeType: 'text/plain',
575
+ sizeBytes: 1000,
576
+ storage: 'local',
577
+ storagePath: '/tmp/large1.txt',
578
+ createdAt: Math.floor(Date.now() / 1000),
579
+ text: 'x'.repeat(1000), // ~250 tokens (1000 chars / 4)
580
+ purpose: 'evidence',
581
+ userMention: true,
582
+ rankScore: 0,
583
+ },
584
+ {
585
+ attachmentId: 'large2',
586
+ filename: 'large2.txt',
587
+ mimeType: 'text/plain',
588
+ sizeBytes: 1000,
589
+ storage: 'local',
590
+ storagePath: '/tmp/large2.txt',
591
+ createdAt: Math.floor(Date.now() / 1000),
592
+ text: 'y'.repeat(1000), // ~250 tokens (1000 chars / 4)
593
+ purpose: 'evidence',
594
+ userMention: false,
595
+ rankScore: 0,
596
+ },
597
+ ];
598
+ const policy = {
599
+ maxTokensTotal: 300, // Only room for 1 large attachment (250 tokens)
600
+ selectionStrategy: {
601
+ rankBy: ['user_mention'],
602
+ },
603
+ };
604
+ const selector = new attachment_selector_js_1.AttachmentSelector(policy, {});
605
+ const result = await selector.selectAttachments(largeAttachments);
606
+ // Verify only 1 selected
607
+ (0, vitest_1.expect)(result.selected.length).toBe(1);
608
+ (0, vitest_1.expect)(result.tokensUsed).toBeLessThanOrEqual(300);
609
+ // Verify user-mentioned one is selected
610
+ (0, vitest_1.expect)(result.selected[0].filename).toBe('large1.txt');
611
+ (0, vitest_1.expect)(result.excluded.length).toBe(1);
612
+ });
613
+ });
614
+ (0, vitest_1.describe)('End-to-end pipeline', () => {
615
+ (0, vitest_1.it)('should handle complete build → fork → compile workflow', async () => {
616
+ // Build context
617
+ const builder = new context_builder_js_1.ContextBuilder();
618
+ // Create a test codec with validate method
619
+ const testCodec = {
620
+ codecId: 'test',
621
+ version: '1.0.0',
622
+ payloadSchema: zod_1.z.object({ data: zod_1.z.string() }),
623
+ canonicalize: (p) => p,
624
+ hash: () => 'test-hash',
625
+ render: (block) => ({
626
+ anthropic: { role: 'user', content: JSON.stringify(block.payload) },
627
+ openai: {},
628
+ gemini: {},
629
+ }),
630
+ validate: (p) => p,
631
+ };
632
+ builder
633
+ .system({ text: 'You are a helpful assistant.' })
634
+ .memory(testCodec, { data: 'Internal knowledge' }, { sensitivity: 'internal' })
635
+ .history([
636
+ { role: 'user', content: 'Hello' },
637
+ { role: 'assistant', content: 'Hi!' },
638
+ ])
639
+ .turn('Summarize our conversation');
640
+ // Create view
641
+ const view = await builder.getGraph().createView({});
642
+ (0, vitest_1.expect)(view.blocks.length).toBeGreaterThan(0);
643
+ // Fork with public sensitivity
644
+ const fork = new context_fork_js_1.ContextFork(builder.getGraph(), view);
645
+ const forkedView = await fork.createFork({
646
+ agentId: 'summarizer',
647
+ name: 'Conversation Summarizer',
648
+ model: { provider: 'anthropic', model: 'claude-haiku-4-5' },
649
+ maxSensitivity: 'public',
650
+ includeHistory: true,
651
+ includeState: false,
652
+ });
653
+ // Verify internal blocks are redacted
654
+ const redactedBlocks = forkedView.blocks.filter((b) => b.meta.codecId === redacted_stub_codec_js_1.RedactedStubCodec.codecId);
655
+ (0, vitest_1.expect)(redactedBlocks.length).toBeGreaterThan(0);
656
+ // Verify history is included
657
+ const historyBlocks = forkedView.blocks.filter((b) => b.meta.kind === 'history');
658
+ (0, vitest_1.expect)(historyBlocks.length).toBeGreaterThan(0);
659
+ // Compile for Anthropic
660
+ const policy = {
661
+ provider: 'anthropic',
662
+ modelId: 'claude-haiku-4-5',
663
+ contextWindow: 200000,
664
+ completionReserve: 4000,
665
+ overflowStrategy: 'truncate',
666
+ kindPriorities: [],
667
+ sensitivity: {
668
+ maxSensitivity: 'public',
669
+ redactRestricted: true,
670
+ },
671
+ };
672
+ const codecRegistry = new Map();
673
+ codecRegistry.set('system-rules', system_rules_codec_js_1.SystemRulesCodec);
674
+ codecRegistry.set('redacted-stub', redacted_stub_codec_js_1.RedactedStubCodec);
675
+ codecRegistry.set('conversation-history', {
676
+ codecId: 'conversation-history',
677
+ version: '1.0.0',
678
+ payloadSchema: {},
679
+ canonicalize: (p) => p,
680
+ hash: () => 'history-hash',
681
+ render: (block) => ({
682
+ anthropic: block.payload.messages.map((m) => ({
683
+ role: m.role,
684
+ content: m.content,
685
+ })),
686
+ openai: {},
687
+ gemini: {},
688
+ }),
689
+ validate: (p) => p,
690
+ });
691
+ codecRegistry.set('user-turn', {
692
+ codecId: 'user-turn',
693
+ version: '1.0.0',
694
+ payloadSchema: {},
695
+ canonicalize: (p) => p,
696
+ hash: () => 'turn-hash',
697
+ render: (block) => ({
698
+ anthropic: { role: 'user', content: block.payload.text },
699
+ openai: {},
700
+ gemini: {},
701
+ }),
702
+ validate: (p) => p,
703
+ });
704
+ codecRegistry.set('test', {
705
+ codecId: 'test',
706
+ version: '1.0.0',
707
+ payloadSchema: {},
708
+ canonicalize: (p) => p,
709
+ hash: () => 'test-hash',
710
+ render: (block) => ({
711
+ anthropic: { role: 'user', content: JSON.stringify(block.payload) },
712
+ openai: {},
713
+ gemini: {},
714
+ }),
715
+ validate: (p) => p,
716
+ });
717
+ const compiled = (0, anthropic_compiler_js_1.compileAnthropicContext)([...forkedView.blocks], policy, {
718
+ codecRegistry,
719
+ });
720
+ // Verify compiled output
721
+ (0, vitest_1.expect)(compiled.provider).toBe('anthropic');
722
+ (0, vitest_1.expect)(compiled.modelId).toBe('claude-haiku-4-5');
723
+ (0, vitest_1.expect)(compiled.messages.length).toBeGreaterThan(0);
724
+ (0, vitest_1.expect)(compiled.system).toBeDefined();
725
+ });
726
+ });
727
+ });
728
+ //# sourceMappingURL=integration.test.js.map