@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.
- package/README.md +270 -0
- package/dist/__tests__/attachment-selector.test.d.ts +11 -0
- package/dist/__tests__/attachment-selector.test.d.ts.map +1 -0
- package/dist/__tests__/attachment-selector.test.js +449 -0
- package/dist/__tests__/attachment-selector.test.js.map +1 -0
- package/dist/__tests__/cache-breakpoints.test.d.ts +11 -0
- package/dist/__tests__/cache-breakpoints.test.d.ts.map +1 -0
- package/dist/__tests__/cache-breakpoints.test.js +398 -0
- package/dist/__tests__/cache-breakpoints.test.js.map +1 -0
- package/dist/__tests__/codecs.test.d.ts +7 -0
- package/dist/__tests__/codecs.test.d.ts.map +1 -0
- package/dist/__tests__/codecs.test.js +331 -0
- package/dist/__tests__/codecs.test.js.map +1 -0
- package/dist/__tests__/compactor.test.d.ts +11 -0
- package/dist/__tests__/compactor.test.d.ts.map +1 -0
- package/dist/__tests__/compactor.test.js +519 -0
- package/dist/__tests__/compactor.test.js.map +1 -0
- package/dist/__tests__/context-graph.test.d.ts +7 -0
- package/dist/__tests__/context-graph.test.d.ts.map +1 -0
- package/dist/__tests__/context-graph.test.js +262 -0
- package/dist/__tests__/context-graph.test.js.map +1 -0
- package/dist/__tests__/hash.test.d.ts +7 -0
- package/dist/__tests__/hash.test.d.ts.map +1 -0
- package/dist/__tests__/hash.test.js +228 -0
- package/dist/__tests__/hash.test.js.map +1 -0
- package/dist/__tests__/integration.test.d.ts +15 -0
- package/dist/__tests__/integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration.test.js +728 -0
- package/dist/__tests__/integration.test.js.map +1 -0
- package/dist/__tests__/kind-order.test.d.ts +7 -0
- package/dist/__tests__/kind-order.test.d.ts.map +1 -0
- package/dist/__tests__/kind-order.test.js +243 -0
- package/dist/__tests__/kind-order.test.js.map +1 -0
- package/dist/__tests__/phase2-integration.test.d.ts +5 -0
- package/dist/__tests__/phase2-integration.test.d.ts.map +1 -0
- package/dist/__tests__/phase2-integration.test.js +222 -0
- package/dist/__tests__/phase2-integration.test.js.map +1 -0
- package/dist/__tests__/queries.test.d.ts +7 -0
- package/dist/__tests__/queries.test.d.ts.map +1 -0
- package/dist/__tests__/queries.test.js +254 -0
- package/dist/__tests__/queries.test.js.map +1 -0
- package/dist/__tests__/token-estimator.test.d.ts +7 -0
- package/dist/__tests__/token-estimator.test.d.ts.map +1 -0
- package/dist/__tests__/token-estimator.test.js +267 -0
- package/dist/__tests__/token-estimator.test.js.map +1 -0
- package/dist/adapters/anthropic-estimator.d.ts +38 -0
- package/dist/adapters/anthropic-estimator.d.ts.map +1 -0
- package/dist/adapters/anthropic-estimator.js +108 -0
- package/dist/adapters/anthropic-estimator.js.map +1 -0
- package/dist/adapters/attachment-resolver.d.ts +96 -0
- package/dist/adapters/attachment-resolver.d.ts.map +1 -0
- package/dist/adapters/attachment-resolver.js +176 -0
- package/dist/adapters/attachment-resolver.js.map +1 -0
- package/dist/adapters/attachment-selector.d.ts +59 -0
- package/dist/adapters/attachment-selector.d.ts.map +1 -0
- package/dist/adapters/attachment-selector.js +163 -0
- package/dist/adapters/attachment-selector.js.map +1 -0
- package/dist/adapters/gemini-estimator.d.ts +27 -0
- package/dist/adapters/gemini-estimator.d.ts.map +1 -0
- package/dist/adapters/gemini-estimator.js +80 -0
- package/dist/adapters/gemini-estimator.js.map +1 -0
- package/dist/adapters/index.d.ts +12 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/memory-store.d.ts +139 -0
- package/dist/adapters/memory-store.d.ts.map +1 -0
- package/dist/adapters/memory-store.js +187 -0
- package/dist/adapters/memory-store.js.map +1 -0
- package/dist/adapters/openai-estimator.d.ts +35 -0
- package/dist/adapters/openai-estimator.d.ts.map +1 -0
- package/dist/adapters/openai-estimator.js +89 -0
- package/dist/adapters/openai-estimator.js.map +1 -0
- package/dist/adapters/summarizer.d.ts +121 -0
- package/dist/adapters/summarizer.d.ts.map +1 -0
- package/dist/adapters/summarizer.js +121 -0
- package/dist/adapters/summarizer.js.map +1 -0
- package/dist/adapters/token-estimator.d.ts +63 -0
- package/dist/adapters/token-estimator.d.ts.map +1 -0
- package/dist/adapters/token-estimator.js +37 -0
- package/dist/adapters/token-estimator.js.map +1 -0
- package/dist/builder/context-builder.d.ts +186 -0
- package/dist/builder/context-builder.d.ts.map +1 -0
- package/dist/builder/context-builder.js +305 -0
- package/dist/builder/context-builder.js.map +1 -0
- package/dist/builder/context-fork.d.ts +166 -0
- package/dist/builder/context-fork.d.ts.map +1 -0
- package/dist/builder/context-fork.js +282 -0
- package/dist/builder/context-fork.js.map +1 -0
- package/dist/builder/index.d.ts +6 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +22 -0
- package/dist/builder/index.js.map +1 -0
- package/dist/codecs/base.d.ts +18 -0
- package/dist/codecs/base.d.ts.map +1 -0
- package/dist/codecs/base.js +39 -0
- package/dist/codecs/base.js.map +1 -0
- package/dist/codecs/conversation-history.codec.d.ts +81 -0
- package/dist/codecs/conversation-history.codec.d.ts.map +1 -0
- package/dist/codecs/conversation-history.codec.js +89 -0
- package/dist/codecs/conversation-history.codec.js.map +1 -0
- package/dist/codecs/index.d.ts +31 -0
- package/dist/codecs/index.d.ts.map +1 -0
- package/dist/codecs/index.js +71 -0
- package/dist/codecs/index.js.map +1 -0
- package/dist/codecs/redacted-stub.codec.d.ts +32 -0
- package/dist/codecs/redacted-stub.codec.d.ts.map +1 -0
- package/dist/codecs/redacted-stub.codec.js +64 -0
- package/dist/codecs/redacted-stub.codec.js.map +1 -0
- package/dist/codecs/structured-reference.codec.d.ts +40 -0
- package/dist/codecs/structured-reference.codec.d.ts.map +1 -0
- package/dist/codecs/structured-reference.codec.js +81 -0
- package/dist/codecs/structured-reference.codec.js.map +1 -0
- package/dist/codecs/system-rules.codec.d.ts +32 -0
- package/dist/codecs/system-rules.codec.d.ts.map +1 -0
- package/dist/codecs/system-rules.codec.js +62 -0
- package/dist/codecs/system-rules.codec.js.map +1 -0
- package/dist/codecs/tool-output.codec.d.ts +66 -0
- package/dist/codecs/tool-output.codec.d.ts.map +1 -0
- package/dist/codecs/tool-output.codec.js +95 -0
- package/dist/codecs/tool-output.codec.js.map +1 -0
- package/dist/codecs/tool-schema.codec.d.ts +36 -0
- package/dist/codecs/tool-schema.codec.d.ts.map +1 -0
- package/dist/codecs/tool-schema.codec.js +74 -0
- package/dist/codecs/tool-schema.codec.js.map +1 -0
- package/dist/codecs/unsafe-text.codec.d.ts +28 -0
- package/dist/codecs/unsafe-text.codec.d.ts.map +1 -0
- package/dist/codecs/unsafe-text.codec.js +63 -0
- package/dist/codecs/unsafe-text.codec.js.map +1 -0
- package/dist/graph/context-graph.d.ts +121 -0
- package/dist/graph/context-graph.d.ts.map +1 -0
- package/dist/graph/context-graph.js +166 -0
- package/dist/graph/context-graph.js.map +1 -0
- package/dist/graph/index.d.ts +8 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +24 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/kind-order.d.ts +60 -0
- package/dist/graph/kind-order.d.ts.map +1 -0
- package/dist/graph/kind-order.js +113 -0
- package/dist/graph/kind-order.js.map +1 -0
- package/dist/graph/queries.d.ts +68 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +240 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/views.d.ts +90 -0
- package/dist/graph/views.d.ts.map +1 -0
- package/dist/graph/views.js +173 -0
- package/dist/graph/views.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/pipeline/compactor.d.ts +128 -0
- package/dist/pipeline/compactor.d.ts.map +1 -0
- package/dist/pipeline/compactor.js +346 -0
- package/dist/pipeline/compactor.js.map +1 -0
- package/dist/pipeline/index.d.ts +6 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +22 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/summarizer.d.ts +18 -0
- package/dist/pipeline/summarizer.d.ts.map +1 -0
- package/dist/pipeline/summarizer.js +68 -0
- package/dist/pipeline/summarizer.js.map +1 -0
- package/dist/policies/default-policy.d.ts +29 -0
- package/dist/policies/default-policy.d.ts.map +1 -0
- package/dist/policies/default-policy.js +58 -0
- package/dist/policies/default-policy.js.map +1 -0
- package/dist/policies/index.d.ts +5 -0
- package/dist/policies/index.d.ts.map +1 -0
- package/dist/policies/index.js +21 -0
- package/dist/policies/index.js.map +1 -0
- package/dist/providers/anthropic-compiler.d.ts +58 -0
- package/dist/providers/anthropic-compiler.d.ts.map +1 -0
- package/dist/providers/anthropic-compiler.js +182 -0
- package/dist/providers/anthropic-compiler.js.map +1 -0
- package/dist/providers/capabilities.d.ts +54 -0
- package/dist/providers/capabilities.d.ts.map +1 -0
- package/dist/providers/capabilities.js +87 -0
- package/dist/providers/capabilities.js.map +1 -0
- package/dist/providers/gemini-compiler.d.ts +51 -0
- package/dist/providers/gemini-compiler.d.ts.map +1 -0
- package/dist/providers/gemini-compiler.js +206 -0
- package/dist/providers/gemini-compiler.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +24 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai-compiler.d.ts +46 -0
- package/dist/providers/openai-compiler.d.ts.map +1 -0
- package/dist/providers/openai-compiler.js +149 -0
- package/dist/providers/openai-compiler.js.map +1 -0
- package/dist/types/attachment.d.ts +62 -0
- package/dist/types/attachment.d.ts.map +1 -0
- package/dist/types/attachment.js +6 -0
- package/dist/types/attachment.js.map +1 -0
- package/dist/types/block.d.ts +61 -0
- package/dist/types/block.d.ts.map +1 -0
- package/dist/types/block.js +8 -0
- package/dist/types/block.js.map +1 -0
- package/dist/types/codec.d.ts +58 -0
- package/dist/types/codec.d.ts.map +1 -0
- package/dist/types/codec.js +6 -0
- package/dist/types/codec.js.map +1 -0
- package/dist/types/compiled.d.ts +91 -0
- package/dist/types/compiled.d.ts.map +1 -0
- package/dist/types/compiled.js +6 -0
- package/dist/types/compiled.js.map +1 -0
- package/dist/types/hash.d.ts +24 -0
- package/dist/types/hash.d.ts.map +1 -0
- package/dist/types/hash.js +49 -0
- package/dist/types/hash.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +26 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/policy.d.ts +128 -0
- package/dist/types/policy.d.ts.map +1 -0
- package/dist/types/policy.js +55 -0
- package/dist/types/policy.js.map +1 -0
- package/package.json +55 -0
- package/postcss.config.js +4 -0
- package/src/__tests__/attachment-selector.test.ts +559 -0
- package/src/__tests__/cache-breakpoints.test.ts +566 -0
- package/src/__tests__/codecs.test.ts +417 -0
- package/src/__tests__/compactor.test.ts +608 -0
- package/src/__tests__/context-graph.test.ts +383 -0
- package/src/__tests__/hash.test.ts +274 -0
- package/src/__tests__/integration.test.ts +866 -0
- package/src/__tests__/kind-order.test.ts +312 -0
- package/src/__tests__/phase2-integration.test.ts +253 -0
- package/src/__tests__/queries.test.ts +387 -0
- package/src/__tests__/token-estimator.test.ts +326 -0
- package/src/adapters/anthropic-estimator.ts +125 -0
- package/src/adapters/attachment-resolver.ts +295 -0
- package/src/adapters/attachment-selector.ts +218 -0
- package/src/adapters/gemini-estimator.ts +93 -0
- package/src/adapters/index.ts +12 -0
- package/src/adapters/memory-store.ts +299 -0
- package/src/adapters/openai-estimator.ts +105 -0
- package/src/adapters/summarizer.ts +250 -0
- package/src/adapters/token-estimator.ts +74 -0
- package/src/builder/context-builder.ts +467 -0
- package/src/builder/context-fork.ts +471 -0
- package/src/builder/index.ts +6 -0
- package/src/codecs/base.ts +36 -0
- package/src/codecs/conversation-history.codec.ts +108 -0
- package/src/codecs/index.ts +57 -0
- package/src/codecs/redacted-stub.codec.ts +76 -0
- package/src/codecs/structured-reference.codec.ts +96 -0
- package/src/codecs/system-rules.codec.ts +74 -0
- package/src/codecs/tool-output.codec.ts +109 -0
- package/src/codecs/tool-schema.codec.ts +87 -0
- package/src/codecs/unsafe-text.codec.ts +74 -0
- package/src/graph/context-graph.ts +205 -0
- package/src/graph/index.ts +8 -0
- package/src/graph/kind-order.ts +125 -0
- package/src/graph/queries.ts +306 -0
- package/src/graph/views.ts +255 -0
- package/src/index.ts +31 -0
- package/src/pipeline/compactor.ts +563 -0
- package/src/pipeline/index.ts +6 -0
- package/src/pipeline/summarizer.ts +76 -0
- package/src/policies/default-policy.ts +69 -0
- package/src/policies/index.ts +5 -0
- package/src/providers/anthropic-compiler.ts +294 -0
- package/src/providers/capabilities.ts +144 -0
- package/src/providers/gemini-compiler.ts +272 -0
- package/src/providers/index.ts +8 -0
- package/src/providers/openai-compiler.ts +191 -0
- package/src/types/attachment.ts +86 -0
- package/src/types/block.ts +84 -0
- package/src/types/codec.ts +68 -0
- package/src/types/compiled.ts +109 -0
- package/src/types/hash.ts +58 -0
- package/src/types/index.ts +10 -0
- package/src/types/policy.ts +194 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +21 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for Anthropic cache breakpoint resolution.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* - "After last matching block" rule
|
|
6
|
+
* - Selector matching (kind, codecId, tag, source)
|
|
7
|
+
* - Diagnostics for no matches / many matches
|
|
8
|
+
* - Cache control placement in system messages
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { compileAnthropicContext } from '../providers/anthropic-compiler.js';
|
|
13
|
+
import type { ContextBlock } from '../types/block.js';
|
|
14
|
+
import type { ContextPolicy } from '../types/policy.js';
|
|
15
|
+
import type { BlockCodec } from '../types/codec.js';
|
|
16
|
+
|
|
17
|
+
// Mock codec for testing
|
|
18
|
+
const mockCodec: BlockCodec<any> = {
|
|
19
|
+
codecId: 'test-codec',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
payloadSchema: null as any,
|
|
22
|
+
canonicalize: (payload) => payload,
|
|
23
|
+
hash: (canon) => 'test-hash',
|
|
24
|
+
validate: (payload) => payload,
|
|
25
|
+
render: (block) => ({
|
|
26
|
+
anthropic: [{ type: 'text', text: JSON.stringify(block.payload) }],
|
|
27
|
+
openai: { role: 'system', content: JSON.stringify(block.payload) },
|
|
28
|
+
gemini: JSON.stringify(block.payload),
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('Anthropic Cache Breakpoints', () => {
|
|
33
|
+
describe('Cache Breakpoint Resolution', () => {
|
|
34
|
+
it('should resolve to last matching block by kind', () => {
|
|
35
|
+
const blocks: ContextBlock[] = [
|
|
36
|
+
createBlock('hash1', 'pinned', 'block1'),
|
|
37
|
+
createBlock('hash2', 'pinned', 'block2'),
|
|
38
|
+
createBlock('hash3', 'pinned', 'block3'),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
42
|
+
|
|
43
|
+
const result = compileAnthropicContext(
|
|
44
|
+
blocks,
|
|
45
|
+
createTestPolicy(),
|
|
46
|
+
{
|
|
47
|
+
codecRegistry,
|
|
48
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Cache control should be on last pinned block (index 2)
|
|
53
|
+
const systemMessages = result.system!;
|
|
54
|
+
expect(systemMessages).toHaveLength(3);
|
|
55
|
+
expect(systemMessages[0].cache_control).toBeUndefined();
|
|
56
|
+
expect(systemMessages[1].cache_control).toBeUndefined();
|
|
57
|
+
expect(systemMessages[2].cache_control).toEqual({ type: 'ephemeral' });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should resolve to last matching block by codecId', () => {
|
|
61
|
+
const blocks: ContextBlock[] = [
|
|
62
|
+
createBlockWithCodec('hash1', 'pinned', 'codec-a', 'block1'),
|
|
63
|
+
createBlockWithCodec('hash2', 'pinned', 'codec-b', 'block2'),
|
|
64
|
+
createBlockWithCodec('hash3', 'pinned', 'codec-a', 'block3'),
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const codecA = { ...mockCodec, codecId: 'codec-a' };
|
|
68
|
+
const codecB = { ...mockCodec, codecId: 'codec-b' };
|
|
69
|
+
const codecRegistry = new Map([
|
|
70
|
+
['codec-a', codecA],
|
|
71
|
+
['codec-b', codecB],
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const result = compileAnthropicContext(
|
|
75
|
+
blocks,
|
|
76
|
+
createTestPolicy(),
|
|
77
|
+
{
|
|
78
|
+
codecRegistry,
|
|
79
|
+
cacheBreakpoint: { codecId: 'codec-a' },
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Cache control should be on last codec-a block (index 2)
|
|
84
|
+
const systemMessages = result.system!;
|
|
85
|
+
expect(systemMessages[2].cache_control).toEqual({ type: 'ephemeral' });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should resolve to last matching block by tag', () => {
|
|
89
|
+
const blocks: ContextBlock[] = [
|
|
90
|
+
createBlockWithTags('hash1', 'pinned', ['tag1'], 'block1'),
|
|
91
|
+
createBlockWithTags('hash2', 'pinned', ['tag2'], 'block2'),
|
|
92
|
+
createBlockWithTags('hash3', 'pinned', ['tag1', 'tag2'], 'block3'),
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
96
|
+
|
|
97
|
+
const result = compileAnthropicContext(
|
|
98
|
+
blocks,
|
|
99
|
+
createTestPolicy(),
|
|
100
|
+
{
|
|
101
|
+
codecRegistry,
|
|
102
|
+
cacheBreakpoint: { tag: 'tag1' },
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Cache control should be on last block with tag1 (index 2)
|
|
107
|
+
const systemMessages = result.system!;
|
|
108
|
+
expect(systemMessages[2].cache_control).toEqual({ type: 'ephemeral' });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should resolve to last matching block by source', () => {
|
|
112
|
+
const blocks: ContextBlock[] = [
|
|
113
|
+
createBlockWithSource('hash1', 'pinned', 'source-a', 'block1'),
|
|
114
|
+
createBlockWithSource('hash2', 'pinned', 'source-b', 'block2'),
|
|
115
|
+
createBlockWithSource('hash3', 'pinned', 'source-a', 'block3'),
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
119
|
+
|
|
120
|
+
const result = compileAnthropicContext(
|
|
121
|
+
blocks,
|
|
122
|
+
createTestPolicy(),
|
|
123
|
+
{
|
|
124
|
+
codecRegistry,
|
|
125
|
+
cacheBreakpoint: { source: 'source-a' },
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Cache control should be on last source-a block (index 2)
|
|
130
|
+
const systemMessages = result.system!;
|
|
131
|
+
expect(systemMessages[2].cache_control).toEqual({ type: 'ephemeral' });
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should match blocks with multiple criteria (AND logic)', () => {
|
|
135
|
+
const blocks: ContextBlock[] = [
|
|
136
|
+
createBlockWithDetails('hash1', 'pinned', 'codec-a', ['tag1'], 'source-a', 'block1'),
|
|
137
|
+
createBlockWithDetails('hash2', 'pinned', 'codec-a', ['tag2'], 'source-a', 'block2'),
|
|
138
|
+
createBlockWithDetails('hash3', 'pinned', 'codec-a', ['tag1'], 'source-b', 'block3'),
|
|
139
|
+
createBlockWithDetails('hash4', 'pinned', 'codec-a', ['tag1'], 'source-a', 'block4'),
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const codecRegistry = new Map([['codec-a', mockCodec]]);
|
|
143
|
+
|
|
144
|
+
const result = compileAnthropicContext(
|
|
145
|
+
blocks,
|
|
146
|
+
createTestPolicy(),
|
|
147
|
+
{
|
|
148
|
+
codecRegistry,
|
|
149
|
+
cacheBreakpoint: {
|
|
150
|
+
kind: 'pinned',
|
|
151
|
+
codecId: 'codec-a',
|
|
152
|
+
tag: 'tag1',
|
|
153
|
+
source: 'source-a',
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Only hash1 and hash4 match all criteria, cache control on hash4 (last)
|
|
159
|
+
const systemMessages = result.system!;
|
|
160
|
+
expect(systemMessages[3].cache_control).toEqual({ type: 'ephemeral' });
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('No Matches', () => {
|
|
165
|
+
it('should not add cache_control when no blocks match', () => {
|
|
166
|
+
const blocks: ContextBlock[] = [
|
|
167
|
+
createBlock('hash1', 'pinned', 'block1'),
|
|
168
|
+
createBlock('hash2', 'pinned', 'block2'),
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
172
|
+
|
|
173
|
+
const result = compileAnthropicContext(
|
|
174
|
+
blocks,
|
|
175
|
+
createTestPolicy(),
|
|
176
|
+
{
|
|
177
|
+
codecRegistry,
|
|
178
|
+
cacheBreakpoint: { kind: 'memory' }, // No memory blocks
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
// No cache_control on any block
|
|
183
|
+
const systemMessages = result.system!;
|
|
184
|
+
expect(systemMessages.every((m) => !m.cache_control)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should handle empty blocks array', () => {
|
|
188
|
+
const blocks: ContextBlock[] = [];
|
|
189
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
190
|
+
|
|
191
|
+
const result = compileAnthropicContext(
|
|
192
|
+
blocks,
|
|
193
|
+
createTestPolicy(),
|
|
194
|
+
{
|
|
195
|
+
codecRegistry,
|
|
196
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
expect(result.system).toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('No Cache Breakpoint', () => {
|
|
205
|
+
it('should not add cache_control when no breakpoint provided', () => {
|
|
206
|
+
const blocks: ContextBlock[] = [
|
|
207
|
+
createBlock('hash1', 'pinned', 'block1'),
|
|
208
|
+
createBlock('hash2', 'pinned', 'block2'),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
212
|
+
|
|
213
|
+
const result = compileAnthropicContext(
|
|
214
|
+
blocks,
|
|
215
|
+
createTestPolicy(),
|
|
216
|
+
{
|
|
217
|
+
codecRegistry,
|
|
218
|
+
// No cacheBreakpoint
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const systemMessages = result.system!;
|
|
223
|
+
expect(systemMessages.every((m) => !m.cache_control)).toBe(true);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('System vs Message Blocks', () => {
|
|
228
|
+
it('should only apply cache control to system blocks (pinned)', () => {
|
|
229
|
+
const blocks: ContextBlock[] = [
|
|
230
|
+
createBlock('hash1', 'pinned', 'system1'),
|
|
231
|
+
createBlock('hash2', 'pinned', 'system2'),
|
|
232
|
+
createBlock('hash3', 'memory', 'memory1'), // Not pinned
|
|
233
|
+
createBlock('hash4', 'history', 'history1'), // Not pinned
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
237
|
+
|
|
238
|
+
const result = compileAnthropicContext(
|
|
239
|
+
blocks,
|
|
240
|
+
createTestPolicy(),
|
|
241
|
+
{
|
|
242
|
+
codecRegistry,
|
|
243
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Only system messages should have cache_control possibility
|
|
248
|
+
expect(result.system).toHaveLength(2);
|
|
249
|
+
expect(result.messages).toHaveLength(2);
|
|
250
|
+
expect(result.system![1].cache_control).toEqual({ type: 'ephemeral' });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should not add cache control to non-pinned blocks', () => {
|
|
254
|
+
const blocks: ContextBlock[] = [
|
|
255
|
+
createBlock('hash1', 'memory', 'memory1'),
|
|
256
|
+
createBlock('hash2', 'history', 'history1'),
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
260
|
+
|
|
261
|
+
const result = compileAnthropicContext(
|
|
262
|
+
blocks,
|
|
263
|
+
createTestPolicy(),
|
|
264
|
+
{
|
|
265
|
+
codecRegistry,
|
|
266
|
+
cacheBreakpoint: { kind: 'memory' },
|
|
267
|
+
}
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// No system blocks, so no cache control
|
|
271
|
+
expect(result.system).toBeUndefined();
|
|
272
|
+
expect(result.messages).toHaveLength(2);
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('Edge Cases', () => {
|
|
277
|
+
it('should handle single matching block', () => {
|
|
278
|
+
const blocks: ContextBlock[] = [
|
|
279
|
+
createBlock('hash1', 'pinned', 'block1'),
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
283
|
+
|
|
284
|
+
const result = compileAnthropicContext(
|
|
285
|
+
blocks,
|
|
286
|
+
createTestPolicy(),
|
|
287
|
+
{
|
|
288
|
+
codecRegistry,
|
|
289
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const systemMessages = result.system!;
|
|
294
|
+
expect(systemMessages).toHaveLength(1);
|
|
295
|
+
expect(systemMessages[0].cache_control).toEqual({ type: 'ephemeral' });
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should handle many matching blocks (use last)', () => {
|
|
299
|
+
const blocks: ContextBlock[] = Array.from({ length: 100 }, (_, i) =>
|
|
300
|
+
createBlock(`hash${i}`, 'pinned', `block${i}`)
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
304
|
+
|
|
305
|
+
const result = compileAnthropicContext(
|
|
306
|
+
blocks,
|
|
307
|
+
createTestPolicy(),
|
|
308
|
+
{
|
|
309
|
+
codecRegistry,
|
|
310
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// Cache control on last block
|
|
315
|
+
const systemMessages = result.system!;
|
|
316
|
+
expect(systemMessages).toHaveLength(100);
|
|
317
|
+
expect(systemMessages[99].cache_control).toEqual({ type: 'ephemeral' });
|
|
318
|
+
// All others should not have cache_control
|
|
319
|
+
for (let i = 0; i < 99; i++) {
|
|
320
|
+
expect(systemMessages[i].cache_control).toBeUndefined();
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('should handle interleaved matching and non-matching blocks', () => {
|
|
325
|
+
const blocks: ContextBlock[] = [
|
|
326
|
+
createBlockWithCodec('hash1', 'pinned', 'codec-a', 'block1'),
|
|
327
|
+
createBlockWithCodec('hash2', 'pinned', 'codec-b', 'block2'),
|
|
328
|
+
createBlockWithCodec('hash3', 'pinned', 'codec-a', 'block3'),
|
|
329
|
+
createBlockWithCodec('hash4', 'pinned', 'codec-b', 'block4'),
|
|
330
|
+
createBlockWithCodec('hash5', 'pinned', 'codec-a', 'block5'),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
const codecA = { ...mockCodec, codecId: 'codec-a' };
|
|
334
|
+
const codecB = { ...mockCodec, codecId: 'codec-b' };
|
|
335
|
+
const codecRegistry = new Map([
|
|
336
|
+
['codec-a', codecA],
|
|
337
|
+
['codec-b', codecB],
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
const result = compileAnthropicContext(
|
|
341
|
+
blocks,
|
|
342
|
+
createTestPolicy(),
|
|
343
|
+
{
|
|
344
|
+
codecRegistry,
|
|
345
|
+
cacheBreakpoint: { codecId: 'codec-a' },
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// Cache control on last codec-a block (index 4)
|
|
350
|
+
const systemMessages = result.system!;
|
|
351
|
+
expect(systemMessages[4].cache_control).toEqual({ type: 'ephemeral' });
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
describe('Selector Combinations', () => {
|
|
356
|
+
it('should match when kind AND codecId both match', () => {
|
|
357
|
+
const blocks: ContextBlock[] = [
|
|
358
|
+
createBlockWithCodec('hash1', 'pinned', 'codec-a', 'block1'),
|
|
359
|
+
createBlockWithCodec('hash2', 'memory', 'codec-a', 'block2'), // Wrong kind
|
|
360
|
+
createBlockWithCodec('hash3', 'pinned', 'codec-b', 'block3'), // Wrong codec
|
|
361
|
+
createBlockWithCodec('hash4', 'pinned', 'codec-a', 'block4'), // Match
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const codecA = { ...mockCodec, codecId: 'codec-a' };
|
|
365
|
+
const codecB = { ...mockCodec, codecId: 'codec-b' };
|
|
366
|
+
const codecRegistry = new Map([
|
|
367
|
+
['codec-a', codecA],
|
|
368
|
+
['codec-b', codecB],
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
const result = compileAnthropicContext(
|
|
372
|
+
blocks.filter((b) => b.meta.kind === 'pinned'), // Only pinned blocks become system messages
|
|
373
|
+
createTestPolicy(),
|
|
374
|
+
{
|
|
375
|
+
codecRegistry,
|
|
376
|
+
cacheBreakpoint: { kind: 'pinned', codecId: 'codec-a' },
|
|
377
|
+
}
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
// hash1 and hash4 match, cache control on hash4 (last)
|
|
381
|
+
const systemMessages = result.system!;
|
|
382
|
+
// Only 3 pinned blocks: hash1, hash3, hash4
|
|
383
|
+
expect(systemMessages).toHaveLength(3);
|
|
384
|
+
expect(systemMessages[2].cache_control).toEqual({ type: 'ephemeral' });
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('should not match when only some criteria match', () => {
|
|
388
|
+
const blocks: ContextBlock[] = [
|
|
389
|
+
createBlockWithDetails('hash1', 'pinned', 'codec-a', ['tag1'], 'source-a', 'block1'),
|
|
390
|
+
createBlockWithDetails('hash2', 'pinned', 'codec-a', ['tag2'], 'source-a', 'block2'),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const codecRegistry = new Map([['codec-a', mockCodec]]);
|
|
394
|
+
|
|
395
|
+
const result = compileAnthropicContext(
|
|
396
|
+
blocks,
|
|
397
|
+
createTestPolicy(),
|
|
398
|
+
{
|
|
399
|
+
codecRegistry,
|
|
400
|
+
cacheBreakpoint: {
|
|
401
|
+
codecId: 'codec-a',
|
|
402
|
+
tag: 'tag1',
|
|
403
|
+
source: 'source-b', // Doesn't match
|
|
404
|
+
},
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
// No blocks match all criteria
|
|
409
|
+
const systemMessages = result.system!;
|
|
410
|
+
expect(systemMessages.every((m) => !m.cache_control)).toBe(true);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('Determinism', () => {
|
|
415
|
+
it('should produce same cache placement for same input', () => {
|
|
416
|
+
const blocks: ContextBlock[] = [
|
|
417
|
+
createBlock('hash1', 'pinned', 'block1'),
|
|
418
|
+
createBlock('hash2', 'pinned', 'block2'),
|
|
419
|
+
createBlock('hash3', 'pinned', 'block3'),
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
const codecRegistry = new Map([['test-codec', mockCodec]]);
|
|
423
|
+
|
|
424
|
+
const result1 = compileAnthropicContext(
|
|
425
|
+
blocks,
|
|
426
|
+
createTestPolicy(),
|
|
427
|
+
{
|
|
428
|
+
codecRegistry,
|
|
429
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const result2 = compileAnthropicContext(
|
|
434
|
+
blocks,
|
|
435
|
+
createTestPolicy(),
|
|
436
|
+
{
|
|
437
|
+
codecRegistry,
|
|
438
|
+
cacheBreakpoint: { kind: 'pinned' },
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
expect(result1.system).toEqual(result2.system);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Test helpers
|
|
448
|
+
|
|
449
|
+
function createBlock(
|
|
450
|
+
hash: string,
|
|
451
|
+
kind: 'pinned' | 'memory' | 'history',
|
|
452
|
+
payload: string
|
|
453
|
+
): ContextBlock {
|
|
454
|
+
return {
|
|
455
|
+
blockHash: hash,
|
|
456
|
+
meta: {
|
|
457
|
+
kind,
|
|
458
|
+
sensitivity: 'public',
|
|
459
|
+
codecId: 'test-codec',
|
|
460
|
+
codecVersion: '1.0.0',
|
|
461
|
+
createdAt: Date.now(),
|
|
462
|
+
},
|
|
463
|
+
payload,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function createBlockWithCodec(
|
|
468
|
+
hash: string,
|
|
469
|
+
kind: 'pinned' | 'memory' | 'history',
|
|
470
|
+
codecId: string,
|
|
471
|
+
payload: string
|
|
472
|
+
): ContextBlock {
|
|
473
|
+
return {
|
|
474
|
+
blockHash: hash,
|
|
475
|
+
meta: {
|
|
476
|
+
kind,
|
|
477
|
+
sensitivity: 'public',
|
|
478
|
+
codecId,
|
|
479
|
+
codecVersion: '1.0.0',
|
|
480
|
+
createdAt: Date.now(),
|
|
481
|
+
},
|
|
482
|
+
payload,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function createBlockWithTags(
|
|
487
|
+
hash: string,
|
|
488
|
+
kind: 'pinned' | 'memory' | 'history',
|
|
489
|
+
tags: string[],
|
|
490
|
+
payload: string
|
|
491
|
+
): ContextBlock {
|
|
492
|
+
return {
|
|
493
|
+
blockHash: hash,
|
|
494
|
+
meta: {
|
|
495
|
+
kind,
|
|
496
|
+
sensitivity: 'public',
|
|
497
|
+
codecId: 'test-codec',
|
|
498
|
+
codecVersion: '1.0.0',
|
|
499
|
+
createdAt: Date.now(),
|
|
500
|
+
tags,
|
|
501
|
+
},
|
|
502
|
+
payload,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function createBlockWithSource(
|
|
507
|
+
hash: string,
|
|
508
|
+
kind: 'pinned' | 'memory' | 'history',
|
|
509
|
+
source: string,
|
|
510
|
+
payload: string
|
|
511
|
+
): ContextBlock {
|
|
512
|
+
return {
|
|
513
|
+
blockHash: hash,
|
|
514
|
+
meta: {
|
|
515
|
+
kind,
|
|
516
|
+
sensitivity: 'public',
|
|
517
|
+
codecId: 'test-codec',
|
|
518
|
+
codecVersion: '1.0.0',
|
|
519
|
+
createdAt: Date.now(),
|
|
520
|
+
source,
|
|
521
|
+
},
|
|
522
|
+
payload,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function createBlockWithDetails(
|
|
527
|
+
hash: string,
|
|
528
|
+
kind: 'pinned' | 'memory' | 'history',
|
|
529
|
+
codecId: string,
|
|
530
|
+
tags: string[],
|
|
531
|
+
source: string,
|
|
532
|
+
payload: string
|
|
533
|
+
): ContextBlock {
|
|
534
|
+
return {
|
|
535
|
+
blockHash: hash,
|
|
536
|
+
meta: {
|
|
537
|
+
kind,
|
|
538
|
+
sensitivity: 'public',
|
|
539
|
+
codecId,
|
|
540
|
+
codecVersion: '1.0.0',
|
|
541
|
+
createdAt: Date.now(),
|
|
542
|
+
tags,
|
|
543
|
+
source,
|
|
544
|
+
},
|
|
545
|
+
payload,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function createTestPolicy(): ContextPolicy {
|
|
550
|
+
return {
|
|
551
|
+
modelId: 'test-model',
|
|
552
|
+
provider: 'anthropic',
|
|
553
|
+
contextWindow: 100000,
|
|
554
|
+
completionReserve: 4000,
|
|
555
|
+
overflowStrategy: 'error',
|
|
556
|
+
kindPriorities: [],
|
|
557
|
+
sensitivity: {
|
|
558
|
+
maxSensitivity: 'public',
|
|
559
|
+
redactRestricted: true,
|
|
560
|
+
},
|
|
561
|
+
compaction: {
|
|
562
|
+
pruneToolOutputs: false,
|
|
563
|
+
summarizeHistory: false,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
}
|