@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,471 @@
1
+ /**
2
+ * ContextFork: Create sub-agent contexts with sensitivity enforcement.
3
+ *
4
+ * Enables forking contexts for sub-agents with automatic sensitivity redaction,
5
+ * budget overrides, and schema-enforced output validation.
6
+ */
7
+
8
+ import { createHash } from 'crypto';
9
+ import { z, type ZodSchema } from 'zod';
10
+ import type { ContextGraph } from '../graph/context-graph.js';
11
+ import type { ContextView } from '../graph/views.js';
12
+ import type { ContextBlock, SensitivityLevel, BlockRef } from '../types/block.js';
13
+ import type { ModelRef } from '../types/policy.js';
14
+ import { RedactedStubCodec, type RedactedStubPayload } from '../codecs/redacted-stub.codec.js';
15
+ import { computeBlockHash } from '../types/hash.js';
16
+
17
+ /**
18
+ * Sub-agent task definition with schema-enforced output.
19
+ */
20
+ export interface SubAgentTask<TOutput = unknown> {
21
+ /** Task instruction for the sub-agent */
22
+ instruction: string;
23
+
24
+ /** Expected output schema (Zod schema, required) */
25
+ expectedOutputSchema: ZodSchema<TOutput>;
26
+
27
+ /** Maximum output tokens (optional) */
28
+ maxOutputTokens?: number;
29
+
30
+ /** Forbidden fields that must not appear in output */
31
+ forbiddenFields?: string[];
32
+ }
33
+
34
+ /**
35
+ * Fork options for creating sub-agent contexts.
36
+ */
37
+ export interface ContextForkOptions {
38
+ /** Unique agent identifier */
39
+ agentId: string;
40
+
41
+ /** Human-readable fork name */
42
+ name: string;
43
+
44
+ /** Model to use for this fork */
45
+ model: ModelRef;
46
+
47
+ /** Inherit query from parent context */
48
+ inheritQuery?: boolean;
49
+
50
+ /** Include conversation history in fork */
51
+ includeHistory?: boolean;
52
+
53
+ /** Include state blocks in fork */
54
+ includeState?: boolean;
55
+
56
+ /** Maximum sensitivity level to include (default: 'public') */
57
+ maxSensitivity?: SensitivityLevel;
58
+
59
+ /** Token budget override (optional) */
60
+ budgetOverride?: number;
61
+ }
62
+
63
+ /**
64
+ * LLM usage statistics.
65
+ */
66
+ export interface UsageStats {
67
+ /** Input tokens */
68
+ inputTokens: number;
69
+
70
+ /** Output tokens */
71
+ outputTokens: number;
72
+
73
+ /** Total tokens */
74
+ totalTokens: number;
75
+ }
76
+
77
+ /**
78
+ * Provenance information for fork result.
79
+ */
80
+ export interface ForkProvenance {
81
+ /** Source view hash */
82
+ sourceViewHash: string;
83
+
84
+ /** Execution hash (deterministic) */
85
+ executionHash: string;
86
+
87
+ /** Fork creation timestamp */
88
+ forkedAt: number;
89
+
90
+ /** Fork completion timestamp */
91
+ completedAt: number;
92
+ }
93
+
94
+ /**
95
+ * Fork execution result.
96
+ */
97
+ export interface ForkResult<TOutput = unknown> {
98
+ /** Agent ID */
99
+ agentId: string;
100
+
101
+ /** Model used */
102
+ model: ModelRef;
103
+
104
+ /** Task summary */
105
+ summary: string;
106
+
107
+ /** Structured output (validated against schema) */
108
+ output: TOutput;
109
+
110
+ /** Generated artifacts (optional) */
111
+ artifacts?: Array<{
112
+ name: string;
113
+ type: string;
114
+ content: string;
115
+ }>;
116
+
117
+ /** Citations to source blocks (block hashes) */
118
+ citations?: string[];
119
+
120
+ /** Usage statistics */
121
+ usage: UsageStats;
122
+
123
+ /** Provenance information */
124
+ provenance: ForkProvenance;
125
+ }
126
+
127
+ /**
128
+ * Sensitivity level ordering (for comparison).
129
+ */
130
+ const SENSITIVITY_ORDER: Record<SensitivityLevel, number> = {
131
+ public: 0,
132
+ internal: 1,
133
+ restricted: 2,
134
+ };
135
+
136
+ /**
137
+ * Check if a sensitivity level exceeds the maximum allowed level.
138
+ *
139
+ * @param level - Sensitivity level to check
140
+ * @param maxLevel - Maximum allowed level
141
+ * @returns True if level exceeds maxLevel
142
+ */
143
+ function exceedsSensitivityLevel(
144
+ level: SensitivityLevel,
145
+ maxLevel: SensitivityLevel
146
+ ): boolean {
147
+ return SENSITIVITY_ORDER[level] > SENSITIVITY_ORDER[maxLevel];
148
+ }
149
+
150
+ /**
151
+ * Create a redacted stub block for sensitive content.
152
+ *
153
+ * @param originalBlock - Original block to redact
154
+ * @param reason - Redaction reason
155
+ * @returns Redacted stub block
156
+ */
157
+ function createRedactedStub(
158
+ originalBlock: ContextBlock<unknown>,
159
+ reason: string
160
+ ): ContextBlock<RedactedStubPayload> {
161
+ const payload: RedactedStubPayload = {
162
+ originalBlockHash: originalBlock.blockHash,
163
+ reason,
164
+ placeholder: '[REDACTED - Sensitive content removed]',
165
+ };
166
+
167
+ const meta = {
168
+ kind: originalBlock.meta.kind,
169
+ sensitivity: 'public' as const,
170
+ codecId: RedactedStubCodec.codecId,
171
+ codecVersion: RedactedStubCodec.version,
172
+ createdAt: Math.floor(Date.now() / 1000),
173
+ source: originalBlock.meta.source,
174
+ tags: originalBlock.meta.tags,
175
+ };
176
+
177
+ const blockHash = computeBlockHash(meta, payload, RedactedStubCodec);
178
+
179
+ return {
180
+ blockHash,
181
+ meta,
182
+ payload,
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Filter view blocks by sensitivity level.
188
+ * Blocks exceeding maxSensitivity are replaced with redacted stubs.
189
+ *
190
+ * @param view - Source context view
191
+ * @param maxSensitivity - Maximum allowed sensitivity level
192
+ * @returns Filtered blocks with redacted stubs
193
+ */
194
+ export function filterBySensitivity(
195
+ view: ContextView,
196
+ maxSensitivity: SensitivityLevel
197
+ ): ContextBlock<unknown>[] {
198
+ return view.blocks.map((block) => {
199
+ if (exceedsSensitivityLevel(block.meta.sensitivity, maxSensitivity)) {
200
+ return createRedactedStub(
201
+ block,
202
+ `Sensitivity level '${block.meta.sensitivity}' exceeds maximum '${maxSensitivity}'`
203
+ );
204
+ }
205
+ return block;
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Compute deterministic execution hash.
211
+ * Hash includes: model + compiledViewHash + instruction + schemaHash + toolsetVersion
212
+ *
213
+ * @param model - Model reference
214
+ * @param viewHash - Compiled view hash
215
+ * @param instruction - Task instruction
216
+ * @param schemaHash - Output schema hash
217
+ * @param toolsetVersion - Toolset version (optional)
218
+ * @returns Hex-encoded SHA-256 hash
219
+ */
220
+ export function computeExecutionHash(
221
+ model: ModelRef,
222
+ viewHash: string,
223
+ instruction: string,
224
+ schemaHash: string,
225
+ toolsetVersion?: string
226
+ ): string {
227
+ const combined = {
228
+ model: `${model.provider}:${model.model}`,
229
+ viewHash,
230
+ instruction,
231
+ schemaHash,
232
+ toolsetVersion: toolsetVersion ?? 'none',
233
+ };
234
+
235
+ return createHash('sha256')
236
+ .update(JSON.stringify(combined))
237
+ .digest('hex');
238
+ }
239
+
240
+ /**
241
+ * Compute schema hash for deterministic execution tracking.
242
+ *
243
+ * @param schema - Zod schema
244
+ * @returns Hex-encoded SHA-256 hash
245
+ */
246
+ export function computeSchemaHash(schema: ZodSchema): string {
247
+ // Use schema description for hashing (simplified approach)
248
+ // In production, you'd want to serialize the schema structure
249
+ const schemaDesc = schema.description ?? JSON.stringify(schema._def);
250
+
251
+ return createHash('sha256')
252
+ .update(schemaDesc)
253
+ .digest('hex');
254
+ }
255
+
256
+ /**
257
+ * ContextFork: Create sub-agent contexts with sensitivity enforcement.
258
+ */
259
+ export class ContextFork {
260
+ constructor(
261
+ private readonly graph: ContextGraph,
262
+ private readonly parentView: ContextView
263
+ ) {}
264
+
265
+ /**
266
+ * Create a fork with sensitivity filtering and budget override.
267
+ *
268
+ * @param options - Fork options
269
+ * @returns Filtered context view for sub-agent
270
+ */
271
+ async createFork(options: ContextForkOptions): Promise<ContextView> {
272
+ const maxSensitivity = options.maxSensitivity ?? 'public';
273
+
274
+ // Filter blocks by sensitivity
275
+ const filteredBlocks = filterBySensitivity(this.parentView, maxSensitivity);
276
+
277
+ // Apply additional filters based on options
278
+ let finalBlocks = filteredBlocks;
279
+
280
+ if (!options.includeHistory) {
281
+ finalBlocks = finalBlocks.filter((block) => block.meta.kind !== 'history');
282
+ }
283
+
284
+ if (!options.includeState) {
285
+ finalBlocks = finalBlocks.filter((block) => block.meta.kind !== 'state');
286
+ }
287
+
288
+ // Create new view with filtered blocks
289
+ // Re-compute stable prefix hash
290
+ const stablePrefixHash = createHash('sha256')
291
+ .update(finalBlocks.map((b) => b.blockHash).join('|'))
292
+ .digest('hex');
293
+
294
+ const forkedView: ContextView = {
295
+ blocks: finalBlocks,
296
+ stablePrefixHash,
297
+ createdAt: Math.floor(Date.now() / 1000),
298
+ tokenEstimate: options.budgetOverride
299
+ ? { tokens: options.budgetOverride, confidence: 'high', truncated: false }
300
+ : this.parentView.tokenEstimate,
301
+ };
302
+
303
+ return forkedView;
304
+ }
305
+
306
+ /**
307
+ * Execute a sub-agent task with schema validation.
308
+ *
309
+ * @param task - Sub-agent task definition
310
+ * @param options - Fork options
311
+ * @param executor - Async executor function (calls LLM)
312
+ * @returns Fork result with validated output
313
+ */
314
+ async executeFork<TOutput>(
315
+ task: SubAgentTask<TOutput>,
316
+ options: ContextForkOptions,
317
+ executor: (instruction: string, view: ContextView) => Promise<{
318
+ output: unknown;
319
+ summary: string;
320
+ artifacts?: Array<{ name: string; type: string; content: string }>;
321
+ citations?: string[];
322
+ usage: UsageStats;
323
+ }>
324
+ ): Promise<ForkResult<TOutput>> {
325
+ const forkedAt = Math.floor(Date.now() / 1000);
326
+
327
+ // Create fork
328
+ const forkedView = await this.createFork(options);
329
+
330
+ // Validate forbidden fields
331
+ if (task.forbiddenFields && task.forbiddenFields.length > 0) {
332
+ // Add validation to instruction
333
+ const forbiddenFieldsNote = `\n\nIMPORTANT: Your output MUST NOT include these fields: ${task.forbiddenFields.join(', ')}`;
334
+ const enhancedInstruction = task.instruction + forbiddenFieldsNote;
335
+
336
+ // Execute with enhanced instruction
337
+ const executionResult = await executor(enhancedInstruction, forkedView);
338
+
339
+ // Validate output against schema
340
+ const validatedOutput = task.expectedOutputSchema.parse(executionResult.output);
341
+
342
+ // Check for forbidden fields in output
343
+ const outputStr = JSON.stringify(validatedOutput);
344
+ for (const field of task.forbiddenFields) {
345
+ if (outputStr.includes(field)) {
346
+ throw new Error(
347
+ `Fork execution failed: Output contains forbidden field '${field}'`
348
+ );
349
+ }
350
+ }
351
+
352
+ const completedAt = Math.floor(Date.now() / 1000);
353
+
354
+ // Compute execution hash
355
+ const schemaHash = computeSchemaHash(task.expectedOutputSchema);
356
+ const executionHash = computeExecutionHash(
357
+ options.model,
358
+ forkedView.stablePrefixHash,
359
+ task.instruction,
360
+ schemaHash
361
+ );
362
+
363
+ return {
364
+ agentId: options.agentId,
365
+ model: options.model,
366
+ summary: executionResult.summary,
367
+ output: validatedOutput,
368
+ artifacts: executionResult.artifacts,
369
+ citations: executionResult.citations,
370
+ usage: executionResult.usage,
371
+ provenance: {
372
+ sourceViewHash: this.parentView.stablePrefixHash,
373
+ executionHash,
374
+ forkedAt,
375
+ completedAt,
376
+ },
377
+ };
378
+ } else {
379
+ // Execute without forbidden field validation
380
+ const executionResult = await executor(task.instruction, forkedView);
381
+
382
+ // Validate output against schema
383
+ const validatedOutput = task.expectedOutputSchema.parse(executionResult.output);
384
+
385
+ const completedAt = Math.floor(Date.now() / 1000);
386
+
387
+ // Compute execution hash
388
+ const schemaHash = computeSchemaHash(task.expectedOutputSchema);
389
+ const executionHash = computeExecutionHash(
390
+ options.model,
391
+ forkedView.stablePrefixHash,
392
+ task.instruction,
393
+ schemaHash
394
+ );
395
+
396
+ return {
397
+ agentId: options.agentId,
398
+ model: options.model,
399
+ summary: executionResult.summary,
400
+ output: validatedOutput,
401
+ artifacts: executionResult.artifacts,
402
+ citations: executionResult.citations,
403
+ usage: executionResult.usage,
404
+ provenance: {
405
+ sourceViewHash: this.parentView.stablePrefixHash,
406
+ executionHash,
407
+ forkedAt,
408
+ completedAt,
409
+ },
410
+ };
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Ingest fork result back into parent graph.
416
+ * Adds result as a memory block with provenance tracking.
417
+ *
418
+ * @param result - Fork result to ingest
419
+ * @param asMemory - Add as memory block (default: true)
420
+ * @returns Block hash of ingested result
421
+ */
422
+ ingestForkResult<TOutput>(
423
+ result: ForkResult<TOutput>,
424
+ asMemory = true
425
+ ): string {
426
+ // Create a structured reference block for the fork result
427
+ // This would use a codec designed for fork results
428
+ // For now, we'll use a simplified approach
429
+
430
+ const payload = {
431
+ agentId: result.agentId,
432
+ model: result.model,
433
+ summary: result.summary,
434
+ output: result.output,
435
+ artifacts: result.artifacts,
436
+ citations: result.citations,
437
+ usage: result.usage,
438
+ provenance: result.provenance,
439
+ };
440
+
441
+ const meta = {
442
+ kind: asMemory ? ('memory' as const) : ('reference' as const),
443
+ sensitivity: 'public' as const,
444
+ codecId: 'fork-result',
445
+ codecVersion: '1.0.0',
446
+ createdAt: Math.floor(Date.now() / 1000),
447
+ source: result.agentId,
448
+ tags: ['fork-result', result.model.provider],
449
+ };
450
+
451
+ // Compute block hash (without codec for now)
452
+ const blockHash = createHash('sha256')
453
+ .update(JSON.stringify({ meta, payload }))
454
+ .digest('hex');
455
+
456
+ const block: ContextBlock<unknown> = {
457
+ blockHash,
458
+ meta,
459
+ payload,
460
+ };
461
+
462
+ // Add to graph with provenance
463
+ const derivedFrom: BlockRef[] = result.citations
464
+ ? result.citations.map((hash) => ({ blockHash: hash }))
465
+ : [];
466
+
467
+ this.graph.addBlock(block, derivedFrom);
468
+
469
+ return blockHash;
470
+ }
471
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Context builder exports.
3
+ */
4
+
5
+ export * from './context-builder.js';
6
+ export * from './context-fork.js';
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Base codec utilities.
3
+ */
4
+
5
+ import { createHash } from 'crypto';
6
+
7
+ /**
8
+ * Default hash implementation: SHA-256 of JSON.stringify(canonicalized).
9
+ *
10
+ * @param canonicalized - Canonicalized payload
11
+ * @returns Hex-encoded SHA-256 hash
12
+ */
13
+ export function defaultHash(canonicalized: unknown): string {
14
+ return createHash('sha256')
15
+ .update(JSON.stringify(canonicalized))
16
+ .digest('hex');
17
+ }
18
+
19
+ /**
20
+ * Sort object keys for deterministic serialization.
21
+ *
22
+ * @param obj - Object to sort
23
+ * @returns Object with sorted keys
24
+ */
25
+ export function sortObjectKeys(obj: Record<string, unknown>): Record<string, unknown> {
26
+ const sorted: Record<string, unknown> = {};
27
+ for (const key of Object.keys(obj).sort()) {
28
+ const value = obj[key];
29
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
30
+ sorted[key] = sortObjectKeys(value as Record<string, unknown>);
31
+ } else {
32
+ sorted[key] = value;
33
+ }
34
+ }
35
+ return sorted;
36
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Conversation history codec (kind: 'history').
3
+ *
4
+ * Previous turns in conversation.
5
+ */
6
+
7
+ import { z } from 'zod';
8
+ import type { BlockCodec, RenderedContent } from '../types/codec.js';
9
+ import type { ContextBlock } from '../types/block.js';
10
+ import { defaultHash, sortObjectKeys } from './base.js';
11
+
12
+ /**
13
+ * Conversation message schema.
14
+ */
15
+ export const ConversationMessageSchema = z.object({
16
+ /** Message role */
17
+ role: z.enum(['user', 'assistant']),
18
+
19
+ /** Message content (text or structured) */
20
+ content: z.union([
21
+ z.string(),
22
+ z.array(z.record(z.unknown())),
23
+ ]),
24
+
25
+ /** Optional message ID */
26
+ messageId: z.string().optional(),
27
+
28
+ /** Optional timestamp */
29
+ timestamp: z.number().optional(),
30
+ });
31
+
32
+ export type ConversationMessage = z.infer<typeof ConversationMessageSchema>;
33
+
34
+ /**
35
+ * Conversation history payload schema.
36
+ */
37
+ export const ConversationHistoryPayloadSchema = z.object({
38
+ /** Array of messages */
39
+ messages: z.array(ConversationMessageSchema),
40
+
41
+ /** Optional summary of earlier messages */
42
+ summary: z.string().optional(),
43
+ });
44
+
45
+ export type ConversationHistoryPayload = z.infer<typeof ConversationHistoryPayloadSchema>;
46
+
47
+ /**
48
+ * Conversation history codec implementation.
49
+ */
50
+ export const ConversationHistoryCodec: BlockCodec<ConversationHistoryPayload> = {
51
+ codecId: 'conversation-history',
52
+ version: '1.0.0',
53
+ payloadSchema: ConversationHistoryPayloadSchema,
54
+
55
+ canonicalize(payload: ConversationHistoryPayload): unknown {
56
+ // Canonicalize messages (exclude volatile fields like timestamp)
57
+ const canonicalMessages = payload.messages.map((msg) => ({
58
+ role: msg.role,
59
+ content: msg.content,
60
+ }));
61
+
62
+ return sortObjectKeys({
63
+ messages: canonicalMessages,
64
+ summary: payload.summary ?? null,
65
+ });
66
+ },
67
+
68
+ hash(canonicalized: unknown): string {
69
+ return defaultHash(canonicalized);
70
+ },
71
+
72
+ render(block: ContextBlock<ConversationHistoryPayload>): RenderedContent {
73
+ const { messages, summary } = block.payload;
74
+
75
+ // Add summary as first message if present
76
+ const allMessages = summary
77
+ ? [{ role: 'user' as const, content: `**Earlier conversation (summary):**\n${summary}` }, ...messages]
78
+ : messages;
79
+
80
+ return {
81
+ // Anthropic: messages array
82
+ anthropic: allMessages.map((msg) => ({
83
+ role: msg.role,
84
+ content: typeof msg.content === 'string'
85
+ ? msg.content
86
+ : msg.content,
87
+ })),
88
+
89
+ // OpenAI: messages array
90
+ openai: allMessages.map((msg) => ({
91
+ role: msg.role,
92
+ content: msg.content,
93
+ })),
94
+
95
+ // Gemini: convert to user/model role
96
+ gemini: allMessages.map((msg) => ({
97
+ role: msg.role === 'assistant' ? 'model' : 'user',
98
+ parts: typeof msg.content === 'string'
99
+ ? [{ text: msg.content }]
100
+ : msg.content,
101
+ })),
102
+ };
103
+ },
104
+
105
+ validate(payload: unknown): ConversationHistoryPayload {
106
+ return ConversationHistoryPayloadSchema.parse(payload);
107
+ },
108
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Built-in codecs for @foundry/context
3
+ */
4
+
5
+ export * from './base.js';
6
+ export * from './system-rules.codec.js';
7
+ export * from './tool-schema.codec.js';
8
+ export * from './structured-reference.codec.js';
9
+ export * from './conversation-history.codec.js';
10
+ export * from './tool-output.codec.js';
11
+ export * from './redacted-stub.codec.js';
12
+ export * from './unsafe-text.codec.js';
13
+
14
+ import type { BlockCodec } from '../types/codec.js';
15
+ import { SystemRulesCodec } from './system-rules.codec.js';
16
+ import { ToolSchemaCodec } from './tool-schema.codec.js';
17
+ import { StructuredReferenceCodec } from './structured-reference.codec.js';
18
+ import { ConversationHistoryCodec } from './conversation-history.codec.js';
19
+ import { ToolOutputCodec } from './tool-output.codec.js';
20
+ import { RedactedStubCodec } from './redacted-stub.codec.js';
21
+ import { UnsafeTextCodec } from './unsafe-text.codec.js';
22
+
23
+ /**
24
+ * Built-in codec registry.
25
+ * Maps codecId -> codec implementation.
26
+ */
27
+ export const BUILT_IN_CODECS: Record<string, BlockCodec> = {
28
+ 'system-rules': SystemRulesCodec,
29
+ 'tool-schema': ToolSchemaCodec,
30
+ 'structured-reference': StructuredReferenceCodec,
31
+ 'conversation-history': ConversationHistoryCodec,
32
+ 'tool-output': ToolOutputCodec,
33
+ 'redacted-stub': RedactedStubCodec,
34
+ 'unsafe-text': UnsafeTextCodec,
35
+ };
36
+
37
+ /**
38
+ * Get codec by ID.
39
+ *
40
+ * @param codecId - Codec identifier
41
+ * @returns Codec implementation, or undefined if not found
42
+ */
43
+ export function getCodec(codecId: string): BlockCodec | undefined {
44
+ return BUILT_IN_CODECS[codecId];
45
+ }
46
+
47
+ /**
48
+ * Register custom codec.
49
+ *
50
+ * @param codec - Custom codec implementation
51
+ */
52
+ export function registerCodec(codec: BlockCodec): void {
53
+ if (BUILT_IN_CODECS[codec.codecId]) {
54
+ throw new Error(`Codec already registered: ${codec.codecId}`);
55
+ }
56
+ BUILT_IN_CODECS[codec.codecId] = codec;
57
+ }