From 20fdac1b372e1c4a5b9217366559ac14a4a26e38 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 13:03:12 -0800 Subject: [PATCH 1/5] improve prompt caching by computing a deterministic hash --- pkg/aiusechat/openai/openai-convertmessage.go | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/aiusechat/openai/openai-convertmessage.go b/pkg/aiusechat/openai/openai-convertmessage.go index 78f91e7b5d..770da200dc 100644 --- a/pkg/aiusechat/openai/openai-convertmessage.go +++ b/pkg/aiusechat/openai/openai-convertmessage.go @@ -6,7 +6,9 @@ package openai import ( "bytes" "context" + "crypto/sha256" "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -388,8 +390,13 @@ func convertFileAIMessagePart(part uctypes.AIMessagePart) (*OpenAIMessageContent encodedFileName := strings.ReplaceAll(fileName, `"`, """) quotedFileName := strconv.Quote(encodedFileName) - randomSuffix := uuid.New().String()[0:8] - formattedText := fmt.Sprintf("\n%s\n", randomSuffix, quotedFileName, textContent, randomSuffix) + // Generate deterministic suffix from content hash for prompt caching + hasher := sha256.New() + hasher.Write([]byte(textContent)) + hasher.Write([]byte(fileName)) + hash := hasher.Sum(nil) + deterministicSuffix := hex.EncodeToString(hash)[:8] + formattedText := fmt.Sprintf("\n%s\n", deterministicSuffix, quotedFileName, textContent, deterministicSuffix) return &OpenAIMessageContent{ Type: "input_text", @@ -412,8 +419,13 @@ func convertFileAIMessagePart(part uctypes.AIMessagePart) (*OpenAIMessageContent encodedDirName := strings.ReplaceAll(directoryName, `"`, """) quotedDirName := strconv.Quote(encodedDirName) - randomSuffix := uuid.New().String()[0:8] - formattedText := fmt.Sprintf("\n%s\n", randomSuffix, quotedDirName, jsonContent, randomSuffix) + // Generate deterministic suffix from content hash for prompt caching + hasher := sha256.New() + hasher.Write([]byte(jsonContent)) + hasher.Write([]byte(directoryName)) + hash := hasher.Sum(nil) + deterministicSuffix := hex.EncodeToString(hash)[:8] + formattedText := fmt.Sprintf("\n%s\n", deterministicSuffix, quotedDirName, jsonContent, deterministicSuffix) return &OpenAIMessageContent{ Type: "input_text", From d48150abb7c4598c7ede9e9279447c63e1a1ab3a Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 14:18:14 -0800 Subject: [PATCH 2/5] extract func --- pkg/aiusechat/openai/openai-convertmessage.go | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/aiusechat/openai/openai-convertmessage.go b/pkg/aiusechat/openai/openai-convertmessage.go index 770da200dc..31cbe1fd04 100644 --- a/pkg/aiusechat/openai/openai-convertmessage.go +++ b/pkg/aiusechat/openai/openai-convertmessage.go @@ -60,6 +60,16 @@ func extractXmlAttribute(tag, attrName string) (string, bool) { return value, true } +// generateDeterministicSuffix creates an 8-character hash from input strings +func generateDeterministicSuffix(inputs ...string) string { + hasher := sha256.New() + for _, input := range inputs { + hasher.Write([]byte(input)) + } + hash := hasher.Sum(nil) + return hex.EncodeToString(hash)[:8] +} + // ---------- OpenAI Request Types ---------- type StreamOptionsType struct { @@ -390,12 +400,7 @@ func convertFileAIMessagePart(part uctypes.AIMessagePart) (*OpenAIMessageContent encodedFileName := strings.ReplaceAll(fileName, `"`, """) quotedFileName := strconv.Quote(encodedFileName) - // Generate deterministic suffix from content hash for prompt caching - hasher := sha256.New() - hasher.Write([]byte(textContent)) - hasher.Write([]byte(fileName)) - hash := hasher.Sum(nil) - deterministicSuffix := hex.EncodeToString(hash)[:8] + deterministicSuffix := generateDeterministicSuffix(textContent, fileName) formattedText := fmt.Sprintf("\n%s\n", deterministicSuffix, quotedFileName, textContent, deterministicSuffix) return &OpenAIMessageContent{ @@ -419,12 +424,7 @@ func convertFileAIMessagePart(part uctypes.AIMessagePart) (*OpenAIMessageContent encodedDirName := strings.ReplaceAll(directoryName, `"`, """) quotedDirName := strconv.Quote(encodedDirName) - // Generate deterministic suffix from content hash for prompt caching - hasher := sha256.New() - hasher.Write([]byte(jsonContent)) - hasher.Write([]byte(directoryName)) - hash := hasher.Sum(nil) - deterministicSuffix := hex.EncodeToString(hash)[:8] + deterministicSuffix := generateDeterministicSuffix(jsonContent, directoryName) formattedText := fmt.Sprintf("\n%s\n", deterministicSuffix, quotedDirName, jsonContent, deterministicSuffix) return &OpenAIMessageContent{ From 43b638318054766aaf3e08c4177b1bd4977ecbdd Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 14:39:43 -0800 Subject: [PATCH 3/5] added getHomeDir() to electron api... --- emain/emain-platform.ts | 3 +++ emain/preload.ts | 1 + frontend/types/custom.d.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/emain/emain-platform.ts b/emain/emain-platform.ts index 714316b93e..86b49a07e2 100644 --- a/emain/emain-platform.ts +++ b/emain/emain-platform.ts @@ -193,6 +193,9 @@ ipcMain.on("get-data-dir", (event) => { ipcMain.on("get-config-dir", (event) => { event.returnValue = getWaveConfigDir(); }); +ipcMain.on("get-home-dir", (event) => { + event.returnValue = app.getPath("home"); +}); /** * Gets the value of the XDG_CURRENT_DESKTOP environment variable. If ORIGINAL_XDG_CURRENT_DESKTOP is set, it will be returned instead. diff --git a/emain/preload.ts b/emain/preload.ts index bdde36096f..cc9af57031 100644 --- a/emain/preload.ts +++ b/emain/preload.ts @@ -12,6 +12,7 @@ contextBridge.exposeInMainWorld("api", { getHostName: () => ipcRenderer.sendSync("get-host-name"), getDataDir: () => ipcRenderer.sendSync("get-data-dir"), getConfigDir: () => ipcRenderer.sendSync("get-config-dir"), + getHomeDir: () => ipcRenderer.sendSync("get-home-dir"), getAboutModalDetails: () => ipcRenderer.sendSync("get-about-modal-details"), getDocsiteUrl: () => ipcRenderer.sendSync("get-docsite-url"), getWebviewPreload: () => ipcRenderer.sendSync("get-webview-preload"), diff --git a/frontend/types/custom.d.ts b/frontend/types/custom.d.ts index 3c5c26650a..23c51a9b95 100644 --- a/frontend/types/custom.d.ts +++ b/frontend/types/custom.d.ts @@ -83,6 +83,7 @@ declare global { getHostName: () => string; // get-host-name getDataDir: () => string; // get-data-dir getConfigDir: () => string; // get-config-dir + getHomeDir: () => string; // get-home-dir getWebviewPreload: () => string; // get-webview-preload getAboutModalDetails: () => AboutModalDetails; // get-about-modal-details getDocsiteUrl: () => string; // get-docsite-url From 9d3549b6c4abc44af6b22983845375ee189eb8d9 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 14:42:40 -0800 Subject: [PATCH 4/5] fix regression in schema endpoints around config schemas... --- .../app/view/codeeditor/schemaendpoints.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/frontend/app/view/codeeditor/schemaendpoints.ts b/frontend/app/view/codeeditor/schemaendpoints.ts index dd6068c477..2c1cac4953 100644 --- a/frontend/app/view/codeeditor/schemaendpoints.ts +++ b/frontend/app/view/codeeditor/schemaendpoints.ts @@ -10,11 +10,33 @@ type EndpointInfo = { schema: object; }; +function prependWildcard(path: string): string { + return path.startsWith("/") ? `*${path}` : `*/${path}`; +} + +function convertToTildePath(absolutePath: string): string { + const homeDir = getApi().getHomeDir(); + if (absolutePath.startsWith(homeDir)) { + return "~" + absolutePath.slice(homeDir.length); + } + return absolutePath; +} + +function makeConfigPathMatches(suffix: string): Array { + const configPath = `${getApi().getConfigDir()}${suffix}`; + const tildePath = convertToTildePath(configPath); + const paths = [configPath, prependWildcard(configPath)]; + if (tildePath !== configPath) { + paths.push(prependWildcard(tildePath)); + } + return paths; +} + const allFilepaths: Map> = new Map(); -allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, [`${getApi().getConfigDir()}/settings.json`]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, [`${getApi().getConfigDir()}/connections.json`]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, [`${getApi().getConfigDir()}/presets/ai.json`]); -allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, [`${getApi().getConfigDir()}/widgets.json`]); +allFilepaths.set(`${getWebServerEndpoint()}/schema/settings.json`, makeConfigPathMatches("/settings.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/connections.json`, makeConfigPathMatches("/connections.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/aipresets.json`, makeConfigPathMatches("/presets/ai.json")); +allFilepaths.set(`${getWebServerEndpoint()}/schema/widgets.json`, makeConfigPathMatches("/widgets.json")); async function getSchemaEndpointInfo(endpoint: string): Promise { let schema: Object; From 5676f99f3f0fc6f73867e9feefc710f1c4cfca41 Mon Sep 17 00:00:00 2001 From: sawka Date: Mon, 3 Nov 2025 14:53:12 -0800 Subject: [PATCH 5/5] add tilde path as well (without glob) --- frontend/app/view/codeeditor/schemaendpoints.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/app/view/codeeditor/schemaendpoints.ts b/frontend/app/view/codeeditor/schemaendpoints.ts index 2c1cac4953..5056f7121e 100644 --- a/frontend/app/view/codeeditor/schemaendpoints.ts +++ b/frontend/app/view/codeeditor/schemaendpoints.ts @@ -27,6 +27,7 @@ function makeConfigPathMatches(suffix: string): Array { const tildePath = convertToTildePath(configPath); const paths = [configPath, prependWildcard(configPath)]; if (tildePath !== configPath) { + paths.push(tildePath); paths.push(prependWildcard(tildePath)); } return paths;