@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,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
|
+
}
|