wave-agent-sdk 0.0.7 → 0.0.8
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/agent.d.ts +32 -20
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +202 -20
- package/dist/constants/events.d.ts +28 -0
- package/dist/constants/events.d.ts.map +1 -0
- package/dist/constants/events.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/managers/aiManager.d.ts +34 -1
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +243 -128
- package/dist/managers/backgroundBashManager.d.ts.map +1 -1
- package/dist/managers/backgroundBashManager.js +7 -6
- package/dist/managers/hookManager.d.ts +9 -4
- package/dist/managers/hookManager.d.ts.map +1 -1
- package/dist/managers/hookManager.js +62 -30
- package/dist/managers/liveConfigManager.d.ts +58 -0
- package/dist/managers/liveConfigManager.d.ts.map +1 -0
- package/dist/managers/liveConfigManager.js +160 -0
- package/dist/managers/messageManager.d.ts +38 -13
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +163 -30
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +4 -1
- package/dist/managers/subagentManager.d.ts +51 -0
- package/dist/managers/subagentManager.d.ts.map +1 -1
- package/dist/managers/subagentManager.js +189 -18
- package/dist/services/aiService.d.ts +13 -5
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +350 -74
- package/dist/services/configurationWatcher.d.ts +120 -0
- package/dist/services/configurationWatcher.d.ts.map +1 -0
- package/dist/services/configurationWatcher.js +439 -0
- package/dist/services/fileWatcher.d.ts +69 -0
- package/dist/services/fileWatcher.d.ts.map +1 -0
- package/dist/services/fileWatcher.js +213 -0
- package/dist/services/hook.d.ts +91 -9
- package/dist/services/hook.d.ts.map +1 -1
- package/dist/services/hook.js +393 -43
- package/dist/services/jsonlHandler.d.ts +62 -0
- package/dist/services/jsonlHandler.d.ts.map +1 -0
- package/dist/services/jsonlHandler.js +257 -0
- package/dist/services/memory.d.ts +9 -0
- package/dist/services/memory.d.ts.map +1 -1
- package/dist/services/memory.js +81 -12
- package/dist/services/memoryStore.d.ts +81 -0
- package/dist/services/memoryStore.d.ts.map +1 -0
- package/dist/services/memoryStore.js +200 -0
- package/dist/services/session.d.ts +64 -49
- package/dist/services/session.d.ts.map +1 -1
- package/dist/services/session.js +310 -132
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +5 -4
- package/dist/tools/deleteFileTool.d.ts.map +1 -1
- package/dist/tools/deleteFileTool.js +2 -1
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +3 -2
- package/dist/tools/multiEditTool.d.ts.map +1 -1
- package/dist/tools/multiEditTool.js +4 -3
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +2 -1
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +5 -6
- package/dist/types/commands.d.ts +4 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/core.d.ts +35 -0
- package/dist/types/core.d.ts.map +1 -1
- package/dist/types/environment.d.ts +42 -0
- package/dist/types/environment.d.ts.map +1 -0
- package/dist/types/environment.js +21 -0
- package/dist/types/hooks.d.ts +8 -2
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/hooks.js +8 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/memoryStore.d.ts +82 -0
- package/dist/types/memoryStore.d.ts.map +1 -0
- package/dist/types/memoryStore.js +7 -0
- package/dist/types/messaging.d.ts +14 -2
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/types/session.d.ts +20 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/utils/bashHistory.d.ts.map +1 -1
- package/dist/utils/bashHistory.js +27 -26
- package/dist/utils/cacheControlUtils.d.ts +121 -0
- package/dist/utils/cacheControlUtils.d.ts.map +1 -0
- package/dist/utils/cacheControlUtils.js +367 -0
- package/dist/utils/commandPathResolver.d.ts +52 -0
- package/dist/utils/commandPathResolver.d.ts.map +1 -0
- package/dist/utils/commandPathResolver.js +145 -0
- package/dist/utils/configPaths.d.ts +85 -0
- package/dist/utils/configPaths.d.ts.map +1 -0
- package/dist/utils/configPaths.js +121 -0
- package/dist/utils/configResolver.d.ts +37 -10
- package/dist/utils/configResolver.d.ts.map +1 -1
- package/dist/utils/configResolver.js +127 -23
- package/dist/utils/constants.d.ts +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -5
- package/dist/utils/customCommands.d.ts.map +1 -1
- package/dist/utils/customCommands.js +66 -21
- package/dist/utils/fileUtils.d.ts +15 -0
- package/dist/utils/fileUtils.d.ts.map +1 -0
- package/dist/utils/fileUtils.js +61 -0
- package/dist/utils/globalLogger.d.ts +102 -0
- package/dist/utils/globalLogger.d.ts.map +1 -0
- package/dist/utils/globalLogger.js +136 -0
- package/dist/utils/mcpUtils.d.ts.map +1 -1
- package/dist/utils/mcpUtils.js +25 -3
- package/dist/utils/messageOperations.d.ts +20 -8
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +25 -16
- package/dist/utils/pathEncoder.d.ts +104 -0
- package/dist/utils/pathEncoder.d.ts.map +1 -0
- package/dist/utils/pathEncoder.js +272 -0
- package/dist/utils/subagentParser.d.ts.map +1 -1
- package/dist/utils/subagentParser.js +2 -1
- package/dist/utils/tokenCalculation.d.ts +26 -0
- package/dist/utils/tokenCalculation.d.ts.map +1 -0
- package/dist/utils/tokenCalculation.js +36 -0
- package/package.json +6 -3
- package/src/agent.ts +298 -34
- package/src/constants/events.ts +38 -0
- package/src/index.ts +2 -0
- package/src/managers/aiManager.ts +323 -170
- package/src/managers/backgroundBashManager.ts +7 -6
- package/src/managers/hookManager.ts +83 -40
- package/src/managers/liveConfigManager.ts +248 -0
- package/src/managers/messageManager.ts +230 -63
- package/src/managers/slashCommandManager.ts +4 -1
- package/src/managers/subagentManager.ts +283 -21
- package/src/services/aiService.ts +474 -83
- package/src/services/configurationWatcher.ts +622 -0
- package/src/services/fileWatcher.ts +301 -0
- package/src/services/hook.ts +538 -47
- package/src/services/jsonlHandler.ts +319 -0
- package/src/services/memory.ts +92 -12
- package/src/services/memoryStore.ts +279 -0
- package/src/services/session.ts +381 -157
- package/src/tools/bashTool.ts +5 -4
- package/src/tools/deleteFileTool.ts +2 -1
- package/src/tools/editTool.ts +3 -2
- package/src/tools/multiEditTool.ts +4 -3
- package/src/tools/readTool.ts +2 -1
- package/src/tools/writeTool.ts +7 -6
- package/src/types/commands.ts +6 -0
- package/src/types/core.ts +44 -0
- package/src/types/environment.ts +60 -0
- package/src/types/hooks.ts +21 -8
- package/src/types/index.ts +2 -0
- package/src/types/memoryStore.ts +94 -0
- package/src/types/messaging.ts +14 -2
- package/src/types/session.ts +25 -0
- package/src/utils/bashHistory.ts +27 -27
- package/src/utils/cacheControlUtils.ts +540 -0
- package/src/utils/commandPathResolver.ts +189 -0
- package/src/utils/configPaths.ts +163 -0
- package/src/utils/configResolver.ts +182 -22
- package/src/utils/constants.ts +1 -1
- package/src/utils/convertMessagesForAPI.ts +7 -5
- package/src/utils/customCommands.ts +90 -22
- package/src/utils/fileUtils.ts +65 -0
- package/src/utils/globalLogger.ts +145 -0
- package/src/utils/mcpUtils.ts +34 -3
- package/src/utils/messageOperations.ts +42 -20
- package/src/utils/pathEncoder.ts +379 -0
- package/src/utils/subagentParser.ts +2 -1
- package/src/utils/tokenCalculation.ts +43 -0
package/src/utils/bashHistory.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import fs from "fs";
|
|
7
7
|
import { BASH_HISTORY_FILE, DATA_DIRECTORY } from "./constants.js";
|
|
8
|
+
import { logger } from "./globalLogger.js";
|
|
8
9
|
|
|
9
10
|
export interface BashHistoryEntry {
|
|
10
11
|
command: string;
|
|
@@ -28,8 +29,8 @@ const ensureDataDirectory = (): void => {
|
|
|
28
29
|
if (!fs.existsSync(DATA_DIRECTORY)) {
|
|
29
30
|
fs.mkdirSync(DATA_DIRECTORY, { recursive: true });
|
|
30
31
|
}
|
|
31
|
-
} catch {
|
|
32
|
-
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.debug("Failed to create data directory:", error);
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
36
|
|
|
@@ -52,7 +53,7 @@ export const loadBashHistory = (): BashHistory => {
|
|
|
52
53
|
|
|
53
54
|
// Version compatibility check
|
|
54
55
|
if (history.version !== HISTORY_VERSION) {
|
|
55
|
-
|
|
56
|
+
logger.debug("Bash history version mismatch, resetting history");
|
|
56
57
|
return {
|
|
57
58
|
commands: [],
|
|
58
59
|
version: HISTORY_VERSION,
|
|
@@ -60,8 +61,8 @@ export const loadBashHistory = (): BashHistory => {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
return history;
|
|
63
|
-
} catch {
|
|
64
|
-
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.debug("Failed to load bash history:", error);
|
|
65
66
|
return {
|
|
66
67
|
commands: [],
|
|
67
68
|
version: HISTORY_VERSION,
|
|
@@ -76,7 +77,7 @@ export const saveBashHistory = (history: BashHistory): void => {
|
|
|
76
77
|
try {
|
|
77
78
|
// Skip saving to file when in test environment
|
|
78
79
|
if (process.env.NODE_ENV === "test") {
|
|
79
|
-
|
|
80
|
+
logger.debug("Skipping bash history save in test environment");
|
|
80
81
|
return;
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -89,8 +90,8 @@ export const saveBashHistory = (history: BashHistory): void => {
|
|
|
89
90
|
|
|
90
91
|
const data = JSON.stringify(history, null, 2);
|
|
91
92
|
fs.writeFileSync(BASH_HISTORY_FILE, data, "utf-8");
|
|
92
|
-
} catch {
|
|
93
|
-
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.debug("Failed to save bash history:", error);
|
|
94
95
|
}
|
|
95
96
|
};
|
|
96
97
|
|
|
@@ -102,12 +103,6 @@ export const addBashCommandToHistory = (
|
|
|
102
103
|
workdir: string,
|
|
103
104
|
): void => {
|
|
104
105
|
try {
|
|
105
|
-
// Filter system-generated commands, do not add to history
|
|
106
|
-
if (command.startsWith("git add . && git commit -m")) {
|
|
107
|
-
// logger.debug("Skipping system-generated command:", { command, workdir });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
106
|
const history = loadBashHistory();
|
|
112
107
|
const timestamp = Date.now();
|
|
113
108
|
|
|
@@ -130,9 +125,9 @@ export const addBashCommandToHistory = (
|
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
saveBashHistory(history);
|
|
133
|
-
|
|
134
|
-
} catch {
|
|
135
|
-
|
|
128
|
+
logger.debug("Added bash command to history:", { command, workdir });
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.debug("Failed to add bash command to history:", error);
|
|
136
131
|
}
|
|
137
132
|
};
|
|
138
133
|
|
|
@@ -201,11 +196,16 @@ export const searchBashHistory = (
|
|
|
201
196
|
const dedupedMatches = deduplicateCommands(matches);
|
|
202
197
|
const result = dedupedMatches.slice(0, limit);
|
|
203
198
|
|
|
204
|
-
|
|
199
|
+
logger.debug("Bash history search results:", {
|
|
200
|
+
query,
|
|
201
|
+
workdir: process.cwd(),
|
|
202
|
+
originalCount: matches.length,
|
|
203
|
+
dedupedCount: result.length,
|
|
204
|
+
});
|
|
205
205
|
|
|
206
206
|
return result;
|
|
207
|
-
} catch {
|
|
208
|
-
|
|
207
|
+
} catch (error) {
|
|
208
|
+
logger.debug("Failed to search bash history:", error);
|
|
209
209
|
return [];
|
|
210
210
|
}
|
|
211
211
|
};
|
|
@@ -248,8 +248,8 @@ export const getRecentBashCommands = (
|
|
|
248
248
|
// Return recent commands after deduplication
|
|
249
249
|
const deduped = deduplicateCommands(filtered);
|
|
250
250
|
return deduped.slice(-limit).reverse(); // Latest first
|
|
251
|
-
} catch {
|
|
252
|
-
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.debug("Failed to get recent bash commands:", error);
|
|
253
253
|
return [];
|
|
254
254
|
}
|
|
255
255
|
};
|
|
@@ -264,9 +264,9 @@ export const clearBashHistory = (): void => {
|
|
|
264
264
|
version: HISTORY_VERSION,
|
|
265
265
|
};
|
|
266
266
|
saveBashHistory(history);
|
|
267
|
-
|
|
268
|
-
} catch {
|
|
269
|
-
|
|
267
|
+
logger.debug("Bash history cleared");
|
|
268
|
+
} catch (error) {
|
|
269
|
+
logger.debug("Failed to clear bash history:", error);
|
|
270
270
|
}
|
|
271
271
|
};
|
|
272
272
|
|
|
@@ -292,8 +292,8 @@ export const getBashCommandStats = (): {
|
|
|
292
292
|
uniqueCommands: uniqueCommands.size,
|
|
293
293
|
workdirs,
|
|
294
294
|
};
|
|
295
|
-
} catch {
|
|
296
|
-
|
|
295
|
+
} catch (error) {
|
|
296
|
+
logger.debug("Failed to get bash command stats:", error);
|
|
297
297
|
return {
|
|
298
298
|
totalCommands: 0,
|
|
299
299
|
uniqueCommands: 0,
|
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Control Utilities for Claude Models
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for adding cache_control markers to Claude models
|
|
5
|
+
* to optimize token usage and reduce costs. Cache control is only applied to Claude
|
|
6
|
+
* models and preserves backward compatibility with existing message formats.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
ChatCompletionMessageParam,
|
|
11
|
+
ChatCompletionContentPart,
|
|
12
|
+
ChatCompletionContentPartText,
|
|
13
|
+
ChatCompletionFunctionTool,
|
|
14
|
+
CompletionUsage,
|
|
15
|
+
} from "openai/resources";
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Core Types
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cache control directive for Claude models
|
|
23
|
+
*/
|
|
24
|
+
export interface CacheControl {
|
|
25
|
+
type: "ephemeral";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extended text content part with cache control support
|
|
30
|
+
*/
|
|
31
|
+
export interface ClaudeChatCompletionContentPartText
|
|
32
|
+
extends ChatCompletionContentPartText {
|
|
33
|
+
type: "text";
|
|
34
|
+
text: string;
|
|
35
|
+
cache_control?: CacheControl;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extended tool definition with cache control support
|
|
40
|
+
*/
|
|
41
|
+
export interface ClaudeChatCompletionFunctionTool
|
|
42
|
+
extends ChatCompletionFunctionTool {
|
|
43
|
+
type: "function";
|
|
44
|
+
function: ChatCompletionFunctionTool["function"];
|
|
45
|
+
cache_control?: CacheControl;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Enhanced usage metrics including Claude cache information
|
|
50
|
+
*/
|
|
51
|
+
export interface ClaudeUsage extends CompletionUsage {
|
|
52
|
+
prompt_tokens: number;
|
|
53
|
+
completion_tokens: number;
|
|
54
|
+
total_tokens: number;
|
|
55
|
+
|
|
56
|
+
// Claude cache extensions
|
|
57
|
+
cache_read_input_tokens?: number;
|
|
58
|
+
cache_creation_input_tokens?: number;
|
|
59
|
+
cache_creation?: {
|
|
60
|
+
ephemeral_5m_input_tokens: number;
|
|
61
|
+
ephemeral_1h_input_tokens: number;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Configuration for cache control application
|
|
67
|
+
*/
|
|
68
|
+
export interface CacheControlConfig {
|
|
69
|
+
cacheSystemMessage: boolean;
|
|
70
|
+
cacheUserMessageCount: number;
|
|
71
|
+
cacheLastTool: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Default Configuration
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Default cache control configuration
|
|
80
|
+
*/
|
|
81
|
+
export const DEFAULT_CACHE_CONTROL_CONFIG: CacheControlConfig = {
|
|
82
|
+
cacheSystemMessage: true,
|
|
83
|
+
cacheUserMessageCount: 2,
|
|
84
|
+
cacheLastTool: true,
|
|
85
|
+
} as const;
|
|
86
|
+
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Utility Functions (Basic Structure - to be implemented)
|
|
89
|
+
// ============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Determines if a model supports cache control
|
|
93
|
+
* @param modelName - Model identifier
|
|
94
|
+
* @returns True if model name contains 'claude' (case-insensitive)
|
|
95
|
+
*/
|
|
96
|
+
export function isClaudeModel(modelName: string): boolean {
|
|
97
|
+
// Handle null, undefined, and non-string inputs
|
|
98
|
+
if (!modelName || typeof modelName !== "string") {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Handle empty strings and whitespace-only strings
|
|
103
|
+
const trimmed = modelName.trim();
|
|
104
|
+
if (trimmed.length === 0) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return trimmed.toLowerCase().includes("claude");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Validates cache control structure
|
|
113
|
+
* @param control - Object to validate
|
|
114
|
+
* @returns True if valid cache control object
|
|
115
|
+
*/
|
|
116
|
+
export function isValidCacheControl(control: unknown): control is CacheControl {
|
|
117
|
+
return (
|
|
118
|
+
control !== null &&
|
|
119
|
+
typeof control === "object" &&
|
|
120
|
+
control !== undefined &&
|
|
121
|
+
"type" in control &&
|
|
122
|
+
(control as { type: unknown }).type === "ephemeral"
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Adds cache control markers to message content
|
|
128
|
+
* @param content - Original content (string or structured)
|
|
129
|
+
* @param shouldCache - Whether to add cache control
|
|
130
|
+
* @returns Structured content with cache control markers
|
|
131
|
+
*/
|
|
132
|
+
export function addCacheControlToContent(
|
|
133
|
+
content: string | ChatCompletionContentPart[],
|
|
134
|
+
shouldCache: boolean,
|
|
135
|
+
): ClaudeChatCompletionContentPartText[] {
|
|
136
|
+
// Handle null/undefined content
|
|
137
|
+
if (content == null) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If shouldCache is false, return content as text parts without cache control
|
|
142
|
+
if (!shouldCache) {
|
|
143
|
+
if (typeof content === "string") {
|
|
144
|
+
return [{ type: "text", text: content }];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Validate array input
|
|
148
|
+
if (!Array.isArray(content)) {
|
|
149
|
+
console.warn(
|
|
150
|
+
"Invalid content type for cache control transformation:",
|
|
151
|
+
typeof content,
|
|
152
|
+
);
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Filter and convert only text parts with validation
|
|
157
|
+
return content
|
|
158
|
+
.filter((part): part is ChatCompletionContentPartText => {
|
|
159
|
+
if (!part || typeof part !== "object") {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
return part.type === "text" && typeof part.text === "string";
|
|
163
|
+
})
|
|
164
|
+
.map((part) => ({ type: "text", text: part.text }));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// shouldCache is true - add cache control markers
|
|
168
|
+
if (typeof content === "string") {
|
|
169
|
+
// Transform string content to structured array with cache control
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
type: "text",
|
|
173
|
+
text: content,
|
|
174
|
+
cache_control: { type: "ephemeral" },
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Validate array input
|
|
180
|
+
if (!Array.isArray(content)) {
|
|
181
|
+
console.warn(
|
|
182
|
+
"Invalid content type for cache control transformation:",
|
|
183
|
+
typeof content,
|
|
184
|
+
);
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle structured content - preserve existing structure, add cache control to text parts
|
|
189
|
+
return content
|
|
190
|
+
.filter((part): part is ChatCompletionContentPartText => {
|
|
191
|
+
if (!part || typeof part !== "object") {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return part.type === "text" && typeof part.text === "string";
|
|
195
|
+
})
|
|
196
|
+
.map((part) => ({
|
|
197
|
+
type: "text",
|
|
198
|
+
text: part.text,
|
|
199
|
+
cache_control: { type: "ephemeral" },
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Adds cache control to the last tool in tools array
|
|
205
|
+
* @param tools - Array of tool definitions
|
|
206
|
+
* @returns Tools array with cache control on last tool
|
|
207
|
+
*/
|
|
208
|
+
export function addCacheControlToLastTool(
|
|
209
|
+
tools: ChatCompletionFunctionTool[],
|
|
210
|
+
): ClaudeChatCompletionFunctionTool[] {
|
|
211
|
+
// Handle null, undefined, or empty arrays
|
|
212
|
+
if (!tools || !Array.isArray(tools) || tools.length === 0) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Validate tools structure
|
|
217
|
+
const validTools = tools.filter((tool) => {
|
|
218
|
+
if (!tool || typeof tool !== "object") {
|
|
219
|
+
console.warn("Invalid tool structure detected, skipping:", tool);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (tool.type !== "function" || !tool.function) {
|
|
223
|
+
console.warn(
|
|
224
|
+
"Tool is not a function type or missing function property:",
|
|
225
|
+
tool,
|
|
226
|
+
);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (validTools.length === 0) {
|
|
233
|
+
console.warn("No valid tools found for cache control");
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Create a copy of the valid tools array
|
|
238
|
+
const result = validTools.map((tool) => ({
|
|
239
|
+
...tool,
|
|
240
|
+
})) as ClaudeChatCompletionFunctionTool[];
|
|
241
|
+
|
|
242
|
+
// Add cache control to the last tool only
|
|
243
|
+
const lastIndex = result.length - 1;
|
|
244
|
+
result[lastIndex] = {
|
|
245
|
+
...result[lastIndex],
|
|
246
|
+
cache_control: { type: "ephemeral" },
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Transforms messages for Claude cache control
|
|
254
|
+
* @param messages - Original OpenAI message array
|
|
255
|
+
* @param modelName - Model name for cache detection
|
|
256
|
+
* @param config - Cache control configuration
|
|
257
|
+
* @returns Messages with cache control markers applied
|
|
258
|
+
*/
|
|
259
|
+
export function transformMessagesForClaudeCache(
|
|
260
|
+
messages: ChatCompletionMessageParam[],
|
|
261
|
+
modelName: string,
|
|
262
|
+
config: CacheControlConfig = DEFAULT_CACHE_CONTROL_CONFIG,
|
|
263
|
+
): ChatCompletionMessageParam[] {
|
|
264
|
+
// Validate inputs
|
|
265
|
+
if (!messages || !Array.isArray(messages)) {
|
|
266
|
+
console.warn(
|
|
267
|
+
"Invalid messages array provided to transformMessagesForClaudeCache",
|
|
268
|
+
);
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (messages.length === 0) {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Only apply cache control for Claude models
|
|
277
|
+
if (!isClaudeModel(modelName)) {
|
|
278
|
+
return messages;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Validate config
|
|
282
|
+
if (!config || typeof config !== "object") {
|
|
283
|
+
console.warn("Invalid cache control config, using defaults");
|
|
284
|
+
config = DEFAULT_CACHE_CONTROL_CONFIG;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const result = messages.map((message, index) => {
|
|
288
|
+
// Validate message structure
|
|
289
|
+
if (!message || typeof message !== "object" || !message.role) {
|
|
290
|
+
console.warn("Invalid message structure at index", index, ":", message);
|
|
291
|
+
return message; // Return as-is to avoid breaking the flow
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// System message: cache if enabled in config
|
|
295
|
+
if (message.role === "system" && config.cacheSystemMessage) {
|
|
296
|
+
return {
|
|
297
|
+
...message,
|
|
298
|
+
content: addCacheControlToContent(message.content, true),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// User messages: cache last N messages based on config
|
|
303
|
+
if (message.role === "user" && config.cacheUserMessageCount > 0) {
|
|
304
|
+
const userMessageIndices: number[] = [];
|
|
305
|
+
messages.forEach((msg, idx) => {
|
|
306
|
+
if (msg.role === "user") {
|
|
307
|
+
userMessageIndices.push(idx);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Check if this user message is among the last N
|
|
312
|
+
const isRecentUser = userMessageIndices
|
|
313
|
+
.slice(-config.cacheUserMessageCount)
|
|
314
|
+
.includes(index);
|
|
315
|
+
|
|
316
|
+
if (isRecentUser) {
|
|
317
|
+
return {
|
|
318
|
+
...message,
|
|
319
|
+
content: addCacheControlToContent(message.content, true),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Return message unchanged
|
|
325
|
+
return message;
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Extends standard usage with cache metrics
|
|
333
|
+
* @param standardUsage - OpenAI usage response
|
|
334
|
+
* @param cacheMetrics - Additional cache metrics from Claude
|
|
335
|
+
* @returns Extended usage with cache information
|
|
336
|
+
*/
|
|
337
|
+
export function extendUsageWithCacheMetrics(
|
|
338
|
+
standardUsage: CompletionUsage,
|
|
339
|
+
cacheMetrics?: Partial<ClaudeUsage>,
|
|
340
|
+
): ClaudeUsage {
|
|
341
|
+
const baseUsage: ClaudeUsage = {
|
|
342
|
+
prompt_tokens: standardUsage.prompt_tokens,
|
|
343
|
+
completion_tokens: standardUsage.completion_tokens,
|
|
344
|
+
total_tokens: standardUsage.total_tokens,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// Add cache metrics if provided
|
|
348
|
+
if (cacheMetrics) {
|
|
349
|
+
if (typeof cacheMetrics.cache_read_input_tokens === "number") {
|
|
350
|
+
baseUsage.cache_read_input_tokens = cacheMetrics.cache_read_input_tokens;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (typeof cacheMetrics.cache_creation_input_tokens === "number") {
|
|
354
|
+
baseUsage.cache_creation_input_tokens =
|
|
355
|
+
cacheMetrics.cache_creation_input_tokens;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (
|
|
359
|
+
cacheMetrics.cache_creation &&
|
|
360
|
+
typeof cacheMetrics.cache_creation.ephemeral_5m_input_tokens ===
|
|
361
|
+
"number" &&
|
|
362
|
+
typeof cacheMetrics.cache_creation.ephemeral_1h_input_tokens === "number"
|
|
363
|
+
) {
|
|
364
|
+
baseUsage.cache_creation = {
|
|
365
|
+
ephemeral_5m_input_tokens:
|
|
366
|
+
cacheMetrics.cache_creation.ephemeral_5m_input_tokens,
|
|
367
|
+
ephemeral_1h_input_tokens:
|
|
368
|
+
cacheMetrics.cache_creation.ephemeral_1h_input_tokens,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return baseUsage;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Validates Claude usage structure
|
|
378
|
+
* @param usage - Usage object to validate
|
|
379
|
+
* @returns True if usage structure is valid
|
|
380
|
+
*/
|
|
381
|
+
export function isValidClaudeUsage(usage: unknown): usage is ClaudeUsage {
|
|
382
|
+
if (!usage || typeof usage !== "object") {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const usageObj = usage as Record<string, unknown>;
|
|
387
|
+
|
|
388
|
+
// Check required standard fields
|
|
389
|
+
const hasStandardFields =
|
|
390
|
+
typeof usageObj.prompt_tokens === "number" &&
|
|
391
|
+
typeof usageObj.completion_tokens === "number" &&
|
|
392
|
+
typeof usageObj.total_tokens === "number";
|
|
393
|
+
|
|
394
|
+
if (!hasStandardFields) {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check optional cache fields
|
|
399
|
+
const hasCacheFields =
|
|
400
|
+
(usageObj.cache_read_input_tokens === undefined ||
|
|
401
|
+
typeof usageObj.cache_read_input_tokens === "number") &&
|
|
402
|
+
(usageObj.cache_creation_input_tokens === undefined ||
|
|
403
|
+
typeof usageObj.cache_creation_input_tokens === "number");
|
|
404
|
+
|
|
405
|
+
if (!hasCacheFields) {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Check cache_creation object if present
|
|
410
|
+
if (usageObj.cache_creation !== undefined) {
|
|
411
|
+
const cacheCreation = usageObj.cache_creation as Record<string, unknown>;
|
|
412
|
+
if (
|
|
413
|
+
typeof cacheCreation !== "object" ||
|
|
414
|
+
typeof cacheCreation.ephemeral_5m_input_tokens !== "number" ||
|
|
415
|
+
typeof cacheCreation.ephemeral_1h_input_tokens !== "number"
|
|
416
|
+
) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Adds cache control to the last N user messages in a conversation
|
|
426
|
+
* This optimizes multi-turn conversations by caching recent user context
|
|
427
|
+
*
|
|
428
|
+
* @param messages - Array of chat completion messages
|
|
429
|
+
* @param maxUserMessagesToCache - Maximum number of recent user messages to cache (default: 2)
|
|
430
|
+
* @returns Modified messages array with cache control on recent user messages
|
|
431
|
+
*/
|
|
432
|
+
export function addCacheControlToRecentUserMessages(
|
|
433
|
+
messages: ChatCompletionMessageParam[],
|
|
434
|
+
maxUserMessagesToCache: number = 2,
|
|
435
|
+
): ChatCompletionMessageParam[] {
|
|
436
|
+
// Validate inputs
|
|
437
|
+
if (!messages || !Array.isArray(messages)) {
|
|
438
|
+
console.warn(
|
|
439
|
+
"Invalid messages array provided to addCacheControlToRecentUserMessages",
|
|
440
|
+
);
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (messages.length === 0 || maxUserMessagesToCache <= 0) {
|
|
445
|
+
return messages;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Validate maxUserMessagesToCache is a reasonable number
|
|
449
|
+
if (maxUserMessagesToCache > 100) {
|
|
450
|
+
console.warn(
|
|
451
|
+
"maxUserMessagesToCache is unusually high:",
|
|
452
|
+
maxUserMessagesToCache,
|
|
453
|
+
"limiting to 100",
|
|
454
|
+
);
|
|
455
|
+
maxUserMessagesToCache = 100;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Find all user message indices in reverse order (most recent first)
|
|
459
|
+
const userMessageIndices: number[] = [];
|
|
460
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
461
|
+
const message = messages[i];
|
|
462
|
+
|
|
463
|
+
// Validate message structure
|
|
464
|
+
if (!message || typeof message !== "object" || !message.role) {
|
|
465
|
+
console.warn("Invalid message at index", i, ", skipping");
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (message.role === "user") {
|
|
470
|
+
userMessageIndices.push(i);
|
|
471
|
+
if (userMessageIndices.length >= maxUserMessagesToCache) {
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// If no user messages found, return unchanged
|
|
478
|
+
if (userMessageIndices.length === 0) {
|
|
479
|
+
return messages;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Create a copy of messages and modify the identified user messages
|
|
483
|
+
const modifiedMessages = [...messages];
|
|
484
|
+
|
|
485
|
+
for (const index of userMessageIndices) {
|
|
486
|
+
const message = modifiedMessages[index];
|
|
487
|
+
if (message.role === "user" && message.content != null) {
|
|
488
|
+
try {
|
|
489
|
+
modifiedMessages[index] = {
|
|
490
|
+
...message,
|
|
491
|
+
content: addCacheControlToContent(message.content, true),
|
|
492
|
+
};
|
|
493
|
+
} catch (error) {
|
|
494
|
+
console.warn(
|
|
495
|
+
"Failed to add cache control to user message at index",
|
|
496
|
+
index,
|
|
497
|
+
":",
|
|
498
|
+
error,
|
|
499
|
+
);
|
|
500
|
+
// Continue with original message if transformation fails
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return modifiedMessages;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Helper function to identify user message indices that should be cached
|
|
510
|
+
* Used for testing and validation purposes
|
|
511
|
+
*
|
|
512
|
+
* @param messages - Array of chat completion messages
|
|
513
|
+
* @param maxUserMessagesToCache - Maximum number of recent user messages to identify
|
|
514
|
+
* @returns Array of indices for user messages that should be cached
|
|
515
|
+
*/
|
|
516
|
+
export function findRecentUserMessageIndices(
|
|
517
|
+
messages: ChatCompletionMessageParam[],
|
|
518
|
+
maxUserMessagesToCache: number = 2,
|
|
519
|
+
): number[] {
|
|
520
|
+
if (
|
|
521
|
+
!Array.isArray(messages) ||
|
|
522
|
+
messages.length === 0 ||
|
|
523
|
+
maxUserMessagesToCache <= 0
|
|
524
|
+
) {
|
|
525
|
+
return [];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const userMessageIndices: number[] = [];
|
|
529
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
530
|
+
if (messages[i].role === "user") {
|
|
531
|
+
userMessageIndices.push(i);
|
|
532
|
+
if (userMessageIndices.length >= maxUserMessagesToCache) {
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Return indices in original order (not reversed)
|
|
539
|
+
return userMessageIndices.reverse();
|
|
540
|
+
}
|