@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,93 @@
1
+ /**
2
+ * GeminiTokenEstimator: High-confidence token counting via tiktoken.
3
+ *
4
+ * Uses tiktoken with gpt-4 encoding as a good approximation for Gemini.
5
+ */
6
+
7
+ import { encoding_for_model } from 'tiktoken';
8
+ import type { ContextBlock } from '../types/block.js';
9
+ import type { TokenEstimator, TokenEstimate } from './token-estimator.js';
10
+ import {
11
+ heuristicTokenCount,
12
+ serializeBlockForEstimation,
13
+ } from './token-estimator.js';
14
+
15
+ /**
16
+ * GeminiTokenEstimator using tiktoken (gpt-4 encoding as approximation).
17
+ */
18
+ export class GeminiTokenEstimator implements TokenEstimator {
19
+ /**
20
+ * Estimate tokens for a single block using tiktoken.
21
+ *
22
+ * @param block - Block to estimate
23
+ * @returns Token estimate (high confidence)
24
+ */
25
+ async estimateBlock(block: ContextBlock<unknown>): Promise<TokenEstimate> {
26
+ try {
27
+ const text = serializeBlockForEstimation(block);
28
+ const encoding = encoding_for_model('gpt-4');
29
+ const tokens = encoding.encode(text);
30
+ encoding.free(); // Important: free memory
31
+
32
+ return {
33
+ tokens: tokens.length,
34
+ confidence: 'high',
35
+ };
36
+ } catch (error) {
37
+ // Fallback to heuristic on error
38
+ console.warn(
39
+ '[GeminiTokenEstimator] tiktoken error, falling back to heuristic:',
40
+ error
41
+ );
42
+ const text = serializeBlockForEstimation(block);
43
+ return {
44
+ tokens: heuristicTokenCount(text),
45
+ confidence: 'low',
46
+ };
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Estimate tokens for multiple blocks.
52
+ *
53
+ * @param blocks - Blocks to estimate
54
+ * @returns Token estimate (high confidence if all succeed)
55
+ */
56
+ async estimate(blocks: ContextBlock<unknown>[]): Promise<TokenEstimate> {
57
+ if (blocks.length === 0) {
58
+ return { tokens: 0, confidence: 'high' };
59
+ }
60
+
61
+ try {
62
+ const combinedText = blocks
63
+ .map((block) => serializeBlockForEstimation(block))
64
+ .join('\n\n');
65
+
66
+ const encoding = encoding_for_model('gpt-4');
67
+ const tokens = encoding.encode(combinedText);
68
+ encoding.free(); // Important: free memory
69
+
70
+ return {
71
+ tokens: tokens.length,
72
+ confidence: 'high',
73
+ };
74
+ } catch (error) {
75
+ // Fallback: sum individual heuristic estimates
76
+ console.warn(
77
+ '[GeminiTokenEstimator] tiktoken error, falling back to heuristic:',
78
+ error
79
+ );
80
+
81
+ let totalTokens = 0;
82
+ for (const block of blocks) {
83
+ const text = serializeBlockForEstimation(block);
84
+ totalTokens += heuristicTokenCount(text);
85
+ }
86
+
87
+ return {
88
+ tokens: totalTokens,
89
+ confidence: 'low',
90
+ };
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Adapter exports (token estimators, attachment resolvers, etc.)
3
+ */
4
+
5
+ export * from './token-estimator.js';
6
+ export * from './anthropic-estimator.js';
7
+ export * from './openai-estimator.js';
8
+ export * from './gemini-estimator.js';
9
+ export * from './attachment-resolver.js';
10
+ export * from './attachment-selector.js';
11
+ export * from './summarizer.js';
12
+ export * from './memory-store.js';
@@ -0,0 +1,299 @@
1
+ /**
2
+ * MemoryStore: Persistence interface for context blocks.
3
+ *
4
+ * Provides a common interface for storing and retrieving context blocks
5
+ * with support for querying and deletion.
6
+ *
7
+ * External implementations:
8
+ * - @foundry/context-store-postgres - PostgreSQL backend
9
+ * - @foundry/context-store-redis - Redis backend
10
+ * - @foundry/context-store-memory - In-memory backend (testing)
11
+ */
12
+
13
+ import type { ContextBlock } from '../types/block.js';
14
+ import type { BlockQuery } from '../graph/queries.js';
15
+
16
+ /**
17
+ * Memory store query options.
18
+ */
19
+ export interface MemoryQueryOptions {
20
+ /** Block query filter */
21
+ query?: BlockQuery;
22
+
23
+ /** Maximum number of results */
24
+ limit?: number;
25
+
26
+ /** Offset for pagination */
27
+ offset?: number;
28
+
29
+ /** Sort by field (default: createdAt descending) */
30
+ sortBy?: 'createdAt' | 'blockHash';
31
+
32
+ /** Sort order */
33
+ sortOrder?: 'asc' | 'desc';
34
+ }
35
+
36
+ /**
37
+ * Memory store save options.
38
+ */
39
+ export interface MemorySaveOptions {
40
+ /** Time-to-live (seconds, optional) */
41
+ ttl?: number;
42
+
43
+ /** Overwrite existing block with same hash */
44
+ overwrite?: boolean;
45
+ }
46
+
47
+ /**
48
+ * MemoryStore interface for context block persistence.
49
+ */
50
+ export interface MemoryStore {
51
+ /**
52
+ * Save a block to the store.
53
+ *
54
+ * @param block - Block to save
55
+ * @param options - Save options
56
+ * @returns Promise resolving when save is complete
57
+ */
58
+ save<TPayload>(
59
+ block: ContextBlock<TPayload>,
60
+ options?: MemorySaveOptions
61
+ ): Promise<void>;
62
+
63
+ /**
64
+ * Load a block by hash.
65
+ *
66
+ * @param blockHash - Block hash to load
67
+ * @returns Block if found, undefined otherwise
68
+ */
69
+ load<TPayload = unknown>(
70
+ blockHash: string
71
+ ): Promise<ContextBlock<TPayload> | undefined>;
72
+
73
+ /**
74
+ * Query blocks matching criteria.
75
+ *
76
+ * @param options - Query options
77
+ * @returns Matching blocks
78
+ */
79
+ query(options?: MemoryQueryOptions): Promise<ContextBlock<unknown>[]>;
80
+
81
+ /**
82
+ * Delete a block by hash.
83
+ *
84
+ * @param blockHash - Block hash to delete
85
+ * @returns True if block was deleted, false if not found
86
+ */
87
+ delete(blockHash: string): Promise<boolean>;
88
+
89
+ /**
90
+ * Delete all blocks matching a query.
91
+ *
92
+ * @param query - Block query filter
93
+ * @returns Number of blocks deleted
94
+ */
95
+ deleteMany(query: BlockQuery): Promise<number>;
96
+
97
+ /**
98
+ * Check if a block exists in the store.
99
+ *
100
+ * @param blockHash - Block hash to check
101
+ * @returns True if block exists
102
+ */
103
+ exists(blockHash: string): Promise<boolean>;
104
+
105
+ /**
106
+ * Get store statistics.
107
+ *
108
+ * @returns Store stats
109
+ */
110
+ getStats(): Promise<{
111
+ blockCount: number;
112
+ totalSizeBytes?: number;
113
+ }>;
114
+
115
+ /**
116
+ * Clear all blocks from the store.
117
+ * Use with caution!
118
+ *
119
+ * @returns Promise resolving when clear is complete
120
+ */
121
+ clear(): Promise<void>;
122
+ }
123
+
124
+ /**
125
+ * InMemoryStore: Simple in-memory implementation for testing.
126
+ *
127
+ * WARNING: Not suitable for production use. Data is lost on process restart.
128
+ */
129
+ export class InMemoryStore implements MemoryStore {
130
+ private readonly blocks: Map<string, ContextBlock<unknown>>;
131
+ private readonly ttls: Map<string, number>; // blockHash -> expiresAt timestamp
132
+
133
+ constructor() {
134
+ this.blocks = new Map();
135
+ this.ttls = new Map();
136
+ }
137
+
138
+ /**
139
+ * Clean up expired blocks (called before operations).
140
+ */
141
+ private cleanupExpired(): void {
142
+ const now = Math.floor(Date.now() / 1000);
143
+ const expiredHashes: string[] = [];
144
+
145
+ for (const [blockHash, expiresAt] of this.ttls.entries()) {
146
+ if (expiresAt <= now) {
147
+ expiredHashes.push(blockHash);
148
+ }
149
+ }
150
+
151
+ for (const hash of expiredHashes) {
152
+ this.blocks.delete(hash);
153
+ this.ttls.delete(hash);
154
+ }
155
+ }
156
+
157
+ async save<TPayload>(
158
+ block: ContextBlock<TPayload>,
159
+ options?: MemorySaveOptions
160
+ ): Promise<void> {
161
+ this.cleanupExpired();
162
+
163
+ const { blockHash } = block;
164
+
165
+ // Check for existing block
166
+ if (this.blocks.has(blockHash) && !options?.overwrite) {
167
+ // Block already exists and overwrite is false - skip
168
+ return;
169
+ }
170
+
171
+ // Save block
172
+ this.blocks.set(blockHash, block as ContextBlock<unknown>);
173
+
174
+ // Set TTL if provided
175
+ if (options?.ttl) {
176
+ const expiresAt = Math.floor(Date.now() / 1000) + options.ttl;
177
+ this.ttls.set(blockHash, expiresAt);
178
+ } else {
179
+ // Remove TTL if overwriting without TTL
180
+ this.ttls.delete(blockHash);
181
+ }
182
+ }
183
+
184
+ async load<TPayload = unknown>(
185
+ blockHash: string
186
+ ): Promise<ContextBlock<TPayload> | undefined> {
187
+ this.cleanupExpired();
188
+ return this.blocks.get(blockHash) as ContextBlock<TPayload> | undefined;
189
+ }
190
+
191
+ async query(options?: MemoryQueryOptions): Promise<ContextBlock<unknown>[]> {
192
+ this.cleanupExpired();
193
+
194
+ let results = Array.from(this.blocks.values());
195
+
196
+ // Apply query filter if provided
197
+ if (options?.query) {
198
+ const { matchesQuery } = await import('../graph/queries.js');
199
+ results = results.filter((block) =>
200
+ matchesQuery(block, options.query!, {
201
+ select: () => [],
202
+ hasBlock: (hash: string) => this.blocks.has(hash),
203
+ } as any)
204
+ );
205
+ }
206
+
207
+ // Sort results
208
+ const sortBy = options?.sortBy ?? 'createdAt';
209
+ const sortOrder = options?.sortOrder ?? 'desc';
210
+
211
+ results.sort((a, b) => {
212
+ let comparison = 0;
213
+
214
+ if (sortBy === 'createdAt') {
215
+ comparison = a.meta.createdAt - b.meta.createdAt;
216
+ } else if (sortBy === 'blockHash') {
217
+ comparison = a.blockHash.localeCompare(b.blockHash);
218
+ }
219
+
220
+ return sortOrder === 'asc' ? comparison : -comparison;
221
+ });
222
+
223
+ // Apply limit and offset
224
+ const offset = options?.offset ?? 0;
225
+ const limit = options?.limit ?? results.length;
226
+
227
+ return results.slice(offset, offset + limit);
228
+ }
229
+
230
+ async delete(blockHash: string): Promise<boolean> {
231
+ this.cleanupExpired();
232
+
233
+ const existed = this.blocks.delete(blockHash);
234
+ this.ttls.delete(blockHash);
235
+
236
+ return existed;
237
+ }
238
+
239
+ async deleteMany(query: BlockQuery): Promise<number> {
240
+ this.cleanupExpired();
241
+
242
+ const matchingBlocks = await this.query({ query });
243
+ let deleted = 0;
244
+
245
+ for (const block of matchingBlocks) {
246
+ if (await this.delete(block.blockHash)) {
247
+ deleted++;
248
+ }
249
+ }
250
+
251
+ return deleted;
252
+ }
253
+
254
+ async exists(blockHash: string): Promise<boolean> {
255
+ this.cleanupExpired();
256
+ return this.blocks.has(blockHash);
257
+ }
258
+
259
+ async getStats(): Promise<{ blockCount: number; totalSizeBytes?: number }> {
260
+ this.cleanupExpired();
261
+
262
+ // Estimate total size in bytes
263
+ let totalSizeBytes = 0;
264
+
265
+ for (const block of this.blocks.values()) {
266
+ // Rough estimate: JSON.stringify length
267
+ totalSizeBytes += JSON.stringify(block).length;
268
+ }
269
+
270
+ return {
271
+ blockCount: this.blocks.size,
272
+ totalSizeBytes,
273
+ };
274
+ }
275
+
276
+ async clear(): Promise<void> {
277
+ this.blocks.clear();
278
+ this.ttls.clear();
279
+ }
280
+
281
+ /**
282
+ * Get all blocks (testing only).
283
+ *
284
+ * @returns All blocks in the store
285
+ */
286
+ getAllBlocks(): ContextBlock<unknown>[] {
287
+ this.cleanupExpired();
288
+ return Array.from(this.blocks.values());
289
+ }
290
+ }
291
+
292
+ /**
293
+ * Create an in-memory store for testing.
294
+ *
295
+ * @returns InMemoryStore instance
296
+ */
297
+ export function createInMemoryStore(): MemoryStore {
298
+ return new InMemoryStore();
299
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * OpenAITokenEstimator: High-confidence token counting via tiktoken.
3
+ *
4
+ * Uses tiktoken library for accurate token counts.
5
+ */
6
+
7
+ import { encoding_for_model } from 'tiktoken';
8
+ import type { TiktokenModel } from 'tiktoken';
9
+ import type { ContextBlock } from '../types/block.js';
10
+ import type { TokenEstimator, TokenEstimate } from './token-estimator.js';
11
+ import {
12
+ heuristicTokenCount,
13
+ serializeBlockForEstimation,
14
+ } from './token-estimator.js';
15
+
16
+ /**
17
+ * OpenAITokenEstimator using tiktoken.
18
+ */
19
+ export class OpenAITokenEstimator implements TokenEstimator {
20
+ private readonly model: TiktokenModel;
21
+
22
+ /**
23
+ * Create an OpenAITokenEstimator.
24
+ *
25
+ * @param model - Model name (e.g., 'gpt-4', 'gpt-3.5-turbo')
26
+ */
27
+ constructor(model: TiktokenModel = 'gpt-4') {
28
+ this.model = model;
29
+ }
30
+
31
+ /**
32
+ * Estimate tokens for a single block using tiktoken.
33
+ *
34
+ * @param block - Block to estimate
35
+ * @returns Token estimate (high confidence)
36
+ */
37
+ async estimateBlock(block: ContextBlock<unknown>): Promise<TokenEstimate> {
38
+ try {
39
+ const text = serializeBlockForEstimation(block);
40
+ const encoding = encoding_for_model(this.model);
41
+ const tokens = encoding.encode(text);
42
+ encoding.free(); // Important: free memory
43
+
44
+ return {
45
+ tokens: tokens.length,
46
+ confidence: 'high',
47
+ };
48
+ } catch (error) {
49
+ // Fallback to heuristic on error
50
+ console.warn(
51
+ '[OpenAITokenEstimator] tiktoken error, falling back to heuristic:',
52
+ error
53
+ );
54
+ const text = serializeBlockForEstimation(block);
55
+ return {
56
+ tokens: heuristicTokenCount(text),
57
+ confidence: 'low',
58
+ };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Estimate tokens for multiple blocks.
64
+ *
65
+ * @param blocks - Blocks to estimate
66
+ * @returns Token estimate (high confidence if all succeed)
67
+ */
68
+ async estimate(blocks: ContextBlock<unknown>[]): Promise<TokenEstimate> {
69
+ if (blocks.length === 0) {
70
+ return { tokens: 0, confidence: 'high' };
71
+ }
72
+
73
+ try {
74
+ const combinedText = blocks
75
+ .map((block) => serializeBlockForEstimation(block))
76
+ .join('\n\n');
77
+
78
+ const encoding = encoding_for_model(this.model);
79
+ const tokens = encoding.encode(combinedText);
80
+ encoding.free(); // Important: free memory
81
+
82
+ return {
83
+ tokens: tokens.length,
84
+ confidence: 'high',
85
+ };
86
+ } catch (error) {
87
+ // Fallback: sum individual heuristic estimates
88
+ console.warn(
89
+ '[OpenAITokenEstimator] tiktoken error, falling back to heuristic:',
90
+ error
91
+ );
92
+
93
+ let totalTokens = 0;
94
+ for (const block of blocks) {
95
+ const text = serializeBlockForEstimation(block);
96
+ totalTokens += heuristicTokenCount(text);
97
+ }
98
+
99
+ return {
100
+ tokens: totalTokens,
101
+ confidence: 'low',
102
+ };
103
+ }
104
+ }
105
+ }