@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.
Files changed (98) hide show
  1. package/README.md +59 -18
  2. package/dist/cloud-executor.d.ts +71 -0
  3. package/dist/cloud-executor.js +335 -0
  4. package/dist/cloud-sync.d.ts +8 -0
  5. package/dist/cloud-sync.js +52 -0
  6. package/dist/config.d.ts +30 -4
  7. package/dist/config.js +61 -2
  8. package/dist/context-builder.d.ts +2 -0
  9. package/dist/context-builder.js +11 -3
  10. package/dist/cost.js +1 -1
  11. package/dist/entitlements/jwt.d.ts +7 -0
  12. package/dist/entitlements/jwt.js +78 -0
  13. package/dist/entitlements/resolve.d.ts +17 -0
  14. package/dist/entitlements/resolve.js +49 -0
  15. package/dist/entitlements/types.d.ts +21 -0
  16. package/dist/entitlements/types.js +4 -0
  17. package/dist/executors/base.d.ts +1 -1
  18. package/dist/executors/bedrock-executor.d.ts +39 -0
  19. package/dist/executors/bedrock-executor.js +197 -0
  20. package/dist/executors/index.d.ts +1 -0
  21. package/dist/executors/index.js +24 -1
  22. package/dist/index.js +468 -23
  23. package/dist/intelligence/index.d.ts +44 -0
  24. package/dist/intelligence/index.js +160 -0
  25. package/dist/key-cache.d.ts +31 -0
  26. package/dist/key-cache.js +84 -0
  27. package/dist/login-helpers.d.ts +25 -0
  28. package/dist/login-helpers.js +54 -0
  29. package/dist/manifest.js +18 -1
  30. package/dist/mcp-instructions.d.ts +1 -0
  31. package/dist/mcp-instructions.js +84 -0
  32. package/dist/mcp-resources.d.ts +8 -0
  33. package/dist/mcp-resources.js +420 -0
  34. package/dist/model-cache.d.ts +18 -0
  35. package/dist/model-cache.js +62 -0
  36. package/dist/model-validator.d.ts +20 -0
  37. package/dist/model-validator.js +125 -0
  38. package/dist/orchestrator.d.ts +14 -0
  39. package/dist/orchestrator.js +191 -32
  40. package/dist/setup/ide-registry.d.ts +13 -0
  41. package/dist/setup/ide-registry.js +51 -0
  42. package/dist/setup/index.d.ts +1 -0
  43. package/dist/setup/index.js +111 -0
  44. package/dist/tier-gating.js +0 -16
  45. package/dist/tiers.d.ts +35 -5
  46. package/dist/tiers.js +39 -3
  47. package/dist/tools.d.ts +6 -1
  48. package/dist/tools.js +852 -95
  49. package/dist/types.d.ts +71 -60
  50. package/dist/types.js +3 -0
  51. package/dist/waves.d.ts +1 -1
  52. package/dist/waves.js +29 -2
  53. package/package.json +41 -5
  54. package/src/entitlements/public-key.pem +9 -0
  55. package/dist/intelligence/anti-pattern-detector.d.ts +0 -117
  56. package/dist/intelligence/anti-pattern-detector.js +0 -327
  57. package/dist/intelligence/budget-enforcer.d.ts +0 -119
  58. package/dist/intelligence/budget-enforcer.js +0 -226
  59. package/dist/intelligence/context-optimizer.d.ts +0 -111
  60. package/dist/intelligence/context-optimizer.js +0 -282
  61. package/dist/intelligence/cost-tracker.d.ts +0 -114
  62. package/dist/intelligence/cost-tracker.js +0 -183
  63. package/dist/intelligence/deliverable-extractor.d.ts +0 -134
  64. package/dist/intelligence/deliverable-extractor.js +0 -909
  65. package/dist/intelligence/dependency-inferrer.d.ts +0 -87
  66. package/dist/intelligence/dependency-inferrer.js +0 -403
  67. package/dist/intelligence/diagnostics.d.ts +0 -33
  68. package/dist/intelligence/diagnostics.js +0 -64
  69. package/dist/intelligence/error-analyzer.d.ts +0 -7
  70. package/dist/intelligence/error-analyzer.js +0 -76
  71. package/dist/intelligence/file-chunker.d.ts +0 -15
  72. package/dist/intelligence/file-chunker.js +0 -64
  73. package/dist/intelligence/fix-stream-manager.d.ts +0 -59
  74. package/dist/intelligence/fix-stream-manager.js +0 -212
  75. package/dist/intelligence/heuristics.d.ts +0 -23
  76. package/dist/intelligence/heuristics.js +0 -124
  77. package/dist/intelligence/learning-engine.d.ts +0 -157
  78. package/dist/intelligence/learning-engine.js +0 -433
  79. package/dist/intelligence/learning-feedback.d.ts +0 -96
  80. package/dist/intelligence/learning-feedback.js +0 -202
  81. package/dist/intelligence/pattern-analyzer.d.ts +0 -35
  82. package/dist/intelligence/pattern-analyzer.js +0 -189
  83. package/dist/intelligence/plan-parser.d.ts +0 -124
  84. package/dist/intelligence/plan-parser.js +0 -498
  85. package/dist/intelligence/planner.d.ts +0 -29
  86. package/dist/intelligence/planner.js +0 -86
  87. package/dist/intelligence/self-healer.d.ts +0 -16
  88. package/dist/intelligence/self-healer.js +0 -84
  89. package/dist/intelligence/slicing-metrics.d.ts +0 -62
  90. package/dist/intelligence/slicing-metrics.js +0 -202
  91. package/dist/intelligence/slicing-templates.d.ts +0 -81
  92. package/dist/intelligence/slicing-templates.js +0 -420
  93. package/dist/intelligence/split-suggester.d.ts +0 -69
  94. package/dist/intelligence/split-suggester.js +0 -176
  95. package/dist/intelligence/stream-generator.d.ts +0 -90
  96. package/dist/intelligence/stream-generator.js +0 -452
  97. package/dist/telemetry/telemetry-types.d.ts +0 -85
  98. 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
- }