@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,250 @@
1
+ /**
2
+ * Summarizer: Generate summaries with sensitivity filtering.
3
+ *
4
+ * Provides schema-enforced summarization with forbidden field validation.
5
+ */
6
+
7
+ import { createHash } from 'crypto';
8
+ import type { ZodSchema } from 'zod';
9
+ import type { ContextBlock, SensitivityLevel } from '../types/block.js';
10
+ import type { ModelRef } from '../types/policy.js';
11
+
12
+ /**
13
+ * Summarization provenance.
14
+ */
15
+ export interface SummaryProvenance {
16
+ /** Source block hashes */
17
+ derivedFrom: string[];
18
+
19
+ /** Summarization method */
20
+ method: 'summarize';
21
+
22
+ /** Summarizer version */
23
+ version: string;
24
+
25
+ /** Source content snapshot hash */
26
+ snapshotHash: string;
27
+
28
+ /** Creation timestamp */
29
+ createdAt: number;
30
+ }
31
+
32
+ /**
33
+ * Summary block result.
34
+ */
35
+ export interface SummaryBlock<TOutput = unknown> {
36
+ /** Validated summary output */
37
+ summary: TOutput;
38
+
39
+ /** Provenance information */
40
+ provenance: SummaryProvenance;
41
+
42
+ /** Token usage */
43
+ usage: {
44
+ inputTokens: number;
45
+ outputTokens: number;
46
+ totalTokens: number;
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Summarizer interface.
52
+ */
53
+ export interface Summarizer {
54
+ /**
55
+ * Generate a summary of blocks with schema validation.
56
+ *
57
+ * @param blocks - Blocks to summarize
58
+ * @param schema - Expected output schema
59
+ * @param options - Summarization options
60
+ * @returns Summary block
61
+ */
62
+ summarize<TOutput>(
63
+ blocks: ContextBlock<unknown>[],
64
+ schema: ZodSchema<TOutput>,
65
+ options?: SummarizationOptions
66
+ ): Promise<SummaryBlock<TOutput>>;
67
+ }
68
+
69
+ /**
70
+ * Summarization options.
71
+ */
72
+ export interface SummarizationOptions {
73
+ /** Instruction/prompt for summarization */
74
+ instruction?: string;
75
+
76
+ /** Maximum output tokens */
77
+ maxOutputTokens?: number;
78
+
79
+ /** Forbidden fields that must not appear in summary */
80
+ forbiddenFields?: string[];
81
+
82
+ /** Allowed sensitivity levels for summarization */
83
+ allowedForSummarization?: SensitivityLevel[];
84
+ }
85
+
86
+ /**
87
+ * Default summarization options.
88
+ */
89
+ export const DEFAULT_SUMMARIZATION_OPTIONS: Required<
90
+ Omit<SummarizationOptions, 'instruction' | 'maxOutputTokens'>
91
+ > = {
92
+ forbiddenFields: ['raw_messages', 'full_transcript', 'raw_history'],
93
+ allowedForSummarization: ['public'],
94
+ };
95
+
96
+ /**
97
+ * Default summarizer using gpt-5-nano.
98
+ */
99
+ export class DefaultSummarizer implements Summarizer {
100
+ private readonly version = '1.0.0';
101
+
102
+ constructor(
103
+ private readonly modelRef: ModelRef = {
104
+ provider: 'openai',
105
+ model: 'gpt-5-nano',
106
+ },
107
+ private readonly executor?: (
108
+ instruction: string,
109
+ content: string,
110
+ schema: ZodSchema
111
+ ) => Promise<{
112
+ output: unknown;
113
+ usage: { inputTokens: number; outputTokens: number; totalTokens: number };
114
+ }>
115
+ ) {}
116
+
117
+ /**
118
+ * Filter blocks by sensitivity level.
119
+ *
120
+ * @param blocks - Blocks to filter
121
+ * @param allowedLevels - Allowed sensitivity levels
122
+ * @returns Filtered blocks
123
+ */
124
+ private filterBySensitivity(
125
+ blocks: ContextBlock<unknown>[],
126
+ allowedLevels: SensitivityLevel[]
127
+ ): ContextBlock<unknown>[] {
128
+ return blocks.filter((block) => allowedLevels.includes(block.meta.sensitivity));
129
+ }
130
+
131
+ /**
132
+ * Compute snapshot hash from block hashes.
133
+ *
134
+ * @param blocks - Blocks to hash
135
+ * @returns Hex-encoded SHA-256 hash
136
+ */
137
+ private computeSnapshotHash(blocks: ContextBlock<unknown>[]): string {
138
+ const concatenated = blocks.map((b) => b.blockHash).join('|');
139
+ return createHash('sha256').update(concatenated).digest('hex');
140
+ }
141
+
142
+ /**
143
+ * Default executor (throws error - must be provided).
144
+ */
145
+ private async defaultExecutor(
146
+ instruction: string,
147
+ content: string,
148
+ schema: ZodSchema
149
+ ): Promise<{
150
+ output: unknown;
151
+ usage: { inputTokens: number; outputTokens: number; totalTokens: number };
152
+ }> {
153
+ throw new Error(
154
+ 'Summarizer executor not provided. Please provide an executor function in constructor.'
155
+ );
156
+ }
157
+
158
+ async summarize<TOutput>(
159
+ blocks: ContextBlock<unknown>[],
160
+ schema: ZodSchema<TOutput>,
161
+ options?: SummarizationOptions
162
+ ): Promise<SummaryBlock<TOutput>> {
163
+ const opts = {
164
+ ...DEFAULT_SUMMARIZATION_OPTIONS,
165
+ ...options,
166
+ };
167
+
168
+ // Filter blocks by sensitivity
169
+ const filteredBlocks = this.filterBySensitivity(
170
+ blocks,
171
+ opts.allowedForSummarization ?? DEFAULT_SUMMARIZATION_OPTIONS.allowedForSummarization
172
+ );
173
+
174
+ if (filteredBlocks.length === 0) {
175
+ throw new Error('No blocks available for summarization after sensitivity filtering');
176
+ }
177
+
178
+ // Build content string from blocks
179
+ const content = filteredBlocks
180
+ .map((block, idx) => {
181
+ return `[Block ${idx + 1}/${filteredBlocks.length}]\nKind: ${block.meta.kind}\nPayload: ${JSON.stringify(block.payload, null, 2)}`;
182
+ })
183
+ .join('\n\n---\n\n');
184
+
185
+ // Build instruction
186
+ const instruction =
187
+ opts.instruction ??
188
+ 'Summarize the following context blocks concisely. Focus on key information and maintain accuracy.';
189
+
190
+ // Add forbidden fields note if specified
191
+ const enhancedInstruction = opts.forbiddenFields
192
+ ? `${instruction}\n\nIMPORTANT: Your summary MUST NOT include these fields: ${opts.forbiddenFields.join(', ')}`
193
+ : instruction;
194
+
195
+ // Execute summarization
196
+ const executor = this.executor ?? this.defaultExecutor.bind(this);
197
+ const result = await executor(enhancedInstruction, content, schema);
198
+
199
+ // Validate output against schema
200
+ const validatedOutput = schema.parse(result.output);
201
+
202
+ // Check for forbidden fields
203
+ if (opts.forbiddenFields && opts.forbiddenFields.length > 0) {
204
+ const outputStr = JSON.stringify(validatedOutput);
205
+ for (const field of opts.forbiddenFields) {
206
+ if (outputStr.includes(field)) {
207
+ throw new Error(
208
+ `Summarization failed: Output contains forbidden field '${field}'`
209
+ );
210
+ }
211
+ }
212
+ }
213
+
214
+ // Compute snapshot hash
215
+ const snapshotHash = this.computeSnapshotHash(filteredBlocks);
216
+
217
+ return {
218
+ summary: validatedOutput,
219
+ provenance: {
220
+ derivedFrom: filteredBlocks.map((b) => b.blockHash),
221
+ method: 'summarize',
222
+ version: this.version,
223
+ snapshotHash,
224
+ createdAt: Math.floor(Date.now() / 1000),
225
+ },
226
+ usage: result.usage,
227
+ };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Create a summarizer with custom model.
233
+ *
234
+ * @param modelRef - Model reference
235
+ * @param executor - Executor function
236
+ * @returns Summarizer instance
237
+ */
238
+ export function createSummarizer(
239
+ modelRef: ModelRef,
240
+ executor: (
241
+ instruction: string,
242
+ content: string,
243
+ schema: ZodSchema
244
+ ) => Promise<{
245
+ output: unknown;
246
+ usage: { inputTokens: number; outputTokens: number; totalTokens: number };
247
+ }>
248
+ ): Summarizer {
249
+ return new DefaultSummarizer(modelRef, executor);
250
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * TokenEstimator: Single source of truth for token counting.
3
+ *
4
+ * Provides provider-specific token estimation for context blocks.
5
+ * NOT implemented by codecs - this is the only place token counting happens.
6
+ */
7
+
8
+ import type { ContextBlock } from '../types/block.js';
9
+
10
+ /**
11
+ * Token estimation result.
12
+ */
13
+ export interface TokenEstimate {
14
+ /** Estimated token count */
15
+ tokens: number;
16
+
17
+ /**
18
+ * Confidence level:
19
+ * - 'exact': API-provided count (Anthropic)
20
+ * - 'high': tiktoken-based (OpenAI/Gemini)
21
+ * - 'low': Heuristic fallback (chars / 4)
22
+ */
23
+ confidence: 'exact' | 'high' | 'low';
24
+ }
25
+
26
+ /**
27
+ * TokenEstimator interface.
28
+ * All implementations must be async (Anthropic uses API calls).
29
+ */
30
+ export interface TokenEstimator {
31
+ /**
32
+ * Estimate tokens for a list of blocks.
33
+ *
34
+ * @param blocks - Blocks to estimate
35
+ * @returns Token estimate
36
+ */
37
+ estimate(blocks: ContextBlock<unknown>[]): Promise<TokenEstimate>;
38
+
39
+ /**
40
+ * Estimate tokens for a single block.
41
+ *
42
+ * @param block - Block to estimate
43
+ * @returns Token estimate
44
+ */
45
+ estimateBlock(block: ContextBlock<unknown>): Promise<TokenEstimate>;
46
+ }
47
+
48
+ /**
49
+ * Safety multiplier for low-confidence estimates.
50
+ * Applies 1.2x multiplier to heuristic estimates to avoid budget overruns.
51
+ */
52
+ export const LOW_CONFIDENCE_MULTIPLIER = 1.2;
53
+
54
+ /**
55
+ * Heuristic token estimation (chars / 4).
56
+ * Used as fallback when tiktoken/API is unavailable.
57
+ *
58
+ * @param text - Text to estimate
59
+ * @returns Token count
60
+ */
61
+ export function heuristicTokenCount(text: string): number {
62
+ return Math.ceil((text.length / 4) * LOW_CONFIDENCE_MULTIPLIER);
63
+ }
64
+
65
+ /**
66
+ * Serialize a block to text for token estimation.
67
+ * Uses JSON.stringify as a reasonable approximation of rendered content.
68
+ *
69
+ * @param block - Block to serialize
70
+ * @returns Serialized text
71
+ */
72
+ export function serializeBlockForEstimation(block: ContextBlock<unknown>): string {
73
+ return JSON.stringify(block.payload);
74
+ }