wave-agent-sdk 0.0.6 → 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.
Files changed (180) hide show
  1. package/dist/agent.d.ts +32 -20
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +209 -24
  4. package/dist/constants/events.d.ts +28 -0
  5. package/dist/constants/events.d.ts.map +1 -0
  6. package/dist/constants/events.js +27 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -0
  10. package/dist/managers/aiManager.d.ts +34 -1
  11. package/dist/managers/aiManager.d.ts.map +1 -1
  12. package/dist/managers/aiManager.js +248 -132
  13. package/dist/managers/backgroundBashManager.d.ts.map +1 -1
  14. package/dist/managers/backgroundBashManager.js +7 -6
  15. package/dist/managers/hookManager.d.ts +13 -16
  16. package/dist/managers/hookManager.d.ts.map +1 -1
  17. package/dist/managers/hookManager.js +81 -44
  18. package/dist/managers/liveConfigManager.d.ts +58 -0
  19. package/dist/managers/liveConfigManager.d.ts.map +1 -0
  20. package/dist/managers/liveConfigManager.js +160 -0
  21. package/dist/managers/messageManager.d.ts +41 -24
  22. package/dist/managers/messageManager.d.ts.map +1 -1
  23. package/dist/managers/messageManager.js +168 -49
  24. package/dist/managers/slashCommandManager.d.ts.map +1 -1
  25. package/dist/managers/slashCommandManager.js +9 -3
  26. package/dist/managers/subagentManager.d.ts +51 -0
  27. package/dist/managers/subagentManager.d.ts.map +1 -1
  28. package/dist/managers/subagentManager.js +190 -19
  29. package/dist/services/aiService.d.ts +13 -5
  30. package/dist/services/aiService.d.ts.map +1 -1
  31. package/dist/services/aiService.js +350 -74
  32. package/dist/services/configurationWatcher.d.ts +120 -0
  33. package/dist/services/configurationWatcher.d.ts.map +1 -0
  34. package/dist/services/configurationWatcher.js +439 -0
  35. package/dist/services/fileWatcher.d.ts +69 -0
  36. package/dist/services/fileWatcher.d.ts.map +1 -0
  37. package/dist/services/fileWatcher.js +213 -0
  38. package/dist/services/hook.d.ts +91 -9
  39. package/dist/services/hook.d.ts.map +1 -1
  40. package/dist/services/hook.js +393 -43
  41. package/dist/services/jsonlHandler.d.ts +62 -0
  42. package/dist/services/jsonlHandler.d.ts.map +1 -0
  43. package/dist/services/jsonlHandler.js +257 -0
  44. package/dist/services/memory.d.ts +9 -0
  45. package/dist/services/memory.d.ts.map +1 -1
  46. package/dist/services/memory.js +81 -12
  47. package/dist/services/memoryStore.d.ts +81 -0
  48. package/dist/services/memoryStore.d.ts.map +1 -0
  49. package/dist/services/memoryStore.js +200 -0
  50. package/dist/services/session.d.ts +64 -49
  51. package/dist/services/session.d.ts.map +1 -1
  52. package/dist/services/session.js +310 -132
  53. package/dist/tools/bashTool.d.ts.map +1 -1
  54. package/dist/tools/bashTool.js +5 -4
  55. package/dist/tools/deleteFileTool.d.ts.map +1 -1
  56. package/dist/tools/deleteFileTool.js +2 -1
  57. package/dist/tools/editTool.d.ts.map +1 -1
  58. package/dist/tools/editTool.js +3 -2
  59. package/dist/tools/multiEditTool.d.ts.map +1 -1
  60. package/dist/tools/multiEditTool.js +4 -3
  61. package/dist/tools/readTool.d.ts.map +1 -1
  62. package/dist/tools/readTool.js +2 -1
  63. package/dist/tools/todoWriteTool.d.ts.map +1 -1
  64. package/dist/tools/todoWriteTool.js +3 -10
  65. package/dist/tools/writeTool.d.ts.map +1 -1
  66. package/dist/tools/writeTool.js +5 -6
  67. package/dist/types/commands.d.ts +4 -0
  68. package/dist/types/commands.d.ts.map +1 -1
  69. package/dist/types/core.d.ts +35 -0
  70. package/dist/types/core.d.ts.map +1 -1
  71. package/dist/types/environment.d.ts +42 -0
  72. package/dist/types/environment.d.ts.map +1 -0
  73. package/dist/types/environment.js +21 -0
  74. package/dist/types/hooks.d.ts +8 -2
  75. package/dist/types/hooks.d.ts.map +1 -1
  76. package/dist/types/hooks.js +8 -2
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/index.d.ts.map +1 -1
  79. package/dist/types/index.js +2 -0
  80. package/dist/types/memoryStore.d.ts +82 -0
  81. package/dist/types/memoryStore.d.ts.map +1 -0
  82. package/dist/types/memoryStore.js +7 -0
  83. package/dist/types/messaging.d.ts +21 -9
  84. package/dist/types/messaging.d.ts.map +1 -1
  85. package/dist/types/messaging.js +5 -1
  86. package/dist/types/session.d.ts +20 -0
  87. package/dist/types/session.d.ts.map +1 -0
  88. package/dist/types/session.js +7 -0
  89. package/dist/utils/bashHistory.d.ts.map +1 -1
  90. package/dist/utils/bashHistory.js +27 -26
  91. package/dist/utils/cacheControlUtils.d.ts +121 -0
  92. package/dist/utils/cacheControlUtils.d.ts.map +1 -0
  93. package/dist/utils/cacheControlUtils.js +367 -0
  94. package/dist/utils/commandPathResolver.d.ts +52 -0
  95. package/dist/utils/commandPathResolver.d.ts.map +1 -0
  96. package/dist/utils/commandPathResolver.js +145 -0
  97. package/dist/utils/configPaths.d.ts +85 -0
  98. package/dist/utils/configPaths.d.ts.map +1 -0
  99. package/dist/utils/configPaths.js +121 -0
  100. package/dist/utils/configResolver.d.ts +37 -10
  101. package/dist/utils/configResolver.d.ts.map +1 -1
  102. package/dist/utils/configResolver.js +127 -23
  103. package/dist/utils/constants.d.ts +1 -1
  104. package/dist/utils/constants.js +1 -1
  105. package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
  106. package/dist/utils/convertMessagesForAPI.js +8 -13
  107. package/dist/utils/customCommands.d.ts.map +1 -1
  108. package/dist/utils/customCommands.js +66 -21
  109. package/dist/utils/fileUtils.d.ts +15 -0
  110. package/dist/utils/fileUtils.d.ts.map +1 -0
  111. package/dist/utils/fileUtils.js +61 -0
  112. package/dist/utils/globalLogger.d.ts +102 -0
  113. package/dist/utils/globalLogger.d.ts.map +1 -0
  114. package/dist/utils/globalLogger.js +136 -0
  115. package/dist/utils/hookMatcher.d.ts +1 -6
  116. package/dist/utils/hookMatcher.d.ts.map +1 -1
  117. package/dist/utils/mcpUtils.d.ts.map +1 -1
  118. package/dist/utils/mcpUtils.js +25 -3
  119. package/dist/utils/messageOperations.d.ts +27 -27
  120. package/dist/utils/messageOperations.d.ts.map +1 -1
  121. package/dist/utils/messageOperations.js +46 -36
  122. package/dist/utils/pathEncoder.d.ts +104 -0
  123. package/dist/utils/pathEncoder.d.ts.map +1 -0
  124. package/dist/utils/pathEncoder.js +272 -0
  125. package/dist/utils/subagentParser.d.ts.map +1 -1
  126. package/dist/utils/subagentParser.js +2 -1
  127. package/dist/utils/tokenCalculation.d.ts +26 -0
  128. package/dist/utils/tokenCalculation.d.ts.map +1 -0
  129. package/dist/utils/tokenCalculation.js +36 -0
  130. package/package.json +6 -3
  131. package/src/agent.ts +301 -37
  132. package/src/constants/events.ts +38 -0
  133. package/src/index.ts +2 -0
  134. package/src/managers/aiManager.ts +325 -173
  135. package/src/managers/backgroundBashManager.ts +7 -6
  136. package/src/managers/hookManager.ts +106 -84
  137. package/src/managers/liveConfigManager.ts +248 -0
  138. package/src/managers/messageManager.ts +237 -100
  139. package/src/managers/slashCommandManager.ts +9 -7
  140. package/src/managers/subagentManager.ts +284 -22
  141. package/src/services/aiService.ts +474 -83
  142. package/src/services/configurationWatcher.ts +622 -0
  143. package/src/services/fileWatcher.ts +301 -0
  144. package/src/services/hook.ts +538 -47
  145. package/src/services/jsonlHandler.ts +319 -0
  146. package/src/services/memory.ts +92 -12
  147. package/src/services/memoryStore.ts +279 -0
  148. package/src/services/session.ts +381 -157
  149. package/src/tools/bashTool.ts +5 -4
  150. package/src/tools/deleteFileTool.ts +2 -1
  151. package/src/tools/editTool.ts +3 -2
  152. package/src/tools/multiEditTool.ts +4 -3
  153. package/src/tools/readTool.ts +2 -1
  154. package/src/tools/todoWriteTool.ts +3 -11
  155. package/src/tools/writeTool.ts +7 -6
  156. package/src/types/commands.ts +6 -0
  157. package/src/types/core.ts +44 -0
  158. package/src/types/environment.ts +60 -0
  159. package/src/types/hooks.ts +21 -8
  160. package/src/types/index.ts +2 -0
  161. package/src/types/memoryStore.ts +94 -0
  162. package/src/types/messaging.ts +21 -10
  163. package/src/types/session.ts +25 -0
  164. package/src/utils/bashHistory.ts +27 -27
  165. package/src/utils/cacheControlUtils.ts +540 -0
  166. package/src/utils/commandPathResolver.ts +189 -0
  167. package/src/utils/configPaths.ts +163 -0
  168. package/src/utils/configResolver.ts +182 -22
  169. package/src/utils/constants.ts +1 -1
  170. package/src/utils/convertMessagesForAPI.ts +8 -14
  171. package/src/utils/customCommands.ts +90 -22
  172. package/src/utils/fileUtils.ts +65 -0
  173. package/src/utils/globalLogger.ts +145 -0
  174. package/src/utils/hookMatcher.ts +1 -12
  175. package/src/utils/mcpUtils.ts +34 -3
  176. package/src/utils/messageOperations.ts +77 -60
  177. package/src/utils/pathEncoder.ts +379 -0
  178. package/src/utils/subagentParser.ts +2 -1
  179. package/src/utils/tokenCalculation.ts +43 -0
  180. package/src/types/index.ts.backup +0 -357
@@ -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
- // logger.debug("Failed to create data directory:", error);
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
- // logger.debug("Bash history version mismatch, resetting history");
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
- // logger.debug("Failed to load bash history:", error);
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
- // logger.debug("Skipping bash history save in test environment");
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
- // logger.debug("Failed to save bash history:", error);
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
- // logger.debug("Added bash command to history:", { command, workdir });
134
- } catch {
135
- // logger.debug("Failed to add bash command to history:", error);
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
- // logger.debug("Bash history search results:", { query, workdir: process.cwd(), originalCount: matches.length, dedupedCount: result.length });
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
- // logger.debug("Failed to search bash history:", error);
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
- // logger.debug("Failed to get recent bash commands:", error);
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
- // logger.debug("Bash history cleared");
268
- } catch {
269
- // logger.debug("Failed to clear bash history:", error);
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
- // logger.debug("Failed to get bash command stats:", error);
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
+ }