@wundam/orchex 1.0.0-rc.2 → 1.0.0-rc.21
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 +59 -18
- package/dist/cloud-executor.d.ts +71 -0
- package/dist/cloud-executor.js +335 -0
- package/dist/cloud-sync.d.ts +8 -0
- package/dist/cloud-sync.js +52 -0
- package/dist/config.d.ts +30 -4
- package/dist/config.js +61 -2
- package/dist/context-builder.d.ts +2 -0
- package/dist/context-builder.js +11 -3
- package/dist/cost.js +1 -1
- package/dist/entitlements/jwt.d.ts +7 -0
- package/dist/entitlements/jwt.js +78 -0
- package/dist/entitlements/resolve.d.ts +17 -0
- package/dist/entitlements/resolve.js +49 -0
- package/dist/entitlements/types.d.ts +21 -0
- package/dist/entitlements/types.js +4 -0
- package/dist/executors/base.d.ts +1 -1
- package/dist/executors/bedrock-executor.d.ts +39 -0
- package/dist/executors/bedrock-executor.js +197 -0
- package/dist/executors/index.d.ts +1 -0
- package/dist/executors/index.js +24 -1
- package/dist/index.js +468 -23
- package/dist/intelligence/index.d.ts +44 -0
- package/dist/intelligence/index.js +160 -0
- package/dist/key-cache.d.ts +31 -0
- package/dist/key-cache.js +84 -0
- package/dist/login-helpers.d.ts +25 -0
- package/dist/login-helpers.js +54 -0
- package/dist/manifest.js +18 -1
- package/dist/mcp-instructions.d.ts +1 -0
- package/dist/mcp-instructions.js +84 -0
- package/dist/mcp-resources.d.ts +8 -0
- package/dist/mcp-resources.js +420 -0
- package/dist/model-cache.d.ts +18 -0
- package/dist/model-cache.js +62 -0
- package/dist/model-validator.d.ts +20 -0
- package/dist/model-validator.js +125 -0
- package/dist/orchestrator.d.ts +14 -0
- package/dist/orchestrator.js +191 -32
- package/dist/setup/ide-registry.d.ts +13 -0
- package/dist/setup/ide-registry.js +51 -0
- package/dist/setup/index.d.ts +1 -0
- package/dist/setup/index.js +111 -0
- package/dist/tier-gating.js +0 -16
- package/dist/tiers.d.ts +35 -5
- package/dist/tiers.js +39 -3
- package/dist/tools.d.ts +6 -1
- package/dist/tools.js +852 -95
- package/dist/types.d.ts +71 -60
- package/dist/types.js +3 -0
- package/dist/waves.d.ts +1 -1
- package/dist/waves.js +29 -2
- package/package.json +41 -5
- package/src/entitlements/public-key.pem +9 -0
- package/dist/intelligence/anti-pattern-detector.d.ts +0 -117
- package/dist/intelligence/anti-pattern-detector.js +0 -327
- package/dist/intelligence/budget-enforcer.d.ts +0 -119
- package/dist/intelligence/budget-enforcer.js +0 -226
- package/dist/intelligence/context-optimizer.d.ts +0 -111
- package/dist/intelligence/context-optimizer.js +0 -282
- package/dist/intelligence/cost-tracker.d.ts +0 -114
- package/dist/intelligence/cost-tracker.js +0 -183
- package/dist/intelligence/deliverable-extractor.d.ts +0 -134
- package/dist/intelligence/deliverable-extractor.js +0 -909
- package/dist/intelligence/dependency-inferrer.d.ts +0 -87
- package/dist/intelligence/dependency-inferrer.js +0 -403
- package/dist/intelligence/diagnostics.d.ts +0 -33
- package/dist/intelligence/diagnostics.js +0 -64
- package/dist/intelligence/error-analyzer.d.ts +0 -7
- package/dist/intelligence/error-analyzer.js +0 -76
- package/dist/intelligence/file-chunker.d.ts +0 -15
- package/dist/intelligence/file-chunker.js +0 -64
- package/dist/intelligence/fix-stream-manager.d.ts +0 -59
- package/dist/intelligence/fix-stream-manager.js +0 -212
- package/dist/intelligence/heuristics.d.ts +0 -23
- package/dist/intelligence/heuristics.js +0 -124
- package/dist/intelligence/learning-engine.d.ts +0 -157
- package/dist/intelligence/learning-engine.js +0 -433
- package/dist/intelligence/learning-feedback.d.ts +0 -96
- package/dist/intelligence/learning-feedback.js +0 -202
- package/dist/intelligence/pattern-analyzer.d.ts +0 -35
- package/dist/intelligence/pattern-analyzer.js +0 -189
- package/dist/intelligence/plan-parser.d.ts +0 -124
- package/dist/intelligence/plan-parser.js +0 -498
- package/dist/intelligence/planner.d.ts +0 -29
- package/dist/intelligence/planner.js +0 -86
- package/dist/intelligence/self-healer.d.ts +0 -16
- package/dist/intelligence/self-healer.js +0 -84
- package/dist/intelligence/slicing-metrics.d.ts +0 -62
- package/dist/intelligence/slicing-metrics.js +0 -202
- package/dist/intelligence/slicing-templates.d.ts +0 -81
- package/dist/intelligence/slicing-templates.js +0 -420
- package/dist/intelligence/split-suggester.d.ts +0 -69
- package/dist/intelligence/split-suggester.js +0 -176
- package/dist/intelligence/stream-generator.d.ts +0 -90
- package/dist/intelligence/stream-generator.js +0 -452
- package/dist/telemetry/telemetry-types.d.ts +0 -85
- package/dist/telemetry/telemetry-types.js +0 -1
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context budget enforcement for Orchex Learn.
|
|
3
|
-
* Implements soft/hard limit checking with provider-aware context limits.
|
|
4
|
-
*/
|
|
5
|
-
/**
|
|
6
|
-
* Provider-specific context window limits (in tokens).
|
|
7
|
-
* These represent the maximum input context size for each provider's models.
|
|
8
|
-
*/
|
|
9
|
-
export const PROVIDER_CONTEXT_LIMITS = {
|
|
10
|
-
anthropic: 200000, // Claude 4.5 Sonnet/Opus context window
|
|
11
|
-
openai: 128000, // GPT-4.5 Turbo context window
|
|
12
|
-
gemini: 1000000, // Gemini 2.5 Pro context window
|
|
13
|
-
deepseek: 128000, // DeepSeek V3.2 context window
|
|
14
|
-
ollama: 128000, // Llama 3.3 70B context window
|
|
15
|
-
default: 100000, // Safe default for unknown providers
|
|
16
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Model-specific context limits (overrides provider defaults).
|
|
19
|
-
*/
|
|
20
|
-
export const MODEL_CONTEXT_LIMITS = {
|
|
21
|
-
// Anthropic (Feb 2026)
|
|
22
|
-
'claude-opus-4-5-20251101': 200000,
|
|
23
|
-
'claude-sonnet-4-5-20250929': 200000,
|
|
24
|
-
'claude-sonnet-4-20250514': 200000,
|
|
25
|
-
'claude-3-5-sonnet-20241022': 200000,
|
|
26
|
-
'claude-3-opus-20240229': 200000,
|
|
27
|
-
'claude-3-haiku-20240307': 200000,
|
|
28
|
-
// OpenAI (Feb 2026)
|
|
29
|
-
'gpt-4.5-turbo': 128000,
|
|
30
|
-
'gpt-4-turbo': 128000,
|
|
31
|
-
'gpt-4-turbo-preview': 128000,
|
|
32
|
-
'gpt-4o': 128000,
|
|
33
|
-
'gpt-4o-mini': 128000,
|
|
34
|
-
'o1-preview': 128000,
|
|
35
|
-
'o1-mini': 128000,
|
|
36
|
-
'o3-mini': 200000,
|
|
37
|
-
'gpt-3.5-turbo': 16385,
|
|
38
|
-
// Google (Feb 2026)
|
|
39
|
-
'gemini-2.5-pro': 1000000,
|
|
40
|
-
'gemini-2.0-flash': 1000000,
|
|
41
|
-
'gemini-1.5-pro': 1000000,
|
|
42
|
-
'gemini-1.5-flash': 1000000,
|
|
43
|
-
'gemini-pro': 32768,
|
|
44
|
-
// DeepSeek (Feb 2026)
|
|
45
|
-
'deepseek-chat': 128000, // V3.2
|
|
46
|
-
'deepseek-coder': 128000, // V3.2
|
|
47
|
-
'deepseek-reasoner': 128000, // R1
|
|
48
|
-
// Ollama/Local
|
|
49
|
-
'llama3.3:70b': 128000,
|
|
50
|
-
'llama3.2:latest': 128000,
|
|
51
|
-
'mistral:latest': 32000,
|
|
52
|
-
};
|
|
53
|
-
/** Default soft limit as percentage of provider limit */
|
|
54
|
-
export const DEFAULT_SOFT_LIMIT_RATIO = 0.7;
|
|
55
|
-
/** Default hard limit as percentage of provider limit */
|
|
56
|
-
export const DEFAULT_HARD_LIMIT_RATIO = 0.9;
|
|
57
|
-
/** Default warning threshold (start warning at 80% of soft limit) */
|
|
58
|
-
export const DEFAULT_WARNING_THRESHOLD = 0.8;
|
|
59
|
-
/**
|
|
60
|
-
* Get the context limit for a provider, optionally with model-specific override.
|
|
61
|
-
*/
|
|
62
|
-
export function getProviderLimit(provider, model) {
|
|
63
|
-
// Check model-specific limit first
|
|
64
|
-
if (model && MODEL_CONTEXT_LIMITS[model]) {
|
|
65
|
-
return MODEL_CONTEXT_LIMITS[model];
|
|
66
|
-
}
|
|
67
|
-
// Fall back to provider limit
|
|
68
|
-
return PROVIDER_CONTEXT_LIMITS[provider] ?? PROVIDER_CONTEXT_LIMITS.default;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Create a budget config from manifest/stream settings and provider info.
|
|
72
|
-
*/
|
|
73
|
-
export function createBudgetConfig(budget, provider, model) {
|
|
74
|
-
const providerLimit = getProviderLimit(provider, model);
|
|
75
|
-
// Use explicit limits if provided, otherwise derive from provider limit
|
|
76
|
-
const softLimitTokens = budget?.softLimitTokens ?? Math.floor(providerLimit * DEFAULT_SOFT_LIMIT_RATIO);
|
|
77
|
-
const hardLimitTokens = budget?.hardLimitTokens ?? Math.floor(providerLimit * DEFAULT_HARD_LIMIT_RATIO);
|
|
78
|
-
const enforcementLevel = budget?.enforcementLevel ?? 'warn';
|
|
79
|
-
const warningThreshold = budget?.warningThreshold ?? DEFAULT_WARNING_THRESHOLD;
|
|
80
|
-
return {
|
|
81
|
-
enforcementLevel,
|
|
82
|
-
softLimitTokens,
|
|
83
|
-
hardLimitTokens,
|
|
84
|
-
warningThreshold,
|
|
85
|
-
provider,
|
|
86
|
-
model,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Generate a suggestion for reducing context size.
|
|
91
|
-
*/
|
|
92
|
-
export function getSuggestion(violationType, estimatedTokens, limit) {
|
|
93
|
-
const overage = estimatedTokens - limit;
|
|
94
|
-
const percentOver = Math.round((overage / limit) * 100);
|
|
95
|
-
if (violationType === 'none') {
|
|
96
|
-
return '';
|
|
97
|
-
}
|
|
98
|
-
const suggestions = [
|
|
99
|
-
`Context is ${percentOver}% over the ${violationType} limit.`,
|
|
100
|
-
'Consider:',
|
|
101
|
-
' - Reducing the number of files in "reads"',
|
|
102
|
-
' - Splitting the stream into smaller tasks',
|
|
103
|
-
' - Using file patterns instead of directories',
|
|
104
|
-
' - Extracting only function signatures instead of full files',
|
|
105
|
-
];
|
|
106
|
-
if (violationType === 'hard') {
|
|
107
|
-
suggestions.push(' - Or increase the hard limit in contextBudget settings');
|
|
108
|
-
}
|
|
109
|
-
return suggestions.join('\n');
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Check estimated context size against budget limits.
|
|
113
|
-
*
|
|
114
|
-
* @param estimatedTokens - Estimated context tokens
|
|
115
|
-
* @param config - Budget configuration
|
|
116
|
-
* @returns Check result with violation status and suggestions
|
|
117
|
-
*/
|
|
118
|
-
export function checkBudget(estimatedTokens, config) {
|
|
119
|
-
const providerLimit = getProviderLimit(config.provider, config.model);
|
|
120
|
-
const { softLimitTokens, hardLimitTokens, warningThreshold, enforcementLevel } = config;
|
|
121
|
-
// Determine violation type
|
|
122
|
-
let violationType = 'none';
|
|
123
|
-
let budgetLimit = softLimitTokens;
|
|
124
|
-
if (estimatedTokens >= hardLimitTokens) {
|
|
125
|
-
violationType = 'hard';
|
|
126
|
-
budgetLimit = hardLimitTokens;
|
|
127
|
-
}
|
|
128
|
-
else if (estimatedTokens >= softLimitTokens) {
|
|
129
|
-
violationType = 'soft';
|
|
130
|
-
budgetLimit = softLimitTokens;
|
|
131
|
-
}
|
|
132
|
-
// Calculate utilization ratio (against soft limit for warnings)
|
|
133
|
-
const utilizationRatio = estimatedTokens / softLimitTokens;
|
|
134
|
-
// Determine if execution is allowed based on enforcement level
|
|
135
|
-
let allowed = true;
|
|
136
|
-
if (violationType === 'hard' && enforcementLevel === 'hard') {
|
|
137
|
-
allowed = false;
|
|
138
|
-
}
|
|
139
|
-
else if (violationType === 'soft' && enforcementLevel === 'soft') {
|
|
140
|
-
// Soft enforcement: warn but don't block by default
|
|
141
|
-
// Could be extended to block with user confirmation
|
|
142
|
-
allowed = true;
|
|
143
|
-
}
|
|
144
|
-
// Generate warning if needed
|
|
145
|
-
let warning;
|
|
146
|
-
if (violationType !== 'none') {
|
|
147
|
-
const limitType = violationType === 'hard' ? 'hard limit' : 'soft limit';
|
|
148
|
-
warning = `Context size (${estimatedTokens.toLocaleString()} tokens) exceeds ${limitType} (${budgetLimit.toLocaleString()} tokens)`;
|
|
149
|
-
if (!allowed) {
|
|
150
|
-
warning += '. Execution blocked.';
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
else if (utilizationRatio >= warningThreshold) {
|
|
154
|
-
// Warn if approaching soft limit
|
|
155
|
-
const percentUsed = Math.round(utilizationRatio * 100);
|
|
156
|
-
warning = `Context size at ${percentUsed}% of soft limit (${estimatedTokens.toLocaleString()} / ${softLimitTokens.toLocaleString()} tokens)`;
|
|
157
|
-
}
|
|
158
|
-
// Generate suggestion if violating
|
|
159
|
-
const suggestion = violationType !== 'none'
|
|
160
|
-
? getSuggestion(violationType, estimatedTokens, budgetLimit)
|
|
161
|
-
: undefined;
|
|
162
|
-
return {
|
|
163
|
-
allowed,
|
|
164
|
-
violationType,
|
|
165
|
-
estimatedTokens,
|
|
166
|
-
budgetLimit,
|
|
167
|
-
utilizationRatio,
|
|
168
|
-
warning,
|
|
169
|
-
suggestion,
|
|
170
|
-
providerLimit,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Analyze budget for multiple streams.
|
|
175
|
-
*
|
|
176
|
-
* @param streams - Map of stream ID to estimated tokens
|
|
177
|
-
* @param config - Budget configuration
|
|
178
|
-
* @returns Analysis result with warnings and violation details
|
|
179
|
-
*/
|
|
180
|
-
export function analyzeStreamBudgets(streams, config) {
|
|
181
|
-
const analyses = [];
|
|
182
|
-
const streamsAtRisk = [];
|
|
183
|
-
const streamsTooLarge = [];
|
|
184
|
-
const warnings = [];
|
|
185
|
-
let totalEstimatedTokens = 0;
|
|
186
|
-
for (const [streamId, estimatedTokens] of streams) {
|
|
187
|
-
const check = checkBudget(estimatedTokens, config);
|
|
188
|
-
totalEstimatedTokens += estimatedTokens;
|
|
189
|
-
analyses.push({ streamId, estimatedTokens, check });
|
|
190
|
-
if (check.violationType === 'hard') {
|
|
191
|
-
streamsTooLarge.push(streamId);
|
|
192
|
-
warnings.push(`Stream "${streamId}": ${check.warning}`);
|
|
193
|
-
}
|
|
194
|
-
else if (check.violationType === 'soft') {
|
|
195
|
-
streamsAtRisk.push(streamId);
|
|
196
|
-
warnings.push(`Stream "${streamId}": ${check.warning}`);
|
|
197
|
-
}
|
|
198
|
-
else if (check.warning) {
|
|
199
|
-
// Approaching limit warning
|
|
200
|
-
warnings.push(`Stream "${streamId}": ${check.warning}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
totalEstimatedTokens,
|
|
205
|
-
streamsAtRisk,
|
|
206
|
-
streamsTooLarge,
|
|
207
|
-
hasHardViolations: streamsTooLarge.length > 0,
|
|
208
|
-
hasSoftViolations: streamsAtRisk.length > 0,
|
|
209
|
-
analyses,
|
|
210
|
-
warnings,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Custom error for budget exceeded scenarios.
|
|
215
|
-
*/
|
|
216
|
-
export class ContextBudgetExceededError extends Error {
|
|
217
|
-
checkResult;
|
|
218
|
-
streamId;
|
|
219
|
-
constructor(checkResult, streamId) {
|
|
220
|
-
const streamInfo = streamId ? ` for stream "${streamId}"` : '';
|
|
221
|
-
super(`Context budget exceeded${streamInfo}: ${checkResult.warning}`);
|
|
222
|
-
this.checkResult = checkResult;
|
|
223
|
-
this.streamId = streamId;
|
|
224
|
-
this.name = 'ContextBudgetExceededError';
|
|
225
|
-
}
|
|
226
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
export interface TokenBudget {
|
|
2
|
-
totalTokens: number;
|
|
3
|
-
projectContext: number;
|
|
4
|
-
streamContext: number;
|
|
5
|
-
dependencyContext: number;
|
|
6
|
-
instructions: number;
|
|
7
|
-
}
|
|
8
|
-
export interface ContextOptimization {
|
|
9
|
-
includedFiles: string[];
|
|
10
|
-
excludedFiles: string[];
|
|
11
|
-
tokenBudget: TokenBudget;
|
|
12
|
-
cachingHints: CachingHint[];
|
|
13
|
-
estimatedSavings: number;
|
|
14
|
-
warnings?: string[];
|
|
15
|
-
metrics?: OptimizationMetrics;
|
|
16
|
-
}
|
|
17
|
-
export interface CachingHint {
|
|
18
|
-
content: string;
|
|
19
|
-
type: 'system_prompt' | 'project_context' | 'stream_context';
|
|
20
|
-
reusable: boolean;
|
|
21
|
-
estimatedTokens: number;
|
|
22
|
-
}
|
|
23
|
-
export interface OptimizationMetrics {
|
|
24
|
-
totalFiles: number;
|
|
25
|
-
includedFiles: number;
|
|
26
|
-
excludedFiles: number;
|
|
27
|
-
originalTokens: number;
|
|
28
|
-
optimizedTokens: number;
|
|
29
|
-
compressionRatio: number;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Default token budgets by model.
|
|
33
|
-
* Based on Anthropic's context window sizes.
|
|
34
|
-
*/
|
|
35
|
-
export declare const MODEL_TOKEN_LIMITS: Record<string, number>;
|
|
36
|
-
/**
|
|
37
|
-
* Analyze import graph and return files actually needed for a stream.
|
|
38
|
-
* Prunes files that are not transitively imported.
|
|
39
|
-
*
|
|
40
|
-
* Uses breadth-first search to traverse the import graph starting from owned files.
|
|
41
|
-
* Detects circular dependencies and includes them in warnings.
|
|
42
|
-
*
|
|
43
|
-
* Algorithm:
|
|
44
|
-
* - BFS from owned files, following imports
|
|
45
|
-
* - Track the import chain (path from root) for each discovered file
|
|
46
|
-
* - Detect cycles when we find an import to an already-visited file
|
|
47
|
-
*/
|
|
48
|
-
export declare function pruneUnusedFiles(ownedFiles: string[], readFiles: string[], fileContents: Record<string, string>): {
|
|
49
|
-
needed: string[];
|
|
50
|
-
pruned: string[];
|
|
51
|
-
warnings?: string[];
|
|
52
|
-
};
|
|
53
|
-
/**
|
|
54
|
-
* Allocate token budget across context layers with intelligent prioritization.
|
|
55
|
-
*
|
|
56
|
-
* Priority: instructions > stream context > dependency context > project context
|
|
57
|
-
*
|
|
58
|
-
* Strategy:
|
|
59
|
-
* - Instructions: Fixed 15% or 1000 tokens (whichever is smaller) for task description
|
|
60
|
-
* - Stream context: Up to 50% of remaining, based on actual file sizes
|
|
61
|
-
* - Dependency context: Up to 30% of remaining, ~300 tokens per artifact
|
|
62
|
-
* - Project context: Remaining budget for file tree and metadata
|
|
63
|
-
*
|
|
64
|
-
* When file contents are provided, uses actual token estimates for more accurate allocation.
|
|
65
|
-
*/
|
|
66
|
-
export declare function allocateTokenBudget(totalTokens: number, streamFiles: string[], dependencyArtifacts: number, fileContents?: Record<string, string>): TokenBudget;
|
|
67
|
-
/**
|
|
68
|
-
* Generate caching hints for Anthropic's prompt caching.
|
|
69
|
-
*
|
|
70
|
-
* Prompt caching can reduce costs by up to 90% for repeated context.
|
|
71
|
-
* See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
|
72
|
-
*
|
|
73
|
-
* Caching strategy:
|
|
74
|
-
* - System prompts: Highly reusable across all streams
|
|
75
|
-
* - Project context: Reusable within an orchestration run
|
|
76
|
-
* - Stream context: Reusable across retries of the same stream
|
|
77
|
-
*
|
|
78
|
-
* Only generates hints for content exceeding minimum thresholds to avoid
|
|
79
|
-
* cache overhead on small contexts.
|
|
80
|
-
*/
|
|
81
|
-
export declare function generateCachingHints(projectContext: string, streamContext: string, instructions: string): CachingHint[];
|
|
82
|
-
/**
|
|
83
|
-
* Estimate token count for a string using content-aware heuristics.
|
|
84
|
-
*
|
|
85
|
-
* Different content types have different token densities:
|
|
86
|
-
* - Code: ~3.5 chars/token (dense, lots of symbols)
|
|
87
|
-
* - Comments: ~4.5 chars/token (prose, more natural language)
|
|
88
|
-
* - Whitespace: ~6.0 chars/token (sparse)
|
|
89
|
-
* - JSON: ~3.8 chars/token (structured data)
|
|
90
|
-
* - Markdown: ~4.2 chars/token (formatted prose)
|
|
91
|
-
*
|
|
92
|
-
* This provides better accuracy than simple char/4 estimation.
|
|
93
|
-
*/
|
|
94
|
-
export declare function estimateTokens(text: string): number;
|
|
95
|
-
/**
|
|
96
|
-
* Full context optimization for a stream.
|
|
97
|
-
*
|
|
98
|
-
* This is the main entry point for context optimization. It:
|
|
99
|
-
* 1. Prunes unused files based on import graph analysis
|
|
100
|
-
* 2. Allocates token budget across context layers
|
|
101
|
-
* 3. Generates prompt caching hints for cost optimization
|
|
102
|
-
* 4. Calculates optimization metrics and savings estimates
|
|
103
|
-
*
|
|
104
|
-
* @param ownedFiles - Files this stream owns (will always be included)
|
|
105
|
-
* @param readFiles - Files this stream needs to read (will always be included)
|
|
106
|
-
* @param fileContents - Map of file paths to their contents
|
|
107
|
-
* @param dependencyArtifacts - Number of dependency artifacts to include
|
|
108
|
-
* @param model - Claude model name for token limit lookup
|
|
109
|
-
* @returns Complete optimization analysis with included/excluded files, budgets, and hints
|
|
110
|
-
*/
|
|
111
|
-
export declare function optimizeContext(ownedFiles: string[], readFiles: string[], fileContents: Record<string, string>, dependencyArtifacts: number, model?: string): ContextOptimization;
|
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { extractImports, resolveImportPath } from './heuristics.js';
|
|
2
|
-
/**
|
|
3
|
-
* Default token budgets by model.
|
|
4
|
-
* Based on Anthropic's context window sizes.
|
|
5
|
-
*/
|
|
6
|
-
export const MODEL_TOKEN_LIMITS = {
|
|
7
|
-
'claude-sonnet-4-20250514': 8192,
|
|
8
|
-
'claude-3-5-sonnet-20241022': 8192,
|
|
9
|
-
'claude-3-opus-20240229': 4096,
|
|
10
|
-
default: 8192,
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Token estimation constants based on empirical analysis.
|
|
14
|
-
* Different content types have different token densities.
|
|
15
|
-
*/
|
|
16
|
-
const TOKEN_DENSITY = {
|
|
17
|
-
CODE: 3.5, // chars per token for code
|
|
18
|
-
COMMENTS: 4.5, // chars per token for comments/prose
|
|
19
|
-
WHITESPACE: 6.0, // chars per token for whitespace
|
|
20
|
-
JSON: 3.8, // chars per token for JSON
|
|
21
|
-
MARKDOWN: 4.2, // chars per token for markdown
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Analyze import graph and return files actually needed for a stream.
|
|
25
|
-
* Prunes files that are not transitively imported.
|
|
26
|
-
*
|
|
27
|
-
* Uses breadth-first search to traverse the import graph starting from owned files.
|
|
28
|
-
* Detects circular dependencies and includes them in warnings.
|
|
29
|
-
*
|
|
30
|
-
* Algorithm:
|
|
31
|
-
* - BFS from owned files, following imports
|
|
32
|
-
* - Track the import chain (path from root) for each discovered file
|
|
33
|
-
* - Detect cycles when we find an import to an already-visited file
|
|
34
|
-
*/
|
|
35
|
-
export function pruneUnusedFiles(ownedFiles, readFiles, fileContents) {
|
|
36
|
-
const needed = new Set([...ownedFiles]);
|
|
37
|
-
const visited = new Set();
|
|
38
|
-
const queue = [...ownedFiles];
|
|
39
|
-
const warnings = [];
|
|
40
|
-
const importChain = new Map(); // Track import path for cycle reporting
|
|
41
|
-
// Initialize chains for owned files (they're the roots)
|
|
42
|
-
for (const file of ownedFiles) {
|
|
43
|
-
importChain.set(file, []);
|
|
44
|
-
}
|
|
45
|
-
// BFS through import graph
|
|
46
|
-
while (queue.length > 0) {
|
|
47
|
-
const file = queue.shift();
|
|
48
|
-
if (visited.has(file))
|
|
49
|
-
continue; // Already processed
|
|
50
|
-
visited.add(file);
|
|
51
|
-
const content = fileContents[file];
|
|
52
|
-
if (!content)
|
|
53
|
-
continue;
|
|
54
|
-
const imports = extractImports(content);
|
|
55
|
-
for (const imp of imports) {
|
|
56
|
-
const resolved = resolveImportPath(file, imp);
|
|
57
|
-
if (resolved && fileContents[resolved]) {
|
|
58
|
-
if (visited.has(resolved)) {
|
|
59
|
-
// CYCLE DETECTED: importing a file we've already fully processed
|
|
60
|
-
const currentChain = importChain.get(file) || [];
|
|
61
|
-
warnings.push(`Circular dependency detected: ${[...currentChain, file, resolved].join(' → ')}`);
|
|
62
|
-
}
|
|
63
|
-
else if (!needed.has(resolved)) {
|
|
64
|
-
// New file discovered - add to queue
|
|
65
|
-
needed.add(resolved);
|
|
66
|
-
queue.push(resolved);
|
|
67
|
-
// Track import chain for cycle reporting
|
|
68
|
-
const currentChain = importChain.get(file) || [];
|
|
69
|
-
importChain.set(resolved, [...currentChain, file]);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Add explicitly requested read files
|
|
75
|
-
for (const f of readFiles) {
|
|
76
|
-
needed.add(f);
|
|
77
|
-
}
|
|
78
|
-
const pruned = Object.keys(fileContents).filter(f => !needed.has(f));
|
|
79
|
-
return {
|
|
80
|
-
needed: [...needed],
|
|
81
|
-
pruned,
|
|
82
|
-
warnings: warnings.length > 0 ? warnings : undefined
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Allocate token budget across context layers with intelligent prioritization.
|
|
87
|
-
*
|
|
88
|
-
* Priority: instructions > stream context > dependency context > project context
|
|
89
|
-
*
|
|
90
|
-
* Strategy:
|
|
91
|
-
* - Instructions: Fixed 15% or 1000 tokens (whichever is smaller) for task description
|
|
92
|
-
* - Stream context: Up to 50% of remaining, based on actual file sizes
|
|
93
|
-
* - Dependency context: Up to 30% of remaining, ~300 tokens per artifact
|
|
94
|
-
* - Project context: Remaining budget for file tree and metadata
|
|
95
|
-
*
|
|
96
|
-
* When file contents are provided, uses actual token estimates for more accurate allocation.
|
|
97
|
-
*/
|
|
98
|
-
export function allocateTokenBudget(totalTokens, streamFiles, dependencyArtifacts, fileContents) {
|
|
99
|
-
// Reserve fixed amounts for essential parts
|
|
100
|
-
const instructions = Math.min(1000, totalTokens * 0.15); // 15% or 1000 max
|
|
101
|
-
const remaining = totalTokens - instructions;
|
|
102
|
-
// If we have file contents, use actual sizes; otherwise use estimates
|
|
103
|
-
let streamContext;
|
|
104
|
-
if (fileContents) {
|
|
105
|
-
const actualTokens = streamFiles.reduce((sum, file) => {
|
|
106
|
-
const content = fileContents[file] || '';
|
|
107
|
-
return sum + estimateTokens(content);
|
|
108
|
-
}, 0);
|
|
109
|
-
streamContext = Math.min(remaining * 0.5, actualTokens);
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
streamContext = Math.min(remaining * 0.5, streamFiles.length * 500); // ~500 tokens per file estimate
|
|
113
|
-
}
|
|
114
|
-
const afterStream = remaining - streamContext;
|
|
115
|
-
// Dependency context: ~300 tokens per artifact
|
|
116
|
-
const dependencyContext = Math.min(afterStream * 0.3, dependencyArtifacts * 300);
|
|
117
|
-
const afterDeps = afterStream - dependencyContext;
|
|
118
|
-
// Project context gets the rest
|
|
119
|
-
const projectContext = afterDeps;
|
|
120
|
-
return {
|
|
121
|
-
totalTokens,
|
|
122
|
-
projectContext: Math.floor(projectContext),
|
|
123
|
-
streamContext: Math.floor(streamContext),
|
|
124
|
-
dependencyContext: Math.floor(dependencyContext),
|
|
125
|
-
instructions: Math.floor(instructions),
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Generate caching hints for Anthropic's prompt caching.
|
|
130
|
-
*
|
|
131
|
-
* Prompt caching can reduce costs by up to 90% for repeated context.
|
|
132
|
-
* See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
|
133
|
-
*
|
|
134
|
-
* Caching strategy:
|
|
135
|
-
* - System prompts: Highly reusable across all streams
|
|
136
|
-
* - Project context: Reusable within an orchestration run
|
|
137
|
-
* - Stream context: Reusable across retries of the same stream
|
|
138
|
-
*
|
|
139
|
-
* Only generates hints for content exceeding minimum thresholds to avoid
|
|
140
|
-
* cache overhead on small contexts.
|
|
141
|
-
*/
|
|
142
|
-
export function generateCachingHints(projectContext, streamContext, instructions) {
|
|
143
|
-
const hints = [];
|
|
144
|
-
// System prompt / instructions are highly reusable
|
|
145
|
-
if (instructions.length > 100) {
|
|
146
|
-
hints.push({
|
|
147
|
-
content: instructions,
|
|
148
|
-
type: 'system_prompt',
|
|
149
|
-
reusable: true,
|
|
150
|
-
estimatedTokens: estimateTokens(instructions),
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
// Project context (file tree, deps) is reusable within an orchestration
|
|
154
|
-
if (projectContext.length > 500) {
|
|
155
|
-
hints.push({
|
|
156
|
-
content: projectContext,
|
|
157
|
-
type: 'project_context',
|
|
158
|
-
reusable: true,
|
|
159
|
-
estimatedTokens: estimateTokens(projectContext),
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
// Stream context is reusable across retries of the same stream
|
|
163
|
-
if (streamContext.length > 500) {
|
|
164
|
-
hints.push({
|
|
165
|
-
content: streamContext,
|
|
166
|
-
type: 'stream_context',
|
|
167
|
-
reusable: true,
|
|
168
|
-
estimatedTokens: estimateTokens(streamContext),
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
return hints;
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Estimate token count for a string using content-aware heuristics.
|
|
175
|
-
*
|
|
176
|
-
* Different content types have different token densities:
|
|
177
|
-
* - Code: ~3.5 chars/token (dense, lots of symbols)
|
|
178
|
-
* - Comments: ~4.5 chars/token (prose, more natural language)
|
|
179
|
-
* - Whitespace: ~6.0 chars/token (sparse)
|
|
180
|
-
* - JSON: ~3.8 chars/token (structured data)
|
|
181
|
-
* - Markdown: ~4.2 chars/token (formatted prose)
|
|
182
|
-
*
|
|
183
|
-
* This provides better accuracy than simple char/4 estimation.
|
|
184
|
-
*/
|
|
185
|
-
export function estimateTokens(text) {
|
|
186
|
-
if (!text || text.length === 0)
|
|
187
|
-
return 0;
|
|
188
|
-
// Detect file type from content patterns
|
|
189
|
-
const isJson = text.trim().startsWith('{') || text.trim().startsWith('[');
|
|
190
|
-
const isMarkdown = /^#+\s|^-\s|^\*\s|^\d+\.\s/m.test(text);
|
|
191
|
-
// For JSON and Markdown, use specialized densities
|
|
192
|
-
if (isJson) {
|
|
193
|
-
return Math.ceil(text.length / TOKEN_DENSITY.JSON);
|
|
194
|
-
}
|
|
195
|
-
if (isMarkdown) {
|
|
196
|
-
return Math.ceil(text.length / TOKEN_DENSITY.MARKDOWN);
|
|
197
|
-
}
|
|
198
|
-
// For code, analyze line-by-line
|
|
199
|
-
const lines = text.split('\n');
|
|
200
|
-
let totalChars = 0;
|
|
201
|
-
let codeChars = 0;
|
|
202
|
-
let commentChars = 0;
|
|
203
|
-
let whitespaceChars = 0;
|
|
204
|
-
for (const line of lines) {
|
|
205
|
-
const trimmed = line.trim();
|
|
206
|
-
totalChars += line.length;
|
|
207
|
-
// Whitespace-only lines
|
|
208
|
-
if (trimmed.length === 0) {
|
|
209
|
-
whitespaceChars += line.length;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
// Detect code vs comments (rough heuristic)
|
|
213
|
-
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('#')) {
|
|
214
|
-
commentChars += line.length;
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
codeChars += line.length;
|
|
218
|
-
// Account for indentation as whitespace
|
|
219
|
-
const indent = line.length - trimmed.length;
|
|
220
|
-
whitespaceChars += indent;
|
|
221
|
-
codeChars -= indent;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
// Calculate tokens by content type
|
|
225
|
-
const codeTokens = Math.ceil(codeChars / TOKEN_DENSITY.CODE);
|
|
226
|
-
const commentTokens = Math.ceil(commentChars / TOKEN_DENSITY.COMMENTS);
|
|
227
|
-
const whitespaceTokens = Math.ceil(whitespaceChars / TOKEN_DENSITY.WHITESPACE);
|
|
228
|
-
const otherTokens = Math.ceil((totalChars - codeChars - commentChars - whitespaceChars) / TOKEN_DENSITY.COMMENTS);
|
|
229
|
-
return codeTokens + commentTokens + whitespaceTokens + otherTokens;
|
|
230
|
-
}
|
|
231
|
-
/**
|
|
232
|
-
* Full context optimization for a stream.
|
|
233
|
-
*
|
|
234
|
-
* This is the main entry point for context optimization. It:
|
|
235
|
-
* 1. Prunes unused files based on import graph analysis
|
|
236
|
-
* 2. Allocates token budget across context layers
|
|
237
|
-
* 3. Generates prompt caching hints for cost optimization
|
|
238
|
-
* 4. Calculates optimization metrics and savings estimates
|
|
239
|
-
*
|
|
240
|
-
* @param ownedFiles - Files this stream owns (will always be included)
|
|
241
|
-
* @param readFiles - Files this stream needs to read (will always be included)
|
|
242
|
-
* @param fileContents - Map of file paths to their contents
|
|
243
|
-
* @param dependencyArtifacts - Number of dependency artifacts to include
|
|
244
|
-
* @param model - Claude model name for token limit lookup
|
|
245
|
-
* @returns Complete optimization analysis with included/excluded files, budgets, and hints
|
|
246
|
-
*/
|
|
247
|
-
export function optimizeContext(ownedFiles, readFiles, fileContents, dependencyArtifacts, model = 'default') {
|
|
248
|
-
const totalTokens = MODEL_TOKEN_LIMITS[model] ?? MODEL_TOKEN_LIMITS.default;
|
|
249
|
-
// Prune unused files
|
|
250
|
-
const { needed, pruned, warnings } = pruneUnusedFiles(ownedFiles, readFiles, fileContents);
|
|
251
|
-
// Build context strings
|
|
252
|
-
const streamContext = needed.map(f => fileContents[f] || '').join('\n');
|
|
253
|
-
const projectContext = `Project files: ${Object.keys(fileContents).length}\nIncluded: ${needed.length}`;
|
|
254
|
-
const instructions = `You are implementing stream changes. Output an orchex-artifact block.`;
|
|
255
|
-
// Allocate budget using actual file contents
|
|
256
|
-
const tokenBudget = allocateTokenBudget(totalTokens, needed, dependencyArtifacts, fileContents);
|
|
257
|
-
// Generate caching hints
|
|
258
|
-
const cachingHints = generateCachingHints(projectContext, streamContext, instructions);
|
|
259
|
-
// Calculate optimization metrics
|
|
260
|
-
const originalTokens = estimateTokens(Object.values(fileContents).join('\n'));
|
|
261
|
-
const optimizedTokens = estimateTokens(streamContext);
|
|
262
|
-
const estimatedSavings = originalTokens > 0
|
|
263
|
-
? Math.round((1 - optimizedTokens / originalTokens) * 100)
|
|
264
|
-
: 0;
|
|
265
|
-
const metrics = {
|
|
266
|
-
totalFiles: Object.keys(fileContents).length,
|
|
267
|
-
includedFiles: needed.length,
|
|
268
|
-
excludedFiles: pruned.length,
|
|
269
|
-
originalTokens,
|
|
270
|
-
optimizedTokens,
|
|
271
|
-
compressionRatio: originalTokens > 0 ? optimizedTokens / originalTokens : 0,
|
|
272
|
-
};
|
|
273
|
-
return {
|
|
274
|
-
includedFiles: needed,
|
|
275
|
-
excludedFiles: pruned,
|
|
276
|
-
tokenBudget,
|
|
277
|
-
cachingHints,
|
|
278
|
-
estimatedSavings,
|
|
279
|
-
warnings,
|
|
280
|
-
metrics,
|
|
281
|
-
};
|
|
282
|
-
}
|