@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,5 @@
1
+ /**
2
+ * Policy configuration exports.
3
+ */
4
+
5
+ export * from './default-policy.js';
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Anthropic compiler: Pure compilation to Anthropic message format.
3
+ *
4
+ * Features:
5
+ * - System messages with prompt caching
6
+ * - Cache breakpoint resolution ("after last matching block")
7
+ * - Diagnostics for cache configuration
8
+ */
9
+
10
+ import type { ContextBlock } from '../types/block.js';
11
+ import type { ContextPolicy } from '../types/policy.js';
12
+ import type { CompiledContext, AnthropicCompiledContext } from '../types/compiled.js';
13
+ import type { BlockCodec } from '../types/codec.js';
14
+ import { getProviderCapabilities } from './capabilities.js';
15
+
16
+ /**
17
+ * Cache breakpoint selector for Anthropic prompt caching.
18
+ */
19
+ export interface CacheBreakpointSelector {
20
+ /** Block kind to match */
21
+ kind?: string;
22
+
23
+ /** Codec ID to match */
24
+ codecId?: string;
25
+
26
+ /** Tag to match */
27
+ tag?: string;
28
+
29
+ /** Source to match */
30
+ source?: string;
31
+ }
32
+
33
+ /**
34
+ * Cache breakpoint diagnostic.
35
+ */
36
+ export interface CacheBreakpointDiagnostic {
37
+ /** Diagnostic level */
38
+ level: 'error' | 'warning' | 'info';
39
+
40
+ /** Diagnostic message */
41
+ message: string;
42
+
43
+ /** Matched blocks count */
44
+ matchedBlocks?: number;
45
+
46
+ /** Breakpoint position (if resolved) */
47
+ position?: number;
48
+ }
49
+
50
+ /**
51
+ * Anthropic compilation options.
52
+ */
53
+ export interface AnthropicCompilationOptions {
54
+ /** Cache breakpoint selector (omit = no caching) */
55
+ cacheBreakpoint?: CacheBreakpointSelector;
56
+
57
+ /** Codec registry for rendering */
58
+ codecRegistry: Map<string, BlockCodec<unknown>>;
59
+ }
60
+
61
+ /**
62
+ * Compile context to Anthropic message format.
63
+ * Pure function: same inputs → identical outputs.
64
+ *
65
+ * @param blocks - Ordered blocks from ContextView
66
+ * @param policy - Context policy
67
+ * @param options - Compilation options
68
+ * @returns Anthropic compiled context
69
+ */
70
+ export function compileAnthropicContext(
71
+ blocks: ContextBlock[],
72
+ policy: ContextPolicy,
73
+ options: AnthropicCompilationOptions
74
+ ): AnthropicCompiledContext {
75
+ const capabilities = getProviderCapabilities('anthropic');
76
+
77
+ // Separate system blocks from message blocks
78
+ const systemBlocks = blocks.filter((b) => b.meta.kind === 'pinned');
79
+ const messageBlocks = blocks.filter((b) => b.meta.kind !== 'pinned');
80
+
81
+ // Compile system messages
82
+ const systemMessages = compileSystemMessages(
83
+ systemBlocks,
84
+ options.codecRegistry,
85
+ options.cacheBreakpoint
86
+ );
87
+
88
+ // Compile message blocks
89
+ const messages = compileMessages(messageBlocks, options.codecRegistry);
90
+
91
+ // Calculate tokens (simplified - would use actual estimator in production)
92
+ const estimatedTokens = blocks.length * 100; // Placeholder
93
+
94
+ return {
95
+ provider: 'anthropic',
96
+ modelId: policy.modelId,
97
+ messages,
98
+ system: systemMessages.length > 0 ? systemMessages : undefined,
99
+ estimatedTokens,
100
+ blocks,
101
+ meta: {
102
+ compiledAt: Math.floor(Date.now() / 1000),
103
+ contextWindow: policy.contextWindow,
104
+ completionReserve: policy.completionReserve,
105
+ availableTokens: policy.contextWindow - policy.completionReserve,
106
+ overflowed: false,
107
+ compacted: false,
108
+ truncated: false,
109
+ tokensByKind: {},
110
+ },
111
+ };
112
+ }
113
+
114
+ /**
115
+ * Compile system messages with optional prompt caching.
116
+ *
117
+ * @param blocks - System blocks
118
+ * @param codecRegistry - Codec registry
119
+ * @param cacheBreakpoint - Cache breakpoint selector
120
+ * @returns Anthropic system messages
121
+ */
122
+ function compileSystemMessages(
123
+ blocks: ContextBlock[],
124
+ codecRegistry: Map<string, BlockCodec<unknown>>,
125
+ cacheBreakpoint?: CacheBreakpointSelector
126
+ ): Array<{
127
+ type: 'text';
128
+ text: string;
129
+ cache_control?: { type: 'ephemeral' };
130
+ }> {
131
+ if (blocks.length === 0) {
132
+ return [];
133
+ }
134
+
135
+ const messages: Array<{
136
+ type: 'text';
137
+ text: string;
138
+ cache_control?: { type: 'ephemeral' };
139
+ }> = [];
140
+
141
+ // Resolve cache breakpoint position
142
+ let cacheBreakpointPos = -1;
143
+ const diagnostics: CacheBreakpointDiagnostic[] = [];
144
+
145
+ if (cacheBreakpoint) {
146
+ const result = resolveCacheBreakpoint(blocks, cacheBreakpoint);
147
+ cacheBreakpointPos = result.position;
148
+ diagnostics.push(...result.diagnostics);
149
+ }
150
+
151
+ // Compile each system block
152
+ for (let i = 0; i < blocks.length; i++) {
153
+ const block = blocks[i];
154
+ const codec = codecRegistry.get(block.meta.codecId);
155
+
156
+ if (!codec) {
157
+ console.warn(`[Anthropic] Codec not found: ${block.meta.codecId}`);
158
+ continue;
159
+ }
160
+
161
+ const rendered = codec.render(block);
162
+ const anthropicContent = rendered.anthropic as any;
163
+
164
+ // Add cache control if this is the breakpoint
165
+ const isCacheBreakpoint = i === cacheBreakpointPos;
166
+
167
+ messages.push({
168
+ type: 'text',
169
+ text: anthropicContent.text || JSON.stringify(anthropicContent),
170
+ ...(isCacheBreakpoint && { cache_control: { type: 'ephemeral' } }),
171
+ });
172
+ }
173
+
174
+ return messages;
175
+ }
176
+
177
+ /**
178
+ * Compile message blocks to Anthropic message format.
179
+ *
180
+ * @param blocks - Message blocks
181
+ * @param codecRegistry - Codec registry
182
+ * @returns Anthropic messages
183
+ */
184
+ function compileMessages(
185
+ blocks: ContextBlock[],
186
+ codecRegistry: Map<string, BlockCodec<unknown>>
187
+ ): Array<{
188
+ role: 'user' | 'assistant';
189
+ content: unknown;
190
+ }> {
191
+ const messages: Array<{
192
+ role: 'user' | 'assistant';
193
+ content: unknown;
194
+ }> = [];
195
+
196
+ for (const block of blocks) {
197
+ const codec = codecRegistry.get(block.meta.codecId);
198
+
199
+ if (!codec) {
200
+ console.warn(`[Anthropic] Codec not found: ${block.meta.codecId}`);
201
+ continue;
202
+ }
203
+
204
+ const rendered = codec.render(block);
205
+ const anthropicContent = rendered.anthropic as any;
206
+
207
+ // Handle different block kinds
208
+ if (block.meta.kind === 'history') {
209
+ // History blocks are already in message format
210
+ if (Array.isArray(anthropicContent)) {
211
+ messages.push(...anthropicContent);
212
+ }
213
+ } else if (block.meta.kind === 'turn') {
214
+ // Turn blocks are user messages
215
+ messages.push({
216
+ role: 'user',
217
+ content: anthropicContent.content || anthropicContent,
218
+ });
219
+ } else {
220
+ // Other blocks (reference, memory, state, tool_output) as user messages
221
+ messages.push({
222
+ role: 'user',
223
+ content: typeof anthropicContent === 'string'
224
+ ? anthropicContent
225
+ : JSON.stringify(anthropicContent),
226
+ });
227
+ }
228
+ }
229
+
230
+ return messages;
231
+ }
232
+
233
+ /**
234
+ * Resolve cache breakpoint position using "after last matching block" strategy.
235
+ *
236
+ * @param blocks - System blocks
237
+ * @param selector - Cache breakpoint selector
238
+ * @returns Resolved position and diagnostics
239
+ */
240
+ function resolveCacheBreakpoint(
241
+ blocks: ContextBlock[],
242
+ selector: CacheBreakpointSelector
243
+ ): {
244
+ position: number;
245
+ diagnostics: CacheBreakpointDiagnostic[];
246
+ } {
247
+ const diagnostics: CacheBreakpointDiagnostic[] = [];
248
+
249
+ // Find all matching blocks
250
+ const matchedIndices: number[] = [];
251
+
252
+ for (let i = 0; i < blocks.length; i++) {
253
+ const block = blocks[i];
254
+
255
+ const matches =
256
+ (selector.kind === undefined || block.meta.kind === selector.kind) &&
257
+ (selector.codecId === undefined || block.meta.codecId === selector.codecId) &&
258
+ (selector.tag === undefined || block.meta.tags?.includes(selector.tag)) &&
259
+ (selector.source === undefined || block.meta.source === selector.source);
260
+
261
+ if (matches) {
262
+ matchedIndices.push(i);
263
+ }
264
+ }
265
+
266
+ // Apply "after last matching block" rule
267
+ if (matchedIndices.length === 0) {
268
+ diagnostics.push({
269
+ level: 'warning',
270
+ message: 'No blocks matched cache breakpoint selector',
271
+ matchedBlocks: 0,
272
+ });
273
+ return { position: -1, diagnostics };
274
+ }
275
+
276
+ if (matchedIndices.length > 10) {
277
+ diagnostics.push({
278
+ level: 'warning',
279
+ message: `Many blocks matched (${matchedIndices.length}), using last match`,
280
+ matchedBlocks: matchedIndices.length,
281
+ });
282
+ }
283
+
284
+ const lastMatchIndex = matchedIndices[matchedIndices.length - 1];
285
+
286
+ diagnostics.push({
287
+ level: 'info',
288
+ message: `Cache breakpoint resolved to block ${lastMatchIndex}`,
289
+ matchedBlocks: matchedIndices.length,
290
+ position: lastMatchIndex,
291
+ });
292
+
293
+ return { position: lastMatchIndex, diagnostics };
294
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Provider capabilities and feature detection.
3
+ */
4
+
5
+ import type { Provider } from '../types/policy.js';
6
+
7
+ /**
8
+ * Provider capabilities.
9
+ */
10
+ export interface ProviderCapabilities {
11
+ /** Provider identifier */
12
+ provider: Provider;
13
+
14
+ /** Supports prompt caching */
15
+ supportsPromptCaching: boolean;
16
+
17
+ /** Supports structured outputs */
18
+ supportsStructuredOutputs: boolean;
19
+
20
+ /** Supports tool/function calling */
21
+ supportsToolCalling: boolean;
22
+
23
+ /** Supports vision (image inputs) */
24
+ supportsVision: boolean;
25
+
26
+ /** Maximum context window (tokens) */
27
+ maxContextWindow: number;
28
+
29
+ /** Maximum output tokens */
30
+ maxOutputTokens: number;
31
+
32
+ /** Message format details */
33
+ messageFormat: {
34
+ /** Supports system messages */
35
+ supportsSystemMessages: boolean;
36
+
37
+ /** System message location */
38
+ systemMessageLocation: 'inline' | 'separate' | 'none';
39
+
40
+ /** Role names */
41
+ roles: {
42
+ system?: string;
43
+ user: string;
44
+ assistant: string;
45
+ tool?: string;
46
+ };
47
+ };
48
+
49
+ /** Cache breakpoint configuration */
50
+ caching?: {
51
+ /** Minimum tokens for caching */
52
+ minTokensForCaching: number;
53
+
54
+ /** Cache TTL (seconds) */
55
+ cacheTTL: number;
56
+
57
+ /** Breakpoint selector strategy */
58
+ breakpointStrategy: 'after_last_match' | 'manual';
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Get capabilities for a provider.
64
+ *
65
+ * @param provider - Provider identifier
66
+ * @returns Provider capabilities
67
+ */
68
+ export function getProviderCapabilities(provider: Provider): ProviderCapabilities {
69
+ switch (provider) {
70
+ case 'anthropic':
71
+ return {
72
+ provider: 'anthropic',
73
+ supportsPromptCaching: true,
74
+ supportsStructuredOutputs: true,
75
+ supportsToolCalling: true,
76
+ supportsVision: true,
77
+ maxContextWindow: 200000,
78
+ maxOutputTokens: 64000,
79
+ messageFormat: {
80
+ supportsSystemMessages: true,
81
+ systemMessageLocation: 'separate',
82
+ roles: {
83
+ system: 'system',
84
+ user: 'user',
85
+ assistant: 'assistant',
86
+ tool: 'user',
87
+ },
88
+ },
89
+ caching: {
90
+ minTokensForCaching: 1024,
91
+ cacheTTL: 300,
92
+ breakpointStrategy: 'after_last_match',
93
+ },
94
+ };
95
+
96
+ case 'openai':
97
+ return {
98
+ provider: 'openai',
99
+ supportsPromptCaching: false,
100
+ supportsStructuredOutputs: true,
101
+ supportsToolCalling: true,
102
+ supportsVision: true,
103
+ maxContextWindow: 128000,
104
+ maxOutputTokens: 16000,
105
+ messageFormat: {
106
+ supportsSystemMessages: true,
107
+ systemMessageLocation: 'inline',
108
+ roles: {
109
+ system: 'system',
110
+ user: 'user',
111
+ assistant: 'assistant',
112
+ tool: 'tool',
113
+ },
114
+ },
115
+ };
116
+
117
+ case 'gemini':
118
+ return {
119
+ provider: 'gemini',
120
+ supportsPromptCaching: true,
121
+ supportsStructuredOutputs: true,
122
+ supportsToolCalling: true,
123
+ supportsVision: true,
124
+ maxContextWindow: 1000000,
125
+ maxOutputTokens: 64000,
126
+ messageFormat: {
127
+ supportsSystemMessages: true,
128
+ systemMessageLocation: 'separate',
129
+ roles: {
130
+ user: 'user',
131
+ assistant: 'model',
132
+ },
133
+ },
134
+ caching: {
135
+ minTokensForCaching: 32000,
136
+ cacheTTL: 300,
137
+ breakpointStrategy: 'after_last_match',
138
+ },
139
+ };
140
+
141
+ default:
142
+ throw new Error(`Unknown provider: ${provider}`);
143
+ }
144
+ }
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Gemini compiler: Pure compilation to Gemini content format.
3
+ *
4
+ * Features:
5
+ * - Separate system instruction
6
+ * - User/model role mapping
7
+ * - Parts-based content structure
8
+ * - Caching support (for large contexts)
9
+ */
10
+
11
+ import type { ContextBlock } from '../types/block.js';
12
+ import type { ContextPolicy } from '../types/policy.js';
13
+ import type { GeminiCompiledContext } from '../types/compiled.js';
14
+ import type { BlockCodec } from '../types/codec.js';
15
+ import { getProviderCapabilities } from './capabilities.js';
16
+
17
+ /**
18
+ * Gemini compilation options.
19
+ */
20
+ export interface GeminiCompilationOptions {
21
+ /** Codec registry for rendering */
22
+ codecRegistry: Map<string, BlockCodec<unknown>>;
23
+
24
+ /** Cache configuration (optional) */
25
+ caching?: {
26
+ /** Enable caching for large contexts */
27
+ enabled: boolean;
28
+
29
+ /** Minimum tokens for caching */
30
+ minTokens: number;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Compile context to Gemini content format.
36
+ * Pure function: same inputs → identical outputs.
37
+ *
38
+ * @param blocks - Ordered blocks from ContextView
39
+ * @param policy - Context policy
40
+ * @param options - Compilation options
41
+ * @returns Gemini compiled context
42
+ */
43
+ export function compileGeminiContext(
44
+ blocks: ContextBlock[],
45
+ policy: ContextPolicy,
46
+ options: GeminiCompilationOptions
47
+ ): GeminiCompiledContext {
48
+ const capabilities = getProviderCapabilities('gemini');
49
+
50
+ // Separate system blocks from message blocks
51
+ const systemBlocks = blocks.filter((b) => b.meta.kind === 'pinned');
52
+ const messageBlocks = blocks.filter((b) => b.meta.kind !== 'pinned');
53
+
54
+ // Compile system instruction
55
+ const systemInstruction = compileSystemInstruction(systemBlocks, options.codecRegistry);
56
+
57
+ // Compile message blocks
58
+ const messages = compileMessages(messageBlocks, options.codecRegistry);
59
+
60
+ // Calculate tokens (simplified - would use actual estimator in production)
61
+ const estimatedTokens = blocks.length * 100; // Placeholder
62
+
63
+ return {
64
+ provider: 'gemini',
65
+ modelId: policy.modelId,
66
+ messages,
67
+ system: systemInstruction,
68
+ estimatedTokens,
69
+ blocks,
70
+ meta: {
71
+ compiledAt: Math.floor(Date.now() / 1000),
72
+ contextWindow: policy.contextWindow,
73
+ completionReserve: policy.completionReserve,
74
+ availableTokens: policy.contextWindow - policy.completionReserve,
75
+ overflowed: false,
76
+ compacted: false,
77
+ truncated: false,
78
+ tokensByKind: {},
79
+ },
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Compile system instruction from system blocks.
85
+ * Gemini uses a single string for system instruction.
86
+ *
87
+ * @param blocks - System blocks
88
+ * @param codecRegistry - Codec registry
89
+ * @returns System instruction string (or undefined)
90
+ */
91
+ function compileSystemInstruction(
92
+ blocks: ContextBlock[],
93
+ codecRegistry: Map<string, BlockCodec<unknown>>
94
+ ): string | undefined {
95
+ if (blocks.length === 0) {
96
+ return undefined;
97
+ }
98
+
99
+ const parts: string[] = [];
100
+
101
+ for (const block of blocks) {
102
+ const codec = codecRegistry.get(block.meta.codecId);
103
+
104
+ if (!codec) {
105
+ console.warn(`[Gemini] Codec not found: ${block.meta.codecId}`);
106
+ continue;
107
+ }
108
+
109
+ const rendered = codec.render(block);
110
+ const geminiContent = rendered.gemini;
111
+
112
+ // Extract text from rendered content
113
+ if (typeof geminiContent === 'string') {
114
+ parts.push(geminiContent);
115
+ } else {
116
+ parts.push(JSON.stringify(geminiContent));
117
+ }
118
+ }
119
+
120
+ return parts.join('\n\n');
121
+ }
122
+
123
+ /**
124
+ * Compile blocks to Gemini messages.
125
+ * Gemini uses user/model roles and parts-based content.
126
+ *
127
+ * @param blocks - Message blocks
128
+ * @param codecRegistry - Codec registry
129
+ * @returns Gemini messages
130
+ */
131
+ function compileMessages(
132
+ blocks: ContextBlock[],
133
+ codecRegistry: Map<string, BlockCodec<unknown>>
134
+ ): Array<{
135
+ role: 'user' | 'model';
136
+ parts: unknown[];
137
+ }> {
138
+ const messages: Array<{
139
+ role: 'user' | 'model';
140
+ parts: unknown[];
141
+ }> = [];
142
+
143
+ for (const block of blocks) {
144
+ const codec = codecRegistry.get(block.meta.codecId);
145
+
146
+ if (!codec) {
147
+ console.warn(`[Gemini] Codec not found: ${block.meta.codecId}`);
148
+ continue;
149
+ }
150
+
151
+ const rendered = codec.render(block);
152
+ const geminiContent = rendered.gemini as any;
153
+
154
+ // Handle different block kinds
155
+ if (block.meta.kind === 'history') {
156
+ // History blocks are already in message format
157
+ if (Array.isArray(geminiContent)) {
158
+ messages.push(...geminiContent);
159
+ } else {
160
+ messages.push(geminiContent);
161
+ }
162
+ } else if (block.meta.kind === 'turn') {
163
+ // Turn blocks are user messages
164
+ messages.push({
165
+ role: 'user',
166
+ parts: geminiContent.parts || [{ text: geminiContent }],
167
+ });
168
+ } else {
169
+ // Other blocks (reference, memory, state, tool_output) as user messages
170
+ const textContent = typeof geminiContent === 'string'
171
+ ? geminiContent
172
+ : JSON.stringify(geminiContent);
173
+
174
+ messages.push({
175
+ role: 'user',
176
+ parts: [{ text: textContent }],
177
+ });
178
+ }
179
+ }
180
+
181
+ // Ensure alternating user/model roles (required by Gemini)
182
+ return enforceAlternatingRoles(messages);
183
+ }
184
+
185
+ /**
186
+ * Enforce alternating user/model roles.
187
+ * Gemini requires strict alternation between user and model messages.
188
+ *
189
+ * @param messages - Input messages
190
+ * @returns Messages with enforced alternation
191
+ */
192
+ function enforceAlternatingRoles(
193
+ messages: Array<{ role: 'user' | 'model'; parts: unknown[] }>
194
+ ): Array<{ role: 'user' | 'model'; parts: unknown[] }> {
195
+ const result: Array<{ role: 'user' | 'model'; parts: unknown[] }> = [];
196
+
197
+ let currentRole: 'user' | 'model' | null = null;
198
+ let currentParts: unknown[] = [];
199
+
200
+ for (const msg of messages) {
201
+ if (msg.role === currentRole) {
202
+ // Same role - merge parts
203
+ currentParts.push(...msg.parts);
204
+ } else {
205
+ // Different role - flush current message
206
+ if (currentRole !== null && currentParts.length > 0) {
207
+ result.push({ role: currentRole, parts: currentParts });
208
+ }
209
+
210
+ // Start new message
211
+ currentRole = msg.role;
212
+ currentParts = [...msg.parts];
213
+ }
214
+ }
215
+
216
+ // Flush final message
217
+ if (currentRole !== null && currentParts.length > 0) {
218
+ result.push({ role: currentRole, parts: currentParts });
219
+ }
220
+
221
+ return result;
222
+ }
223
+
224
+ /**
225
+ * Validate Gemini message sequence.
226
+ *
227
+ * @param messages - Gemini messages
228
+ * @returns Validation diagnostics
229
+ */
230
+ export function validateGeminiMessages(
231
+ messages: Array<{ role: 'user' | 'model'; parts: unknown[] }>
232
+ ): Array<{ level: 'error' | 'warning'; message: string }> {
233
+ const diagnostics: Array<{ level: 'error' | 'warning'; message: string }> = [];
234
+
235
+ // Check for empty messages
236
+ if (messages.length === 0) {
237
+ diagnostics.push({
238
+ level: 'error',
239
+ message: 'Message array is empty',
240
+ });
241
+ return diagnostics;
242
+ }
243
+
244
+ // Check for strict alternation
245
+ let lastRole: 'user' | 'model' | null = null;
246
+ for (let i = 0; i < messages.length; i++) {
247
+ const msg = messages[i];
248
+
249
+ if (msg.role === lastRole) {
250
+ diagnostics.push({
251
+ level: 'error',
252
+ message: `Consecutive ${msg.role} messages at index ${i} (Gemini requires strict alternation)`,
253
+ });
254
+ }
255
+
256
+ lastRole = msg.role;
257
+ }
258
+
259
+ // Check for empty parts
260
+ for (let i = 0; i < messages.length; i++) {
261
+ const msg = messages[i];
262
+
263
+ if (msg.parts.length === 0) {
264
+ diagnostics.push({
265
+ level: 'error',
266
+ message: `Empty parts array at index ${i}`,
267
+ });
268
+ }
269
+ }
270
+
271
+ return diagnostics;
272
+ }