@claudetools/tools 0.8.2 → 0.8.4
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/dist/cli.js +41 -0
- package/dist/context/deduplication.d.ts +72 -0
- package/dist/context/deduplication.js +77 -0
- package/dist/context/deduplication.test.d.ts +6 -0
- package/dist/context/deduplication.test.js +84 -0
- package/dist/context/emergency-eviction.d.ts +73 -0
- package/dist/context/emergency-eviction.example.d.ts +13 -0
- package/dist/context/emergency-eviction.example.js +94 -0
- package/dist/context/emergency-eviction.js +226 -0
- package/dist/context/eviction-engine.d.ts +76 -0
- package/dist/context/eviction-engine.example.d.ts +7 -0
- package/dist/context/eviction-engine.example.js +144 -0
- package/dist/context/eviction-engine.js +176 -0
- package/dist/context/example-usage.d.ts +1 -0
- package/dist/context/example-usage.js +128 -0
- package/dist/context/exchange-summariser.d.ts +80 -0
- package/dist/context/exchange-summariser.js +261 -0
- package/dist/context/health-monitor.d.ts +97 -0
- package/dist/context/health-monitor.example.d.ts +1 -0
- package/dist/context/health-monitor.example.js +164 -0
- package/dist/context/health-monitor.js +210 -0
- package/dist/context/importance-scorer.d.ts +94 -0
- package/dist/context/importance-scorer.example.d.ts +1 -0
- package/dist/context/importance-scorer.example.js +140 -0
- package/dist/context/importance-scorer.js +187 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.js +16 -0
- package/dist/context/session-helper.d.ts +10 -0
- package/dist/context/session-helper.js +51 -0
- package/dist/context/session-store.d.ts +94 -0
- package/dist/context/session-store.js +286 -0
- package/dist/context/usage-estimator.d.ts +131 -0
- package/dist/context/usage-estimator.js +260 -0
- package/dist/context/usage-estimator.test.d.ts +1 -0
- package/dist/context/usage-estimator.test.js +208 -0
- package/dist/context-cli.d.ts +16 -0
- package/dist/context-cli.js +309 -0
- package/dist/evaluation/build-dataset.d.ts +1 -0
- package/dist/evaluation/build-dataset.js +135 -0
- package/dist/evaluation/threshold-eval.d.ts +63 -0
- package/dist/evaluation/threshold-eval.js +250 -0
- package/dist/handlers/codedna-handlers.d.ts +2 -2
- package/dist/handlers/tool-handlers.js +126 -165
- package/dist/helpers/api-client.d.ts +5 -1
- package/dist/helpers/api-client.js +3 -1
- package/dist/helpers/compact-formatter.d.ts +51 -0
- package/dist/helpers/compact-formatter.js +130 -0
- package/dist/helpers/engagement-tracker.d.ts +10 -0
- package/dist/helpers/engagement-tracker.js +61 -0
- package/dist/helpers/error-tracking.js +1 -1
- package/dist/helpers/session-validation.d.ts +76 -0
- package/dist/helpers/session-validation.js +221 -0
- package/dist/helpers/usage-analytics.js +1 -1
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/post-tool-use-hook-cli.d.ts +2 -0
- package/dist/hooks/post-tool-use-hook-cli.js +34 -0
- package/dist/hooks/post-tool-use.d.ts +67 -0
- package/dist/hooks/post-tool-use.js +234 -0
- package/dist/hooks/stop-hook-cli.d.ts +2 -0
- package/dist/hooks/stop-hook-cli.js +34 -0
- package/dist/hooks/stop.d.ts +64 -0
- package/dist/hooks/stop.js +192 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +4 -0
- package/dist/resources.js +3 -0
- package/dist/setup.js +206 -2
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +23 -35
- package/dist/templates/worker-prompt.js +35 -202
- package/dist/tools.js +26 -20
- package/package.json +6 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Usage Estimator Example
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Example demonstrating how to use the context usage estimator in practice.
|
|
6
|
+
//
|
|
7
|
+
// =============================================================================
|
|
8
|
+
import { usageEstimator, estimateTokens, isContextNearLimit, getContextFill, MODEL_CONTEXT_LIMITS, } from './index.js';
|
|
9
|
+
/**
|
|
10
|
+
* Example: Track a complete conversation flow
|
|
11
|
+
*/
|
|
12
|
+
function exampleConversation() {
|
|
13
|
+
console.log('=== Context Usage Estimator Example ===\n');
|
|
14
|
+
// 1. Initial session context injection (SessionStart)
|
|
15
|
+
const sessionContext = `
|
|
16
|
+
You are Claude Code, an AI assistant. You have access to memory tools...
|
|
17
|
+
[large system prompt and instructions]
|
|
18
|
+
`.repeat(50);
|
|
19
|
+
const sessionTokens = estimateTokens(sessionContext);
|
|
20
|
+
usageEstimator.trackInjection(sessionTokens);
|
|
21
|
+
console.log(`Session context injected: ${sessionTokens} tokens`);
|
|
22
|
+
// 2. User message
|
|
23
|
+
const userMessage = 'Help me implement a new feature for user authentication';
|
|
24
|
+
const userTokens = estimateTokens(userMessage);
|
|
25
|
+
usageEstimator.trackUserMessage(userTokens);
|
|
26
|
+
console.log(`User message: ${userTokens} tokens`);
|
|
27
|
+
// 3. Context injection based on user query
|
|
28
|
+
const relevantContext = `
|
|
29
|
+
Memory facts about authentication:
|
|
30
|
+
- User authentication uses JWT tokens
|
|
31
|
+
- Tokens expire after 24 hours
|
|
32
|
+
- Refresh tokens are stored in httpOnly cookies
|
|
33
|
+
`.repeat(20);
|
|
34
|
+
const contextTokens = estimateTokens(relevantContext);
|
|
35
|
+
usageEstimator.trackInjection(contextTokens);
|
|
36
|
+
console.log(`Query-based context injected: ${contextTokens} tokens`);
|
|
37
|
+
// 4. Tool outputs (e.g., codebase search results)
|
|
38
|
+
const toolOutput = `
|
|
39
|
+
Found 5 authentication-related files:
|
|
40
|
+
- src/auth/jwt.ts
|
|
41
|
+
- src/auth/refresh.ts
|
|
42
|
+
- src/middleware/auth.ts
|
|
43
|
+
`.repeat(10);
|
|
44
|
+
const toolTokens = estimateTokens(toolOutput);
|
|
45
|
+
usageEstimator.trackToolOutput(toolTokens);
|
|
46
|
+
console.log(`Tool output: ${toolTokens} tokens`);
|
|
47
|
+
// 5. Assistant response
|
|
48
|
+
const assistantResponse = `
|
|
49
|
+
I'll help you implement user authentication. Based on the existing codebase,
|
|
50
|
+
here's my recommendation:
|
|
51
|
+
|
|
52
|
+
[detailed implementation plan]
|
|
53
|
+
`.repeat(100);
|
|
54
|
+
const assistantTokens = estimateTokens(assistantResponse);
|
|
55
|
+
usageEstimator.trackAssistantMessage(assistantTokens);
|
|
56
|
+
console.log(`Assistant response: ${assistantTokens} tokens`);
|
|
57
|
+
// 6. Check status
|
|
58
|
+
console.log('\n--- Current Status ---');
|
|
59
|
+
const usage = usageEstimator.getCurrentUsage();
|
|
60
|
+
console.log(`Total tokens: ${usage.totalEstimated.toLocaleString()}`);
|
|
61
|
+
console.log(`Context fill: ${getContextFill().toFixed(1)}%`);
|
|
62
|
+
console.log(`Approaching limit: ${isContextNearLimit() ? 'YES ⚠️' : 'NO ✅'}`);
|
|
63
|
+
// 7. Print full report
|
|
64
|
+
console.log('\n');
|
|
65
|
+
console.log(usageEstimator.getReport(MODEL_CONTEXT_LIMITS.SONNET));
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Example: Simulate approaching context limit
|
|
69
|
+
*/
|
|
70
|
+
function exampleApproachingLimit() {
|
|
71
|
+
console.log('\n=== Example: Approaching Limit ===\n');
|
|
72
|
+
// Reset estimator
|
|
73
|
+
usageEstimator.reset();
|
|
74
|
+
// Simulate a long conversation with lots of context
|
|
75
|
+
const limit = MODEL_CONTEXT_LIMITS.SONNET;
|
|
76
|
+
// Large initial injection
|
|
77
|
+
usageEstimator.trackInjection(100_000);
|
|
78
|
+
console.log(`After session start: ${getContextFill().toFixed(1)}% full`);
|
|
79
|
+
// Multiple exchanges
|
|
80
|
+
for (let i = 0; i < 10; i++) {
|
|
81
|
+
usageEstimator.trackExchange(2_000, 8_000);
|
|
82
|
+
}
|
|
83
|
+
console.log(`After 10 exchanges: ${getContextFill().toFixed(1)}% full`);
|
|
84
|
+
// Check if we should start pruning context
|
|
85
|
+
if (isContextNearLimit(80)) {
|
|
86
|
+
console.log('\n⚠️ Context is near limit! Time to:');
|
|
87
|
+
console.log(' - Prune old context');
|
|
88
|
+
console.log(' - Reduce injection size');
|
|
89
|
+
console.log(' - Summarise conversation history');
|
|
90
|
+
const remaining = usageEstimator.getRemainingBudget(limit);
|
|
91
|
+
console.log(`\nRemaining budget: ${remaining.toLocaleString()} tokens`);
|
|
92
|
+
}
|
|
93
|
+
// Show history trend
|
|
94
|
+
const history = usageEstimator.getHistory();
|
|
95
|
+
console.log(`\nHistory snapshots: ${history.length}`);
|
|
96
|
+
console.log('Fill percentage trend:');
|
|
97
|
+
history.slice(-5).forEach((snapshot, i) => {
|
|
98
|
+
console.log(` ${i + 1}. ${snapshot.fillPercentage.toFixed(1)}%`);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Example: Token estimation accuracy
|
|
103
|
+
*/
|
|
104
|
+
function exampleTokenEstimation() {
|
|
105
|
+
console.log('\n=== Example: Token Estimation ===\n');
|
|
106
|
+
const samples = [
|
|
107
|
+
'Hello',
|
|
108
|
+
'Hello, how are you today?',
|
|
109
|
+
'The quick brown fox jumps over the lazy dog',
|
|
110
|
+
`function calculateTotal(items) {
|
|
111
|
+
return items.reduce((sum, item) => sum + item.price, 0);
|
|
112
|
+
}`,
|
|
113
|
+
'a'.repeat(1000),
|
|
114
|
+
];
|
|
115
|
+
console.log('Character count → Estimated tokens:\n');
|
|
116
|
+
samples.forEach(text => {
|
|
117
|
+
const tokens = estimateTokens(text);
|
|
118
|
+
const preview = text.length > 50 ? text.slice(0, 47) + '...' : text;
|
|
119
|
+
console.log(` ${text.length} chars → ~${tokens} tokens`);
|
|
120
|
+
console.log(` "${preview}"\n`);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Run examples if executed directly
|
|
124
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
125
|
+
exampleConversation();
|
|
126
|
+
exampleApproachingLimit();
|
|
127
|
+
exampleTokenEstimation();
|
|
128
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { SessionState } from './importance-scorer.js';
|
|
2
|
+
import type { Exchange } from './session-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* Session state with exchanges (extends base SessionState)
|
|
5
|
+
*/
|
|
6
|
+
export interface SessionStateWithExchanges extends SessionState {
|
|
7
|
+
exchanges: Exchange[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Result of summarising a batch of exchanges
|
|
11
|
+
*/
|
|
12
|
+
export interface SummarisationResult {
|
|
13
|
+
summarisedCount: number;
|
|
14
|
+
summary: string;
|
|
15
|
+
tokensSaved: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* AI environment interface for summarisation (optional)
|
|
19
|
+
*/
|
|
20
|
+
export interface AIEnvironment {
|
|
21
|
+
AI?: {
|
|
22
|
+
run: (model: string, options: {
|
|
23
|
+
messages: Array<{
|
|
24
|
+
role: string;
|
|
25
|
+
content: string;
|
|
26
|
+
}>;
|
|
27
|
+
}) => Promise<unknown>;
|
|
28
|
+
};
|
|
29
|
+
EXTRACTION_MODEL?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class ExchangeSummariser {
|
|
32
|
+
private env?;
|
|
33
|
+
constructor(env?: AIEnvironment);
|
|
34
|
+
/**
|
|
35
|
+
* Check if session has exchanges that should be summarised
|
|
36
|
+
*
|
|
37
|
+
* @param session - Current session state
|
|
38
|
+
* @param threshold - Number of unsummarised turns to trigger (default: 10)
|
|
39
|
+
* @returns True if summarisation should be triggered
|
|
40
|
+
*/
|
|
41
|
+
shouldSummarise(session: SessionStateWithExchanges, threshold?: number): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Summarise old exchanges in the session
|
|
44
|
+
*
|
|
45
|
+
* @param session - Current session state (with exchanges)
|
|
46
|
+
* @param threshold - Number of unsummarised turns to leave active (default: 10)
|
|
47
|
+
* @returns Summarisation result with token savings
|
|
48
|
+
*/
|
|
49
|
+
summariseOldExchanges(session: SessionStateWithExchanges, threshold?: number): Promise<SummarisationResult>;
|
|
50
|
+
/**
|
|
51
|
+
* Get the current summary for a session (if exists)
|
|
52
|
+
*
|
|
53
|
+
* @param session - Session state
|
|
54
|
+
* @returns Summary text or null if no summarised exchanges
|
|
55
|
+
*/
|
|
56
|
+
getSummary(session: SessionStateWithExchanges): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Generate summary for a batch of exchanges
|
|
59
|
+
*
|
|
60
|
+
* Uses AI if available, falls back to extractive summarisation
|
|
61
|
+
*/
|
|
62
|
+
private generateSummary;
|
|
63
|
+
/**
|
|
64
|
+
* AI-powered summarisation using Workers AI
|
|
65
|
+
*/
|
|
66
|
+
private aiSummarise;
|
|
67
|
+
/**
|
|
68
|
+
* Extractive summarisation (local, no AI required)
|
|
69
|
+
*
|
|
70
|
+
* Strategy: Create a factual summary based on exchange metadata
|
|
71
|
+
*/
|
|
72
|
+
private extractiveSummarise;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an ExchangeSummariser instance
|
|
76
|
+
*
|
|
77
|
+
* @param env - Optional AI environment for AI-powered summarisation
|
|
78
|
+
* @returns ExchangeSummariser instance
|
|
79
|
+
*/
|
|
80
|
+
export declare function createExchangeSummariser(env?: AIEnvironment): ExchangeSummariser;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Exchange Summariser - Automatic Context Management
|
|
3
|
+
// =============================================================================
|
|
4
|
+
// Summarises old exchanges (>10 turns) to reduce token count while preserving
|
|
5
|
+
// decisions, outcomes, and key information.
|
|
6
|
+
//
|
|
7
|
+
// Strategy:
|
|
8
|
+
// - For local/offline: extractive summarisation (key sentences)
|
|
9
|
+
// - With AI available: Workers AI for better quality summaries
|
|
10
|
+
// - Stores summaries in session state
|
|
11
|
+
// - Marks exchanges as summarised to avoid re-processing
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// Configuration
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
const SUMMARISATION_CONFIG = {
|
|
17
|
+
DEFAULT_THRESHOLD: 10, // Default number of turns before summarisation
|
|
18
|
+
EXTRACTIVE_SENTENCES: 3, // Number of sentences to extract in local mode
|
|
19
|
+
MAX_SUMMARY_LENGTH: 500, // Maximum summary length (tokens ~125)
|
|
20
|
+
TURN_BATCH_SIZE: 10, // Summarise in batches of N turns
|
|
21
|
+
};
|
|
22
|
+
// -----------------------------------------------------------------------------
|
|
23
|
+
// Token Estimation
|
|
24
|
+
// -----------------------------------------------------------------------------
|
|
25
|
+
/**
|
|
26
|
+
* Estimate token count for text (rough approximation: ~4 chars per token)
|
|
27
|
+
*/
|
|
28
|
+
function estimateTokens(text) {
|
|
29
|
+
return Math.ceil(text.length / 4);
|
|
30
|
+
}
|
|
31
|
+
// -----------------------------------------------------------------------------
|
|
32
|
+
// Exchange Summariser
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
export class ExchangeSummariser {
|
|
35
|
+
env;
|
|
36
|
+
constructor(env) {
|
|
37
|
+
this.env = env;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Check if session has exchanges that should be summarised
|
|
41
|
+
*
|
|
42
|
+
* @param session - Current session state
|
|
43
|
+
* @param threshold - Number of unsummarised turns to trigger (default: 10)
|
|
44
|
+
* @returns True if summarisation should be triggered
|
|
45
|
+
*/
|
|
46
|
+
shouldSummarise(session, threshold = SUMMARISATION_CONFIG.DEFAULT_THRESHOLD) {
|
|
47
|
+
if (!session.exchanges || session.exchanges.length === 0) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
// Count unsummarised exchanges
|
|
51
|
+
const unsummarised = session.exchanges.filter(ex => !ex.summarised_at);
|
|
52
|
+
return unsummarised.length >= threshold;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Summarise old exchanges in the session
|
|
56
|
+
*
|
|
57
|
+
* @param session - Current session state (with exchanges)
|
|
58
|
+
* @param threshold - Number of unsummarised turns to leave active (default: 10)
|
|
59
|
+
* @returns Summarisation result with token savings
|
|
60
|
+
*/
|
|
61
|
+
async summariseOldExchanges(session, threshold = SUMMARISATION_CONFIG.DEFAULT_THRESHOLD) {
|
|
62
|
+
if (!session.exchanges || session.exchanges.length === 0) {
|
|
63
|
+
return {
|
|
64
|
+
summarisedCount: 0,
|
|
65
|
+
summary: '',
|
|
66
|
+
tokensSaved: 0,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Get unsummarised exchanges, oldest first
|
|
70
|
+
const unsummarised = session.exchanges
|
|
71
|
+
.filter((ex) => !ex.summarised_at)
|
|
72
|
+
.sort((a, b) => a.turn - b.turn);
|
|
73
|
+
// Only summarise if we have enough exchanges beyond threshold
|
|
74
|
+
if (unsummarised.length <= threshold) {
|
|
75
|
+
return {
|
|
76
|
+
summarisedCount: 0,
|
|
77
|
+
summary: '',
|
|
78
|
+
tokensSaved: 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// Identify exchanges to summarise (leave threshold most recent ones active)
|
|
82
|
+
const toSummarise = unsummarised.slice(0, -threshold);
|
|
83
|
+
if (toSummarise.length === 0) {
|
|
84
|
+
return {
|
|
85
|
+
summarisedCount: 0,
|
|
86
|
+
summary: '',
|
|
87
|
+
tokensSaved: 0,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Calculate token savings (approximate)
|
|
91
|
+
const originalTokens = toSummarise.reduce((sum, ex) => sum + ex.user_tokens + ex.assistant_tokens + ex.tool_tokens, 0);
|
|
92
|
+
// Generate summary
|
|
93
|
+
const summary = await this.generateSummary(toSummarise, session.session_id);
|
|
94
|
+
const summaryTokens = estimateTokens(summary);
|
|
95
|
+
const tokensSaved = originalTokens - summaryTokens;
|
|
96
|
+
// Mark exchanges as summarised
|
|
97
|
+
const now = new Date();
|
|
98
|
+
toSummarise.forEach((ex) => {
|
|
99
|
+
ex.summarised_at = now;
|
|
100
|
+
});
|
|
101
|
+
console.log(`📝 SUMMARISATION: Turns ${toSummarise[0].turn}-${toSummarise[toSummarise.length - 1].turn} ` +
|
|
102
|
+
`(${toSummarise.length} exchanges): ${originalTokens} → ${summaryTokens} tokens ` +
|
|
103
|
+
`(${((tokensSaved / originalTokens) * 100).toFixed(1)}% reduction)`);
|
|
104
|
+
return {
|
|
105
|
+
summarisedCount: toSummarise.length,
|
|
106
|
+
summary,
|
|
107
|
+
tokensSaved,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the current summary for a session (if exists)
|
|
112
|
+
*
|
|
113
|
+
* @param session - Session state
|
|
114
|
+
* @returns Summary text or null if no summarised exchanges
|
|
115
|
+
*/
|
|
116
|
+
getSummary(session) {
|
|
117
|
+
if (!session.exchanges || session.exchanges.length === 0) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const summarised = session.exchanges.filter((ex) => ex.summarised_at);
|
|
121
|
+
if (summarised.length === 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
// Return a compact summary header
|
|
125
|
+
const turnRange = `${Math.min(...summarised.map((e) => e.turn))}-${Math.max(...summarised.map((e) => e.turn))}`;
|
|
126
|
+
return `Turns ${turnRange} summary: ${summarised.length} exchanges summarised`;
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Summary Generation
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
/**
|
|
132
|
+
* Generate summary for a batch of exchanges
|
|
133
|
+
*
|
|
134
|
+
* Uses AI if available, falls back to extractive summarisation
|
|
135
|
+
*/
|
|
136
|
+
async generateSummary(exchanges, sessionId) {
|
|
137
|
+
// Try AI-powered summarisation first
|
|
138
|
+
if (this.env?.AI && this.env?.EXTRACTION_MODEL) {
|
|
139
|
+
try {
|
|
140
|
+
return await this.aiSummarise(exchanges);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('AI summarisation failed, falling back to extractive:', error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// Fallback: extractive summarisation
|
|
147
|
+
return this.extractiveSummarise(exchanges);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* AI-powered summarisation using Workers AI
|
|
151
|
+
*/
|
|
152
|
+
async aiSummarise(exchanges) {
|
|
153
|
+
if (!this.env?.AI || !this.env?.EXTRACTION_MODEL) {
|
|
154
|
+
throw new Error('AI not available');
|
|
155
|
+
}
|
|
156
|
+
// Build simple exchange representation
|
|
157
|
+
// Note: We don't have the actual message content in Exchange type,
|
|
158
|
+
// so we focus on metadata
|
|
159
|
+
const turnSummary = exchanges
|
|
160
|
+
.map(ex => {
|
|
161
|
+
const tokens = ex.user_tokens + ex.assistant_tokens + ex.tool_tokens;
|
|
162
|
+
return `Turn ${ex.turn}: ${tokens} tokens (user: ${ex.user_tokens}, assistant: ${ex.assistant_tokens}, tools: ${ex.tool_tokens})`;
|
|
163
|
+
})
|
|
164
|
+
.join('\n');
|
|
165
|
+
const prompt = `You are a conversation summariser. Summarise these conversation turns concisely.
|
|
166
|
+
|
|
167
|
+
TURNS:
|
|
168
|
+
"""
|
|
169
|
+
${turnSummary}
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
Create a 1-2 sentence summary focusing on:
|
|
173
|
+
- Number of turns and turn range
|
|
174
|
+
- Key activity indicators (high token usage = detailed discussion)
|
|
175
|
+
- Progress indicators (tool usage patterns)
|
|
176
|
+
|
|
177
|
+
RESPOND WITH TEXT ONLY. NO JSON. NO MARKDOWN. Just the summary.
|
|
178
|
+
|
|
179
|
+
Example: "Turns 1-10: Investigated authentication bug in src/auth.ts with extensive code review (high token usage). Used grep and read tools to analyse JWT implementation, identified expiry issue."`;
|
|
180
|
+
const response = await this.env.AI.run(this.env.EXTRACTION_MODEL, { messages: [{ role: 'user', content: prompt }] });
|
|
181
|
+
// Parse response
|
|
182
|
+
let summaryText;
|
|
183
|
+
if (typeof response === 'string') {
|
|
184
|
+
summaryText = response;
|
|
185
|
+
}
|
|
186
|
+
else if (response && typeof response === 'object') {
|
|
187
|
+
const responseData = response.response;
|
|
188
|
+
if (responseData) {
|
|
189
|
+
if (typeof responseData === 'string') {
|
|
190
|
+
summaryText = responseData;
|
|
191
|
+
}
|
|
192
|
+
else if (typeof responseData === 'object') {
|
|
193
|
+
// Try to extract a summary field if it's JSON
|
|
194
|
+
summaryText = responseData.summary || JSON.stringify(responseData);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
summaryText = 'Summary generation failed';
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
const possibleText = response.text
|
|
202
|
+
|| response.content
|
|
203
|
+
|| 'Summary generation failed';
|
|
204
|
+
summaryText = typeof possibleText === 'string' ? possibleText : 'Summary generation failed';
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
summaryText = 'Summary generation failed';
|
|
209
|
+
}
|
|
210
|
+
// Truncate if too long
|
|
211
|
+
if (summaryText.length > SUMMARISATION_CONFIG.MAX_SUMMARY_LENGTH) {
|
|
212
|
+
summaryText = summaryText.slice(0, SUMMARISATION_CONFIG.MAX_SUMMARY_LENGTH) + '...';
|
|
213
|
+
}
|
|
214
|
+
return summaryText.trim();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Extractive summarisation (local, no AI required)
|
|
218
|
+
*
|
|
219
|
+
* Strategy: Create a factual summary based on exchange metadata
|
|
220
|
+
*/
|
|
221
|
+
extractiveSummarise(exchanges) {
|
|
222
|
+
if (exchanges.length === 0) {
|
|
223
|
+
return '';
|
|
224
|
+
}
|
|
225
|
+
const turnRange = `${exchanges[0].turn}-${exchanges[exchanges.length - 1].turn}`;
|
|
226
|
+
const totalTokens = exchanges.reduce((sum, ex) => sum + ex.user_tokens + ex.assistant_tokens + ex.tool_tokens, 0);
|
|
227
|
+
const avgTokens = Math.round(totalTokens / exchanges.length);
|
|
228
|
+
// Classify activity level
|
|
229
|
+
let activity = 'discussion';
|
|
230
|
+
if (avgTokens > 2000) {
|
|
231
|
+
activity = 'extensive code analysis';
|
|
232
|
+
}
|
|
233
|
+
else if (avgTokens > 1000) {
|
|
234
|
+
activity = 'detailed investigation';
|
|
235
|
+
}
|
|
236
|
+
else if (avgTokens > 500) {
|
|
237
|
+
activity = 'moderate discussion';
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
activity = 'brief exchanges';
|
|
241
|
+
}
|
|
242
|
+
// Detect tool usage patterns
|
|
243
|
+
const toolHeavy = exchanges.filter(ex => ex.tool_tokens > ex.assistant_tokens).length;
|
|
244
|
+
const toolPattern = toolHeavy > exchanges.length / 2
|
|
245
|
+
? ' Heavy tool usage indicates codebase exploration or file operations.'
|
|
246
|
+
: '';
|
|
247
|
+
return `Turns ${turnRange} summary: ${exchanges.length} exchanges with ${activity} (avg ${avgTokens} tokens/turn).${toolPattern}`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// -----------------------------------------------------------------------------
|
|
251
|
+
// Factory Functions
|
|
252
|
+
// -----------------------------------------------------------------------------
|
|
253
|
+
/**
|
|
254
|
+
* Create an ExchangeSummariser instance
|
|
255
|
+
*
|
|
256
|
+
* @param env - Optional AI environment for AI-powered summarisation
|
|
257
|
+
* @returns ExchangeSummariser instance
|
|
258
|
+
*/
|
|
259
|
+
export function createExchangeSummariser(env) {
|
|
260
|
+
return new ExchangeSummariser(env);
|
|
261
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { SessionState } from './importance-scorer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Health status of context estimation
|
|
4
|
+
*/
|
|
5
|
+
export interface HealthStatus {
|
|
6
|
+
estimatedFill: number;
|
|
7
|
+
calibrationFactor: number;
|
|
8
|
+
driftWarning: boolean;
|
|
9
|
+
recommendation: string;
|
|
10
|
+
metrics: {
|
|
11
|
+
sessionsMonitored: number;
|
|
12
|
+
avgDrift: number;
|
|
13
|
+
lastCalibration: Date | null;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Single drift measurement
|
|
18
|
+
*/
|
|
19
|
+
export interface DriftRecord {
|
|
20
|
+
timestamp: Date;
|
|
21
|
+
estimated: number;
|
|
22
|
+
actual: number;
|
|
23
|
+
drift: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class HealthMonitor {
|
|
26
|
+
private driftHistory;
|
|
27
|
+
private calibrationFactor;
|
|
28
|
+
private lastCalibrationDate;
|
|
29
|
+
/**
|
|
30
|
+
* Check health status of session estimation
|
|
31
|
+
*
|
|
32
|
+
* @param session - Current session state
|
|
33
|
+
* @returns Health status with metrics and recommendations
|
|
34
|
+
*/
|
|
35
|
+
checkHealth(session: SessionState): HealthStatus;
|
|
36
|
+
/**
|
|
37
|
+
* Calibrate estimates using actual fill measurement
|
|
38
|
+
* Records drift and adjusts calibration factor if needed
|
|
39
|
+
*
|
|
40
|
+
* @param session - Current session state
|
|
41
|
+
* @param actualFill - Actual token usage (0.0 to 1.0)
|
|
42
|
+
*/
|
|
43
|
+
calibrate(session: SessionState, actualFill: number): void;
|
|
44
|
+
/**
|
|
45
|
+
* Get current calibration factor
|
|
46
|
+
* Multiply estimated tokens by this factor to get calibrated estimate
|
|
47
|
+
*
|
|
48
|
+
* @returns Current calibration factor
|
|
49
|
+
*/
|
|
50
|
+
getCalibrationFactor(): number;
|
|
51
|
+
/**
|
|
52
|
+
* Get drift history for analysis
|
|
53
|
+
*
|
|
54
|
+
* @returns Array of drift records (newest first)
|
|
55
|
+
*/
|
|
56
|
+
getDriftHistory(): DriftRecord[];
|
|
57
|
+
/**
|
|
58
|
+
* Clear drift history and reset calibration
|
|
59
|
+
* Useful when estimation algorithm changes
|
|
60
|
+
*/
|
|
61
|
+
reset(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Get estimated fill from session state
|
|
64
|
+
* This would normally come from the usage estimator
|
|
65
|
+
*/
|
|
66
|
+
private getEstimatedFill;
|
|
67
|
+
/**
|
|
68
|
+
* Record a drift measurement
|
|
69
|
+
*/
|
|
70
|
+
private recordDrift;
|
|
71
|
+
/**
|
|
72
|
+
* Calculate average drift from recent history
|
|
73
|
+
* Uses exponential weighting (recent samples weighted more)
|
|
74
|
+
*/
|
|
75
|
+
private calculateAverageDrift;
|
|
76
|
+
/**
|
|
77
|
+
* Update calibration factor based on drift history
|
|
78
|
+
* Uses exponential moving average to smooth adjustments
|
|
79
|
+
*/
|
|
80
|
+
private updateCalibrationFactor;
|
|
81
|
+
/**
|
|
82
|
+
* Generate health recommendation based on drift
|
|
83
|
+
*/
|
|
84
|
+
private generateRecommendation;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a new health monitor instance
|
|
88
|
+
*/
|
|
89
|
+
export declare function createHealthMonitor(): HealthMonitor;
|
|
90
|
+
/**
|
|
91
|
+
* Get or create the singleton health monitor instance
|
|
92
|
+
*/
|
|
93
|
+
export declare function getHealthMonitor(): HealthMonitor;
|
|
94
|
+
/**
|
|
95
|
+
* Reset the singleton instance (for testing)
|
|
96
|
+
*/
|
|
97
|
+
export declare function resetHealthMonitor(): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|