wave-agent-sdk 1.0.4 → 1.0.6
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 +23 -1
- package/dist/agent.js +34 -4
- package/dist/managers/aiManager.d.ts +36 -0
- package/dist/managers/aiManager.js +240 -8
- package/dist/managers/messageManager.d.ts +1 -3
- package/dist/managers/messageManager.js +19 -9
- package/dist/managers/slashCommandManager.js +28 -0
- package/dist/managers/subagentManager.d.ts +0 -2
- package/dist/services/session.js +2 -9
- package/dist/telemetry/instrumentation.d.ts +1 -1
- package/dist/telemetry/instrumentation.js +57 -28
- package/dist/tools/bashTool.js +16 -6
- package/dist/tools/grepTool.js +11 -3
- package/dist/types/index.d.ts +3 -0
- package/dist/types/messaging.d.ts +2 -0
- package/dist/utils/containerSetup.js +0 -3
- package/dist/utils/messageOperations.d.ts +10 -1
- package/dist/utils/messageOperations.js +19 -1
- package/dist/utils/notificationXml.js +5 -0
- package/dist/utils/ripgrep.d.ts +4 -4
- package/dist/utils/ripgrep.js +4 -27
- package/package.json +20 -8
- package/bin/rg +0 -79
- package/dist/managers/forkedAgentManager.d.ts +0 -50
- package/dist/managers/forkedAgentManager.js +0 -130
- package/scripts/install_ripgrep.js +0 -111
- package/scripts/postinstall.js +0 -38
- package/src/agent.ts +0 -1271
- package/src/constants/subagents.ts +0 -4
- package/src/constants/toolLimits.ts +0 -15
- package/src/constants/tools.ts +0 -24
- package/src/core/plugin.ts +0 -237
- package/src/core/session.ts +0 -9
- package/src/index.ts +0 -38
- package/src/managers/MemoryRuleManager.ts +0 -198
- package/src/managers/aiManager.ts +0 -2429
- package/src/managers/backgroundTaskManager.ts +0 -480
- package/src/managers/bangManager.ts +0 -119
- package/src/managers/cronManager.ts +0 -401
- package/src/managers/foregroundTaskManager.ts +0 -30
- package/src/managers/forkedAgentManager.ts +0 -216
- package/src/managers/hookManager.ts +0 -1089
- package/src/managers/liveConfigManager.ts +0 -445
- package/src/managers/lspManager.ts +0 -470
- package/src/managers/mcpManager.ts +0 -1010
- package/src/managers/messageManager.ts +0 -1180
- package/src/managers/messageQueue.ts +0 -157
- package/src/managers/permissionManager.ts +0 -1140
- package/src/managers/planManager.ts +0 -112
- package/src/managers/pluginManager.ts +0 -330
- package/src/managers/pluginScopeManager.ts +0 -124
- package/src/managers/reversionManager.ts +0 -178
- package/src/managers/skillManager.ts +0 -578
- package/src/managers/slashCommandManager.ts +0 -564
- package/src/managers/subagentManager.ts +0 -915
- package/src/managers/toolManager.ts +0 -489
- package/src/managers/workflowManager.ts +0 -493
- package/src/prompts/autoMemory.ts +0 -33
- package/src/prompts/autoMemoryExtraction.ts +0 -146
- package/src/prompts/index.ts +0 -506
- package/src/prompts/planModeReminders.ts +0 -126
- package/src/services/GitService.ts +0 -131
- package/src/services/MarketplaceService.ts +0 -1052
- package/src/services/MemoryRuleService.ts +0 -71
- package/src/services/aiService.ts +0 -919
- package/src/services/authService.ts +0 -565
- package/src/services/autoMemoryService.ts +0 -280
- package/src/services/configurationService.ts +0 -1520
- package/src/services/fileWatcher.ts +0 -319
- package/src/services/hook.ts +0 -348
- package/src/services/initializationService.ts +0 -357
- package/src/services/interactionService.ts +0 -229
- package/src/services/jsonlHandler.ts +0 -295
- package/src/services/memory.ts +0 -234
- package/src/services/pluginLoader.ts +0 -252
- package/src/services/remoteSettingsService.ts +0 -366
- package/src/services/reversionService.ts +0 -122
- package/src/services/session.ts +0 -922
- package/src/services/taskManager.ts +0 -321
- package/src/telemetry/events.ts +0 -65
- package/src/telemetry/instrumentation.ts +0 -499
- package/src/telemetry/sessionTracing.ts +0 -348
- package/src/tools/agentTool.ts +0 -322
- package/src/tools/askUserQuestion.ts +0 -160
- package/src/tools/bashTool.ts +0 -621
- package/src/tools/buildTool.ts +0 -61
- package/src/tools/cronCreateTool.ts +0 -161
- package/src/tools/cronDeleteTool.ts +0 -51
- package/src/tools/cronListTool.ts +0 -47
- package/src/tools/editTool.ts +0 -313
- package/src/tools/enterPlanMode.ts +0 -124
- package/src/tools/enterWorktreeTool.ts +0 -190
- package/src/tools/exitPlanMode.ts +0 -139
- package/src/tools/exitWorktreeTool.ts +0 -236
- package/src/tools/globTool.ts +0 -167
- package/src/tools/grepTool.ts +0 -399
- package/src/tools/lspTool.ts +0 -883
- package/src/tools/readTool.ts +0 -426
- package/src/tools/skillTool.ts +0 -248
- package/src/tools/taskManagementTools.ts +0 -574
- package/src/tools/taskStopTool.ts +0 -78
- package/src/tools/types.ts +0 -137
- package/src/tools/webFetchTool.ts +0 -373
- package/src/tools/workflowTool.ts +0 -205
- package/src/tools/writeTool.ts +0 -267
- package/src/types/agent.ts +0 -128
- package/src/types/auth.ts +0 -19
- package/src/types/commands.ts +0 -30
- package/src/types/config.ts +0 -43
- package/src/types/configuration.ts +0 -168
- package/src/types/core.ts +0 -105
- package/src/types/cron.ts +0 -13
- package/src/types/environment.ts +0 -106
- package/src/types/fileSearch.ts +0 -4
- package/src/types/history.ts +0 -7
- package/src/types/hooks.ts +0 -256
- package/src/types/index.ts +0 -41
- package/src/types/lsp.ts +0 -98
- package/src/types/marketplace.ts +0 -71
- package/src/types/mcp.ts +0 -43
- package/src/types/memoryRule.ts +0 -31
- package/src/types/messaging.ts +0 -114
- package/src/types/permissions.ts +0 -80
- package/src/types/plugins.ts +0 -39
- package/src/types/processes.ts +0 -99
- package/src/types/reversion.ts +0 -29
- package/src/types/session.ts +0 -12
- package/src/types/skills.ts +0 -108
- package/src/types/tasks.ts +0 -13
- package/src/types/telemetry.ts +0 -98
- package/src/types/tools.ts +0 -44
- package/src/types/workflow.ts +0 -6
- package/src/utils/abortUtils.ts +0 -118
- package/src/utils/atomicWrite.ts +0 -61
- package/src/utils/bashParser.ts +0 -899
- package/src/utils/cacheControlUtils.ts +0 -426
- package/src/utils/commandArgumentParser.ts +0 -161
- package/src/utils/commandPathResolver.ts +0 -88
- package/src/utils/configPaths.ts +0 -220
- package/src/utils/configValidator.ts +0 -100
- package/src/utils/constants.ts +0 -39
- package/src/utils/container.ts +0 -92
- package/src/utils/containerSetup.ts +0 -371
- package/src/utils/convertMessagesForAPI.ts +0 -326
- package/src/utils/cronTasks.ts +0 -128
- package/src/utils/cronTasksLock.ts +0 -182
- package/src/utils/cronToHuman.ts +0 -99
- package/src/utils/customCommands.ts +0 -95
- package/src/utils/editUtils.ts +0 -20
- package/src/utils/fileFormat.ts +0 -40
- package/src/utils/fileSearch.ts +0 -151
- package/src/utils/fileUtils.ts +0 -223
- package/src/utils/gitUtils.ts +0 -315
- package/src/utils/globalLogger.ts +0 -128
- package/src/utils/groupMessagesByApiRound.ts +0 -121
- package/src/utils/hookMatcher.ts +0 -176
- package/src/utils/markdownParser.ts +0 -317
- package/src/utils/mcpUtils.ts +0 -138
- package/src/utils/messageOperations.ts +0 -641
- package/src/utils/modelCapabilities.ts +0 -30
- package/src/utils/nameGenerator.ts +0 -99
- package/src/utils/notificationXml.ts +0 -57
- package/src/utils/openaiClient.ts +0 -251
- package/src/utils/parseCronExpression.ts +0 -78
- package/src/utils/path.ts +0 -72
- package/src/utils/pathEncoder.ts +0 -400
- package/src/utils/pathSafety.ts +0 -40
- package/src/utils/promptHistory.ts +0 -168
- package/src/utils/ripgrep.ts +0 -35
- package/src/utils/shellResolver.ts +0 -190
- package/src/utils/skillParser.ts +0 -246
- package/src/utils/stringUtils.ts +0 -156
- package/src/utils/subagentParser.ts +0 -279
- package/src/utils/taskReminder.ts +0 -96
- package/src/utils/tokenCalculation.ts +0 -43
- package/src/utils/tokenEstimate.ts +0 -34
- package/src/utils/toolResultStorage.ts +0 -117
- package/src/utils/worktreeSession.ts +0 -26
- package/src/utils/worktreeUtils.ts +0 -786
- package/src/workflow/budgetTracker.ts +0 -34
- package/src/workflow/concurrencyLimiter.ts +0 -47
- package/src/workflow/journal.ts +0 -95
- package/src/workflow/progressReporter.ts +0 -141
- package/src/workflow/runState.ts +0 -65
- package/src/workflow/scriptRuntime.ts +0 -274
- package/src/workflow/structuredOutput.ts +0 -123
- package/src/workflow/types.ts +0 -101
- package/src/workflow/workflowApis.ts +0 -410
- package/vendor/ripgrep/linux-aarch64/rg +0 -0
- package/vendor/ripgrep/linux-x86_64/rg +0 -0
- package/vendor/ripgrep/macos-aarch64/rg +0 -0
- package/vendor/ripgrep/macos-x86_64/rg +0 -0
- package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
- package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
|
@@ -1,426 +0,0 @@
|
|
|
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
|
-
CompletionUsage,
|
|
14
|
-
} from "openai/resources";
|
|
15
|
-
import { logger } from "./globalLogger.js";
|
|
16
|
-
import { supportsPromptCaching } from "./modelCapabilities.js";
|
|
17
|
-
import type { ModelCapabilities } from "../types/config.js";
|
|
18
|
-
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Core Types
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Cache control directive for Claude models
|
|
25
|
-
*/
|
|
26
|
-
export interface CacheControl {
|
|
27
|
-
type: "ephemeral";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Extended text content part with cache control support
|
|
32
|
-
*/
|
|
33
|
-
export interface ClaudeChatCompletionContentPartText
|
|
34
|
-
extends ChatCompletionContentPartText {
|
|
35
|
-
type: "text";
|
|
36
|
-
text: string;
|
|
37
|
-
cache_control?: CacheControl;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Extended prompt_tokens_details with cache_creation_input_tokens
|
|
42
|
-
* Some models (e.g. Gemini, DeepSeek) return this field inside prompt_tokens_details
|
|
43
|
-
*/
|
|
44
|
-
export interface ExtendedPromptTokensDetails
|
|
45
|
-
extends CompletionUsage.PromptTokensDetails {
|
|
46
|
-
cache_creation_input_tokens?: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Enhanced usage metrics including cache information
|
|
51
|
-
* Supports both Claude-specific top-level fields and OpenAI-standard prompt_tokens_details
|
|
52
|
-
*/
|
|
53
|
-
export interface ClaudeUsage extends CompletionUsage {
|
|
54
|
-
prompt_tokens: number;
|
|
55
|
-
completion_tokens: number;
|
|
56
|
-
total_tokens: number;
|
|
57
|
-
|
|
58
|
-
// Cache extensions (from Claude top-level or OpenAI prompt_tokens_details)
|
|
59
|
-
cache_read_input_tokens?: number; // Claude: cache_read_input_tokens / OpenAI: prompt_tokens_details.cached_tokens
|
|
60
|
-
cache_creation_input_tokens?: number; // Claude: cache_creation_input_tokens / OpenAI: prompt_tokens_details.cache_creation_input_tokens
|
|
61
|
-
cache_creation?: {
|
|
62
|
-
ephemeral_5m_input_tokens: number;
|
|
63
|
-
ephemeral_1h_input_tokens: number;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Override prompt_tokens_details to include cache_creation_input_tokens
|
|
67
|
-
prompt_tokens_details?: ExtendedPromptTokensDetails;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ============================================================================
|
|
71
|
-
// Default Configuration
|
|
72
|
-
// ============================================================================
|
|
73
|
-
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// Utility Functions (Basic Structure - to be implemented)
|
|
76
|
-
// ============================================================================
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Validates cache control structure
|
|
80
|
-
* @param control - Object to validate
|
|
81
|
-
* @returns True if valid cache control object
|
|
82
|
-
*/
|
|
83
|
-
export function isValidCacheControl(control: unknown): control is CacheControl {
|
|
84
|
-
return (
|
|
85
|
-
control !== null &&
|
|
86
|
-
typeof control === "object" &&
|
|
87
|
-
control !== undefined &&
|
|
88
|
-
"type" in control &&
|
|
89
|
-
(control as { type: unknown }).type === "ephemeral"
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Adds cache control markers to message content
|
|
95
|
-
* @param content - Original content (string or structured)
|
|
96
|
-
* @param shouldCache - Whether to add cache control
|
|
97
|
-
* @returns Structured content with cache control markers
|
|
98
|
-
*/
|
|
99
|
-
export function addCacheControlToContent(
|
|
100
|
-
content: string | ChatCompletionContentPart[],
|
|
101
|
-
shouldCache: boolean,
|
|
102
|
-
): ClaudeChatCompletionContentPartText[] {
|
|
103
|
-
// Handle null/undefined content
|
|
104
|
-
if (content == null) {
|
|
105
|
-
return [];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// If shouldCache is false, return content as text parts without cache control
|
|
109
|
-
if (!shouldCache) {
|
|
110
|
-
if (typeof content === "string") {
|
|
111
|
-
return [{ type: "text", text: content }];
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Validate array input
|
|
115
|
-
if (!Array.isArray(content)) {
|
|
116
|
-
logger.warn(
|
|
117
|
-
"Invalid content type for cache control transformation:",
|
|
118
|
-
typeof content,
|
|
119
|
-
);
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Filter and convert only text parts with validation
|
|
124
|
-
return content
|
|
125
|
-
.filter((part): part is ChatCompletionContentPartText => {
|
|
126
|
-
if (!part || typeof part !== "object") {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
return part.type === "text" && typeof part.text === "string";
|
|
130
|
-
})
|
|
131
|
-
.map((part) => ({ type: "text", text: part.text }));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// shouldCache is true - add cache control markers
|
|
135
|
-
if (typeof content === "string") {
|
|
136
|
-
// Transform string content to structured array with cache control
|
|
137
|
-
return [
|
|
138
|
-
{
|
|
139
|
-
type: "text",
|
|
140
|
-
text: content,
|
|
141
|
-
cache_control: { type: "ephemeral" },
|
|
142
|
-
},
|
|
143
|
-
];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Validate array input
|
|
147
|
-
if (!Array.isArray(content)) {
|
|
148
|
-
logger.warn(
|
|
149
|
-
"Invalid content type for cache control transformation:",
|
|
150
|
-
typeof content,
|
|
151
|
-
);
|
|
152
|
-
return [];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Handle structured content - preserve all parts, add cache control to last text part only
|
|
156
|
-
let lastTextIndex = -1;
|
|
157
|
-
for (let i = content.length - 1; i >= 0; i--) {
|
|
158
|
-
const part = content[i];
|
|
159
|
-
if (
|
|
160
|
-
part &&
|
|
161
|
-
typeof part === "object" &&
|
|
162
|
-
part.type === "text" &&
|
|
163
|
-
typeof (part as ChatCompletionContentPartText).text === "string"
|
|
164
|
-
) {
|
|
165
|
-
lastTextIndex = i;
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return content.map((part, index) => {
|
|
171
|
-
if (
|
|
172
|
-
index === lastTextIndex &&
|
|
173
|
-
part &&
|
|
174
|
-
typeof part === "object" &&
|
|
175
|
-
part.type === "text" &&
|
|
176
|
-
typeof (part as ChatCompletionContentPartText).text === "string"
|
|
177
|
-
) {
|
|
178
|
-
return {
|
|
179
|
-
...(part as ChatCompletionContentPartText),
|
|
180
|
-
cache_control: { type: "ephemeral" },
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
return part;
|
|
184
|
-
}) as ClaudeChatCompletionContentPartText[];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Counts the total number of content blocks across all messages.
|
|
189
|
-
* Each element in a message's content array counts as one block.
|
|
190
|
-
* String content counts as one block. Null/undefined content counts as zero
|
|
191
|
-
* (e.g. assistant messages with only tool_calls).
|
|
192
|
-
* @param messages - Array of chat messages
|
|
193
|
-
* @returns Total content block count
|
|
194
|
-
*/
|
|
195
|
-
export function countContentBlocks(
|
|
196
|
-
messages: ChatCompletionMessageParam[],
|
|
197
|
-
): number {
|
|
198
|
-
let count = 0;
|
|
199
|
-
for (const message of messages) {
|
|
200
|
-
if (!message || typeof message !== "object") continue;
|
|
201
|
-
const content = message.content;
|
|
202
|
-
if (typeof content === "string") {
|
|
203
|
-
count += 1;
|
|
204
|
-
} else if (Array.isArray(content)) {
|
|
205
|
-
count += content.length;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return count;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Transforms messages for explicit cache control.
|
|
213
|
-
*
|
|
214
|
-
* Cache breakpoints (following Claude Code's "last message marker" strategy):
|
|
215
|
-
* 1. System message — always marked (stable prefix)
|
|
216
|
-
* 2. Last message — the last user/assistant message with content is marked.
|
|
217
|
-
* The API scans backward from this marker within 20 blocks. Since each
|
|
218
|
-
* turn adds ~2 blocks (< 20), the previous request's marker position
|
|
219
|
-
* always falls within the scan window, ensuring cache hits.
|
|
220
|
-
*
|
|
221
|
-
* Tools are marked separately via addCacheControlToLastTool (called by aiService).
|
|
222
|
-
*
|
|
223
|
-
* @param messages - Original OpenAI message array
|
|
224
|
-
* @param capabilities - Declarative model capabilities for cache detection
|
|
225
|
-
* @returns Messages with cache control markers applied
|
|
226
|
-
*/
|
|
227
|
-
export function transformMessagesForExplicitCache(
|
|
228
|
-
messages: ChatCompletionMessageParam[],
|
|
229
|
-
capabilities?: ModelCapabilities,
|
|
230
|
-
): ChatCompletionMessageParam[] {
|
|
231
|
-
// Validate inputs
|
|
232
|
-
if (!messages || !Array.isArray(messages)) {
|
|
233
|
-
logger.warn(
|
|
234
|
-
"Invalid messages array provided to transformMessagesForExplicitCache",
|
|
235
|
-
);
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (messages.length === 0) {
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Only apply cache control for models that support prompt caching
|
|
244
|
-
if (!supportsPromptCaching(capabilities)) {
|
|
245
|
-
return messages;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Find first system message index
|
|
249
|
-
const firstSystemIndex = messages.findIndex((m) => m.role === "system");
|
|
250
|
-
|
|
251
|
-
// Determine which message indices should receive cache_control markers
|
|
252
|
-
const cacheIndices = new Set<number>();
|
|
253
|
-
|
|
254
|
-
// Marker 1: First system message (always — stable prefix)
|
|
255
|
-
if (firstSystemIndex !== -1) {
|
|
256
|
-
cacheIndices.add(firstSystemIndex);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Marker 2: Last message with content (user or assistant).
|
|
260
|
-
// This marker moves each turn (~2 blocks), but since the move is < 20 blocks,
|
|
261
|
-
// the previous marker position is always within the API's 20-block scan window.
|
|
262
|
-
// This ensures the entire conversation prefix is cached.
|
|
263
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
264
|
-
const msg = messages[i];
|
|
265
|
-
if (!msg || typeof msg !== "object") continue;
|
|
266
|
-
if (i === firstSystemIndex) continue;
|
|
267
|
-
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
268
|
-
|
|
269
|
-
const content = msg.content;
|
|
270
|
-
const hasContent =
|
|
271
|
-
(typeof content === "string" && content.length > 0) ||
|
|
272
|
-
(Array.isArray(content) && content.length > 0);
|
|
273
|
-
|
|
274
|
-
if (hasContent) {
|
|
275
|
-
cacheIndices.add(i);
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Apply cache_control markers to selected messages
|
|
281
|
-
const result = messages.map((message, index) => {
|
|
282
|
-
// Validate message structure
|
|
283
|
-
if (!message || typeof message !== "object" || !message.role) {
|
|
284
|
-
logger.warn("Invalid message structure at index", index, ":", message);
|
|
285
|
-
return message;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!cacheIndices.has(index)) {
|
|
289
|
-
return message;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const content =
|
|
293
|
-
(message.content as string | ChatCompletionContentPart[]) || "";
|
|
294
|
-
|
|
295
|
-
// Idempotency: skip if content already has cache_control (system message)
|
|
296
|
-
if (message.role === "system" && Array.isArray(content)) {
|
|
297
|
-
const hasCacheControl = content.some(
|
|
298
|
-
(part) =>
|
|
299
|
-
part.type === "text" &&
|
|
300
|
-
(part as ClaudeChatCompletionContentPartText).cache_control,
|
|
301
|
-
);
|
|
302
|
-
if (hasCacheControl) {
|
|
303
|
-
return message;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const transformedContent = addCacheControlToContent(content, true);
|
|
308
|
-
|
|
309
|
-
return {
|
|
310
|
-
...message,
|
|
311
|
-
content: transformedContent,
|
|
312
|
-
} as ChatCompletionMessageParam;
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
return result;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Extends standard usage with cache metrics
|
|
320
|
-
* Extracts cache tokens from both Claude-specific top-level fields and
|
|
321
|
-
* OpenAI-standard prompt_tokens_details (used by Gemini, DeepSeek, etc.)
|
|
322
|
-
* @param standardUsage - OpenAI usage response
|
|
323
|
-
* @param cacheMetrics - Additional cache metrics from the API response
|
|
324
|
-
* @returns Extended usage with cache information
|
|
325
|
-
*/
|
|
326
|
-
export function extendUsageWithCacheMetrics(
|
|
327
|
-
standardUsage: CompletionUsage,
|
|
328
|
-
cacheMetrics?: Partial<ClaudeUsage>,
|
|
329
|
-
): ClaudeUsage {
|
|
330
|
-
const baseUsage: ClaudeUsage = {
|
|
331
|
-
prompt_tokens: standardUsage.prompt_tokens,
|
|
332
|
-
completion_tokens: standardUsage.completion_tokens,
|
|
333
|
-
total_tokens: standardUsage.total_tokens,
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
if (!cacheMetrics) {
|
|
337
|
-
return baseUsage;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Extract cache_read_input_tokens from Claude top-level field
|
|
341
|
-
if (typeof cacheMetrics.cache_read_input_tokens === "number") {
|
|
342
|
-
baseUsage.cache_read_input_tokens = cacheMetrics.cache_read_input_tokens;
|
|
343
|
-
}
|
|
344
|
-
// Fallback to prompt_tokens_details.cached_tokens (OpenAI standard)
|
|
345
|
-
else if (cacheMetrics.prompt_tokens_details?.cached_tokens != null) {
|
|
346
|
-
baseUsage.cache_read_input_tokens =
|
|
347
|
-
cacheMetrics.prompt_tokens_details.cached_tokens;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Extract cache_creation_input_tokens from Claude top-level field
|
|
351
|
-
if (typeof cacheMetrics.cache_creation_input_tokens === "number") {
|
|
352
|
-
baseUsage.cache_creation_input_tokens =
|
|
353
|
-
cacheMetrics.cache_creation_input_tokens;
|
|
354
|
-
}
|
|
355
|
-
// Fallback to prompt_tokens_details.cache_creation_input_tokens
|
|
356
|
-
else if (
|
|
357
|
-
cacheMetrics.prompt_tokens_details?.cache_creation_input_tokens != null
|
|
358
|
-
) {
|
|
359
|
-
baseUsage.cache_creation_input_tokens =
|
|
360
|
-
cacheMetrics.prompt_tokens_details.cache_creation_input_tokens;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Extract cache_creation breakdown (Claude-specific)
|
|
364
|
-
if (
|
|
365
|
-
cacheMetrics.cache_creation &&
|
|
366
|
-
typeof cacheMetrics.cache_creation.ephemeral_5m_input_tokens === "number" &&
|
|
367
|
-
typeof cacheMetrics.cache_creation.ephemeral_1h_input_tokens === "number"
|
|
368
|
-
) {
|
|
369
|
-
baseUsage.cache_creation = {
|
|
370
|
-
ephemeral_5m_input_tokens:
|
|
371
|
-
cacheMetrics.cache_creation.ephemeral_5m_input_tokens,
|
|
372
|
-
ephemeral_1h_input_tokens:
|
|
373
|
-
cacheMetrics.cache_creation.ephemeral_1h_input_tokens,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
return baseUsage;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Validates Claude usage structure
|
|
382
|
-
* @param usage - Usage object to validate
|
|
383
|
-
* @returns True if usage structure is valid
|
|
384
|
-
*/
|
|
385
|
-
export function isValidClaudeUsage(usage: unknown): usage is ClaudeUsage {
|
|
386
|
-
if (!usage || typeof usage !== "object") {
|
|
387
|
-
return false;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const usageObj = usage as Record<string, unknown>;
|
|
391
|
-
|
|
392
|
-
// Check required standard fields
|
|
393
|
-
const hasStandardFields =
|
|
394
|
-
typeof usageObj.prompt_tokens === "number" &&
|
|
395
|
-
typeof usageObj.completion_tokens === "number" &&
|
|
396
|
-
typeof usageObj.total_tokens === "number";
|
|
397
|
-
|
|
398
|
-
if (!hasStandardFields) {
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Check optional cache fields
|
|
403
|
-
const hasCacheFields =
|
|
404
|
-
(usageObj.cache_read_input_tokens === undefined ||
|
|
405
|
-
typeof usageObj.cache_read_input_tokens === "number") &&
|
|
406
|
-
(usageObj.cache_creation_input_tokens === undefined ||
|
|
407
|
-
typeof usageObj.cache_creation_input_tokens === "number");
|
|
408
|
-
|
|
409
|
-
if (!hasCacheFields) {
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Check cache_creation object if present
|
|
414
|
-
if (usageObj.cache_creation !== undefined) {
|
|
415
|
-
const cacheCreation = usageObj.cache_creation as Record<string, unknown>;
|
|
416
|
-
if (
|
|
417
|
-
typeof cacheCreation !== "object" ||
|
|
418
|
-
typeof cacheCreation.ephemeral_5m_input_tokens !== "number" ||
|
|
419
|
-
typeof cacheCreation.ephemeral_1h_input_tokens !== "number"
|
|
420
|
-
) {
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return true;
|
|
426
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command Argument Parser
|
|
3
|
-
*
|
|
4
|
-
* Provides parameter substitution for custom slash commands similar to Claude's system:
|
|
5
|
-
* - $ARGUMENTS: All arguments as a single string
|
|
6
|
-
* - $1, $2, $3, etc.: Individual positional arguments
|
|
7
|
-
* - Supports quoted arguments with spaces
|
|
8
|
-
* - Handles escaped quotes within arguments
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Parse command arguments from a string, respecting quotes
|
|
13
|
-
*/
|
|
14
|
-
export function parseCommandArguments(argsString: string): string[] {
|
|
15
|
-
if (!argsString.trim()) {
|
|
16
|
-
return [];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const args: string[] = [];
|
|
20
|
-
let current = "";
|
|
21
|
-
let inQuotes = false;
|
|
22
|
-
let quoteChar = "";
|
|
23
|
-
let escaped = false;
|
|
24
|
-
|
|
25
|
-
for (let i = 0; i < argsString.length; i++) {
|
|
26
|
-
const char = argsString[i];
|
|
27
|
-
const nextChar = argsString[i + 1];
|
|
28
|
-
|
|
29
|
-
if (escaped) {
|
|
30
|
-
current += char;
|
|
31
|
-
escaped = false;
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (char === "\\") {
|
|
36
|
-
// Handle escape sequences
|
|
37
|
-
if (inQuotes && (nextChar === quoteChar || nextChar === "\\")) {
|
|
38
|
-
escaped = true;
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
current += char;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!inQuotes && (char === '"' || char === "'")) {
|
|
46
|
-
// Start quoted string
|
|
47
|
-
inQuotes = true;
|
|
48
|
-
quoteChar = char;
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (inQuotes && char === quoteChar) {
|
|
53
|
-
// End quoted string
|
|
54
|
-
inQuotes = false;
|
|
55
|
-
quoteChar = "";
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!inQuotes && /\s/.test(char)) {
|
|
60
|
-
// Whitespace outside quotes - end current argument
|
|
61
|
-
if (current) {
|
|
62
|
-
args.push(current);
|
|
63
|
-
current = "";
|
|
64
|
-
}
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
current += char;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Add final argument if any
|
|
72
|
-
if (current) {
|
|
73
|
-
args.push(current);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return args;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Substitute command parameters in content
|
|
81
|
-
*/
|
|
82
|
-
export function substituteCommandParameters(
|
|
83
|
-
content: string,
|
|
84
|
-
argsString: string,
|
|
85
|
-
): string {
|
|
86
|
-
if (!hasParameterPlaceholders(content)) {
|
|
87
|
-
if (!argsString.trim()) {
|
|
88
|
-
return content;
|
|
89
|
-
}
|
|
90
|
-
const separator = content && !/\s$/.test(content) ? " " : "";
|
|
91
|
-
return `${content}${separator}${argsString}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const args = parseCommandArguments(argsString);
|
|
95
|
-
|
|
96
|
-
let result = content;
|
|
97
|
-
|
|
98
|
-
// Replace $ARGUMENTS with all arguments
|
|
99
|
-
result = result.replace(/\$ARGUMENTS/g, argsString);
|
|
100
|
-
|
|
101
|
-
// Replace positional parameters $1, $2, etc.
|
|
102
|
-
// Sort by parameter number (descending) to avoid replacing $10 with $1 + "0"
|
|
103
|
-
const positionalParams = [...result.matchAll(/\$(\d+)/g)]
|
|
104
|
-
.map((match) => parseInt(match[1], 10))
|
|
105
|
-
.filter((value, index, array) => array.indexOf(value) === index) // unique
|
|
106
|
-
.sort((a, b) => b - a); // descending order
|
|
107
|
-
|
|
108
|
-
for (const paramNum of positionalParams) {
|
|
109
|
-
const paramValue = args[paramNum - 1] || ""; // Arrays are 0-indexed, params are 1-indexed
|
|
110
|
-
const paramRegex = new RegExp(`\\$${paramNum}`, "g");
|
|
111
|
-
result = result.replace(paramRegex, paramValue);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return result;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Extract command name and arguments from a slash command input
|
|
119
|
-
* Example: "/fix-issue 123 high-priority" -> { command: "fix-issue", args: "123 high-priority" }
|
|
120
|
-
*/
|
|
121
|
-
export function parseSlashCommandInput(input: string): {
|
|
122
|
-
command: string;
|
|
123
|
-
args: string;
|
|
124
|
-
} {
|
|
125
|
-
const trimmed = input.trim();
|
|
126
|
-
|
|
127
|
-
if (!trimmed.startsWith("/")) {
|
|
128
|
-
throw new Error("Input must start with /");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const withoutSlash = trimmed.substring(1);
|
|
132
|
-
const firstSpaceIndex = withoutSlash.indexOf(" ");
|
|
133
|
-
|
|
134
|
-
if (firstSpaceIndex === -1) {
|
|
135
|
-
// No arguments
|
|
136
|
-
return {
|
|
137
|
-
command: withoutSlash,
|
|
138
|
-
args: "",
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
command: withoutSlash.substring(0, firstSpaceIndex),
|
|
144
|
-
args: withoutSlash.substring(firstSpaceIndex + 1).trim(),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Check if content contains parameter placeholders
|
|
150
|
-
*/
|
|
151
|
-
export function hasParameterPlaceholders(content: string): boolean {
|
|
152
|
-
return /\$(?:ARGUMENTS|\d+)/.test(content);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get all parameter placeholders used in content
|
|
157
|
-
*/
|
|
158
|
-
export function getUsedParameterPlaceholders(content: string): string[] {
|
|
159
|
-
const matches = content.match(/\$(?:ARGUMENTS|\d+)/g);
|
|
160
|
-
return matches ? [...new Set(matches)] : [];
|
|
161
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { relative, basename } from "path";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Command path resolver utilities for command discovery
|
|
5
|
-
* Handles conversion between file paths and command IDs
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Generate command ID from file path
|
|
10
|
-
* @param filePath - Absolute path to markdown file
|
|
11
|
-
* @param rootDir - Root commands directory path
|
|
12
|
-
* @returns Command identifier string
|
|
13
|
-
* @throws Error on invalid path structure
|
|
14
|
-
*/
|
|
15
|
-
export function generateCommandId(filePath: string, rootDir: string): string {
|
|
16
|
-
// Handle null/undefined inputs
|
|
17
|
-
if (filePath == null || rootDir == null) {
|
|
18
|
-
throw new Error("File path and root directory must be provided");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Handle empty root directory (for root level commands)
|
|
22
|
-
const relativePath = rootDir === "" ? filePath : relative(rootDir, filePath);
|
|
23
|
-
|
|
24
|
-
// Handle edge cases
|
|
25
|
-
if (!relativePath || relativePath === ".") {
|
|
26
|
-
throw new Error("Command filename cannot be empty");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const segments = relativePath
|
|
30
|
-
.split(/[\\/]/)
|
|
31
|
-
.filter((segment) => segment !== "");
|
|
32
|
-
|
|
33
|
-
// Remove .md extension from the last segment
|
|
34
|
-
const lastSegment = segments[segments.length - 1];
|
|
35
|
-
if (!lastSegment.endsWith(".md")) {
|
|
36
|
-
throw new Error(`Command files must have .md extension`);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const commandName = basename(lastSegment, ".md");
|
|
40
|
-
|
|
41
|
-
// Handle empty filename after removing extension
|
|
42
|
-
if (commandName === "") {
|
|
43
|
-
throw new Error("Command filename cannot be empty");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Validate depth (no nesting allowed)
|
|
47
|
-
if (segments.length > 1) {
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Command nesting not supported: ${relativePath}. Commands must be in the root directory.`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Validate command name
|
|
54
|
-
if (!validateSegment(commandName)) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Invalid command name: "${commandName}" in ${relativePath}. Must match pattern /^[a-zA-Z][a-zA-Z0-9_.-]*$/`,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return commandName;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Validate command ID format
|
|
65
|
-
* @param commandId - Command identifier to validate
|
|
66
|
-
* @returns Boolean indicating validity
|
|
67
|
-
*/
|
|
68
|
-
export function validateCommandId(commandId: string): boolean {
|
|
69
|
-
// Handle null/undefined inputs
|
|
70
|
-
if (commandId == null) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Command ID must be a single segment (no colons)
|
|
75
|
-
const pattern = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
|
|
76
|
-
return pattern.test(commandId);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Validate individual path segment
|
|
81
|
-
* @param segment - Path segment to validate
|
|
82
|
-
* @returns Boolean indicating validity
|
|
83
|
-
*/
|
|
84
|
-
function validateSegment(segment: string): boolean {
|
|
85
|
-
// Segments should start with letters and can contain letters, numbers, dashes, underscores, dots
|
|
86
|
-
const pattern = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
|
|
87
|
-
return pattern.test(segment);
|
|
88
|
-
}
|