Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/azzab_opencodex-desktop.json
T

1 line
299 KiB
JSON

{"repo": "azzab/opencodex-desktop", "files": {"kun/config.example.json": {"ab1332c56336d43f0780fc6a9fa1fe66582bb7e9": {"t": 1785746283.922343, "n": 4295, "h": "a6159f2783cefef1", "c": "{\n \"serve\": {\n \"host\": \"127.0.0.1\",\n \"port\": 18999,\n \"dataDir\": \"~/.opencodex/kun\",\n \"runtimeToken\": \"\",\n \"apiKey\": \"\",\n \"baseUrl\": \"https://api.deepseek.com/beta\",\n \"model\": \"deepseek-v4-pro\",\n \"approvalPolicy\": \"auto\",\n \"sandboxMode\": \"workspace-write\",\n \"tokenEconomyMode\": false,\n \"tokenEconomy\": {\n \"enabled\": false,\n \"compressToolDescriptions\": true,\n \"compressToolResults\": true,\n \"conciseResponses\": true,\n \"historyHygiene\": {\n \"maxToolResultLines\": 320,\n \"maxToolResultBytes\": 32768,\n \"maxToolResultTokens\": 8000,\n \"maxToolArgumentStringBytes\": 8192,\n \"maxToolArgumentStringTokens\": 2000,\n \"maxArrayItems\": 80\n }\n },\n \"insecure\": false,\n \"storage\": {\n \"backend\": \"hybrid\"\n }\n },\n \"contextCompaction\": {\n \"defaultSoftThreshold\": 16000,\n \"defaultHardThreshold\": 24000,\n \"summaryMode\": \"heuristic\",\n \"summaryTimeoutMs\": 15000,\n \"summaryMaxTokens\": 1200,\n \"summaryInputMaxBytes\": 98304\n },\n \"models\": {\n \"profiles\": {\n \"deepseek-v4-pro\": {\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n },\n \"deepseek-v4-flash\": {\n \"aliases\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n }\n }\n },\n \"capabilities\": {\n \"mcp\": {\n \"enabled\": false,\n \"search\": {\n \"enabled\": false,\n \"mode\": \"auto\",\n \"autoThresholdToolCount\": 24,\n \"topKDefault\": 5,\n \"topKMax\": 10,\n \"minScore\": 0.15,\n \"bm25\": {\n \"k1\": 1.2,\n \"b\": 0.75\n }\n },\n \"servers\": {}\n },\n \"web\": {\n \"enabled\": false,\n \"fetchEnabled\": false,\n \"searchEnabled\": false,\n \"allowDomains\": [],\n \"denyDomains\": []\n },\n \"skills\": {\n \"enabled\": false,\n \"roots\": [],\n \"legacySkillMd\": true\n },\n \"subagents\": {\n \"enabled\": false,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"defaultPreset\": \"research_split\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000,\n \"workflowPresets\": {\n \"review_swarm\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 4,\n \"maxChildRuns\": 8,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 90000\n },\n \"implementation_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 70000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 180000\n },\n \"research_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000\n },\n \"audit_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 150000\n }\n }\n },\n \"attachments\": {\n \"enabled\": false,\n \"maxImageBytes\": 5242880,\n \"maxImageDimension\": 4096,\n \"allowedMimeTypes\": [\"image/png\", \"image/jpeg\", \"image/webp\"],\n \"textFallbackMaxBase64Bytes\": 524288,\n \"textFallbackMaxImageDimension\": 1280,\n \"textFallbackPreferredMimeType\": \"image/webp\"\n },\n \"memory\": {\n \"enabled\": false,\n \"scopes\": [\"user\", \"workspace\", \"project\"],\n \"maxInjectedRecords\": 8\n }\n }\n}\n"}, "7120350c4879848998ad65c19d63e75a7514b4a8": {"t": 1785761429.1754425, "n": 4295, "h": "a6159f2783cefef1", "c": "{\n \"serve\": {\n \"host\": \"127.0.0.1\",\n \"port\": 18999,\n \"dataDir\": \"~/.opencodex/kun\",\n \"runtimeToken\": \"\",\n \"apiKey\": \"\",\n \"baseUrl\": \"https://api.deepseek.com/beta\",\n \"model\": \"deepseek-v4-pro\",\n \"approvalPolicy\": \"auto\",\n \"sandboxMode\": \"workspace-write\",\n \"tokenEconomyMode\": false,\n \"tokenEconomy\": {\n \"enabled\": false,\n \"compressToolDescriptions\": true,\n \"compressToolResults\": true,\n \"conciseResponses\": true,\n \"historyHygiene\": {\n \"maxToolResultLines\": 320,\n \"maxToolResultBytes\": 32768,\n \"maxToolResultTokens\": 8000,\n \"maxToolArgumentStringBytes\": 8192,\n \"maxToolArgumentStringTokens\": 2000,\n \"maxArrayItems\": 80\n }\n },\n \"insecure\": false,\n \"storage\": {\n \"backend\": \"hybrid\"\n }\n },\n \"contextCompaction\": {\n \"defaultSoftThreshold\": 16000,\n \"defaultHardThreshold\": 24000,\n \"summaryMode\": \"heuristic\",\n \"summaryTimeoutMs\": 15000,\n \"summaryMaxTokens\": 1200,\n \"summaryInputMaxBytes\": 98304\n },\n \"models\": {\n \"profiles\": {\n \"deepseek-v4-pro\": {\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n },\n \"deepseek-v4-flash\": {\n \"aliases\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n }\n }\n },\n \"capabilities\": {\n \"mcp\": {\n \"enabled\": false,\n \"search\": {\n \"enabled\": false,\n \"mode\": \"auto\",\n \"autoThresholdToolCount\": 24,\n \"topKDefault\": 5,\n \"topKMax\": 10,\n \"minScore\": 0.15,\n \"bm25\": {\n \"k1\": 1.2,\n \"b\": 0.75\n }\n },\n \"servers\": {}\n },\n \"web\": {\n \"enabled\": false,\n \"fetchEnabled\": false,\n \"searchEnabled\": false,\n \"allowDomains\": [],\n \"denyDomains\": []\n },\n \"skills\": {\n \"enabled\": false,\n \"roots\": [],\n \"legacySkillMd\": true\n },\n \"subagents\": {\n \"enabled\": false,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"defaultPreset\": \"research_split\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000,\n \"workflowPresets\": {\n \"review_swarm\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 4,\n \"maxChildRuns\": 8,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 90000\n },\n \"implementation_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 70000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 180000\n },\n \"research_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000\n },\n \"audit_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 150000\n }\n }\n },\n \"attachments\": {\n \"enabled\": false,\n \"maxImageBytes\": 5242880,\n \"maxImageDimension\": 4096,\n \"allowedMimeTypes\": [\"image/png\", \"image/jpeg\", \"image/webp\"],\n \"textFallbackMaxBase64Bytes\": 524288,\n \"textFallbackMaxImageDimension\": 1280,\n \"textFallbackPreferredMimeType\": \"image/webp\"\n },\n \"memory\": {\n \"enabled\": false,\n \"scopes\": [\"user\", \"workspace\", \"project\"],\n \"maxInjectedRecords\": 8\n }\n }\n}\n"}, "55c6ec8a92f1febd6fb698ad191957901e0aaaaf": {"t": 1785761430.7477114, "n": 4296, "h": "441a73e120684610", "c": "{\n \"serve\": {\n \"host\": \"127.0.0.1\",\n \"port\": 8899,\n \"dataDir\": \"~/.deepseekgui/kun\",\n \"runtimeToken\": \"\",\n \"apiKey\": \"\",\n \"baseUrl\": \"https://api.deepseek.com/beta\",\n \"model\": \"deepseek-v4-pro\",\n \"approvalPolicy\": \"auto\",\n \"sandboxMode\": \"workspace-write\",\n \"tokenEconomyMode\": false,\n \"tokenEconomy\": {\n \"enabled\": false,\n \"compressToolDescriptions\": true,\n \"compressToolResults\": true,\n \"conciseResponses\": true,\n \"historyHygiene\": {\n \"maxToolResultLines\": 320,\n \"maxToolResultBytes\": 32768,\n \"maxToolResultTokens\": 8000,\n \"maxToolArgumentStringBytes\": 8192,\n \"maxToolArgumentStringTokens\": 2000,\n \"maxArrayItems\": 80\n }\n },\n \"insecure\": false,\n \"storage\": {\n \"backend\": \"hybrid\"\n }\n },\n \"contextCompaction\": {\n \"defaultSoftThreshold\": 16000,\n \"defaultHardThreshold\": 24000,\n \"summaryMode\": \"heuristic\",\n \"summaryTimeoutMs\": 15000,\n \"summaryMaxTokens\": 1200,\n \"summaryInputMaxBytes\": 98304\n },\n \"models\": {\n \"profiles\": {\n \"deepseek-v4-pro\": {\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n },\n \"deepseek-v4-flash\": {\n \"aliases\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n }\n }\n },\n \"capabilities\": {\n \"mcp\": {\n \"enabled\": false,\n \"search\": {\n \"enabled\": false,\n \"mode\": \"auto\",\n \"autoThresholdToolCount\": 24,\n \"topKDefault\": 5,\n \"topKMax\": 10,\n \"minScore\": 0.15,\n \"bm25\": {\n \"k1\": 1.2,\n \"b\": 0.75\n }\n },\n \"servers\": {}\n },\n \"web\": {\n \"enabled\": false,\n \"fetchEnabled\": false,\n \"searchEnabled\": false,\n \"allowDomains\": [],\n \"denyDomains\": []\n },\n \"skills\": {\n \"enabled\": false,\n \"roots\": [],\n \"legacySkillMd\": true\n },\n \"subagents\": {\n \"enabled\": false,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"defaultPreset\": \"research_split\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000,\n \"workflowPresets\": {\n \"review_swarm\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 4,\n \"maxChildRuns\": 8,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 90000\n },\n \"implementation_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 2,\n \"maxChildRuns\": 4,\n \"maxTotalChildTokens\": 70000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 180000\n },\n \"research_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 50000,\n \"maxChildCostUsd\": 1,\n \"perAgentTimeoutMs\": 120000\n },\n \"audit_split\": {\n \"enabled\": true,\n \"defaultModel\": \"deepseek-v4-flash\",\n \"maxParallel\": 3,\n \"maxChildRuns\": 6,\n \"maxTotalChildTokens\": 80000,\n \"maxChildCostUsd\": 1.5,\n \"perAgentTimeoutMs\": 150000\n }\n }\n },\n \"attachments\": {\n \"enabled\": false,\n \"maxImageBytes\": 5242880,\n \"maxImageDimension\": 4096,\n \"allowedMimeTypes\": [\"image/png\", \"image/jpeg\", \"image/webp\"],\n \"textFallbackMaxBase64Bytes\": 524288,\n \"textFallbackMaxImageDimension\": 1280,\n \"textFallbackPreferredMimeType\": \"image/webp\"\n },\n \"memory\": {\n \"enabled\": false,\n \"scopes\": [\"user\", \"workspace\", \"project\"],\n \"maxInjectedRecords\": 8\n }\n }\n}\n"}, "faa4db70da8261d37f47e6c62ec23c342c6ed69a": {"t": 1785761432.1371956, "n": 2954, "h": "3f10bdc1a98f4e3f", "c": "{\n \"serve\": {\n \"host\": \"127.0.0.1\",\n \"port\": 8899,\n \"dataDir\": \"~/.deepseekgui/kun\",\n \"runtimeToken\": \"\",\n \"apiKey\": \"\",\n \"baseUrl\": \"https://api.deepseek.com/beta\",\n \"model\": \"deepseek-v4-pro\",\n \"approvalPolicy\": \"auto\",\n \"sandboxMode\": \"workspace-write\",\n \"tokenEconomyMode\": false,\n \"tokenEconomy\": {\n \"enabled\": false,\n \"compressToolDescriptions\": true,\n \"compressToolResults\": true,\n \"conciseResponses\": true,\n \"historyHygiene\": {\n \"maxToolResultLines\": 320,\n \"maxToolResultBytes\": 32768,\n \"maxToolResultTokens\": 8000,\n \"maxToolArgumentStringBytes\": 8192,\n \"maxToolArgumentStringTokens\": 2000,\n \"maxArrayItems\": 80\n }\n },\n \"insecure\": false,\n \"storage\": {\n \"backend\": \"hybrid\"\n }\n },\n \"contextCompaction\": {\n \"defaultSoftThreshold\": 16000,\n \"defaultHardThreshold\": 24000,\n \"summaryMode\": \"heuristic\",\n \"summaryTimeoutMs\": 15000,\n \"summaryMaxTokens\": 1200,\n \"summaryInputMaxBytes\": 98304\n },\n \"models\": {\n \"profiles\": {\n \"deepseek-v4-pro\": {\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n },\n \"deepseek-v4-flash\": {\n \"aliases\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n }\n }\n },\n \"capabilities\": {\n \"mcp\": {\n \"enabled\": false,\n \"search\": {\n \"enabled\": false,\n \"mode\": \"auto\",\n \"autoThresholdToolCount\": 24,\n \"topKDefault\": 5,\n \"topKMax\": 10,\n \"minScore\": 0.15,\n \"bm25\": {\n \"k1\": 1.2,\n \"b\": 0.75\n }\n },\n \"servers\": {}\n },\n \"web\": {\n \"enabled\": false,\n \"fetchEnabled\": false,\n \"searchEnabled\": false,\n \"allowDomains\": [],\n \"denyDomains\": []\n },\n \"skills\": {\n \"enabled\": false,\n \"roots\": [],\n \"legacySkillMd\": true\n },\n \"subagents\": {\n \"enabled\": false,\n \"maxParallel\": 0,\n \"maxChildRuns\": 0\n },\n \"attachments\": {\n \"enabled\": false,\n \"maxImageBytes\": 5242880,\n \"maxImageDimension\": 4096,\n \"allowedMimeTypes\": [\"image/png\", \"image/jpeg\", \"image/webp\"],\n \"textFallbackMaxBase64Bytes\": 524288,\n \"textFallbackMaxImageDimension\": 1280,\n \"textFallbackPreferredMimeType\": \"image/webp\"\n },\n \"memory\": {\n \"enabled\": false,\n \"scopes\": [\"user\", \"workspace\", \"project\"],\n \"maxInjectedRecords\": 8\n }\n }\n}\n"}, "0c60469336ccc97eae89bc09c3500603df88bd16": {"t": 1785761433.3680112, "n": 2960, "h": "da624ded6814ffd2", "c": "{\n \"serve\": {\n \"host\": \"127.0.0.1\",\n \"port\": 8899,\n \"dataDir\": \"~/.deepseekgui/kun\",\n \"runtimeToken\": \"\",\n \"apiKey\": \"\",\n \"baseUrl\": \"https://api.deepseek.com/beta\",\n \"model\": \"deepseek-v4-pro\",\n \"approvalPolicy\": \"on-request\",\n \"sandboxMode\": \"workspace-write\",\n \"tokenEconomyMode\": false,\n \"tokenEconomy\": {\n \"enabled\": false,\n \"compressToolDescriptions\": true,\n \"compressToolResults\": true,\n \"conciseResponses\": true,\n \"historyHygiene\": {\n \"maxToolResultLines\": 320,\n \"maxToolResultBytes\": 32768,\n \"maxToolResultTokens\": 8000,\n \"maxToolArgumentStringBytes\": 8192,\n \"maxToolArgumentStringTokens\": 2000,\n \"maxArrayItems\": 80\n }\n },\n \"insecure\": false,\n \"storage\": {\n \"backend\": \"hybrid\"\n }\n },\n \"contextCompaction\": {\n \"defaultSoftThreshold\": 16000,\n \"defaultHardThreshold\": 24000,\n \"summaryMode\": \"heuristic\",\n \"summaryTimeoutMs\": 15000,\n \"summaryMaxTokens\": 1200,\n \"summaryInputMaxBytes\": 98304\n },\n \"models\": {\n \"profiles\": {\n \"deepseek-v4-pro\": {\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n },\n \"deepseek-v4-flash\": {\n \"aliases\": [\"deepseek-chat\", \"deepseek-reasoner\"],\n \"contextWindowTokens\": 1000000,\n \"contextCompaction\": {\n \"softThreshold\": 980000,\n \"hardThreshold\": 990000\n },\n \"inputModalities\": [\"text\"],\n \"outputModalities\": [\"text\"],\n \"supportsToolCalling\": true,\n \"messageParts\": [\"text\"]\n }\n }\n },\n \"capabilities\": {\n \"mcp\": {\n \"enabled\": false,\n \"search\": {\n \"enabled\": false,\n \"mode\": \"auto\",\n \"autoThresholdToolCount\": 24,\n \"topKDefault\": 5,\n \"topKMax\": 10,\n \"minScore\": 0.15,\n \"bm25\": {\n \"k1\": 1.2,\n \"b\": 0.75\n }\n },\n \"servers\": {}\n },\n \"web\": {\n \"enabled\": false,\n \"fetchEnabled\": false,\n \"searchEnabled\": false,\n \"allowDomains\": [],\n \"denyDomains\": []\n },\n \"skills\": {\n \"enabled\": false,\n \"roots\": [],\n \"legacySkillMd\": true\n },\n \"subagents\": {\n \"enabled\": false,\n \"maxParallel\": 0,\n \"maxChildRuns\": 0\n },\n \"attachments\": {\n \"enabled\": false,\n \"maxImageBytes\": 5242880,\n \"maxImageDimension\": 4096,\n \"allowedMimeTypes\": [\"image/png\", \"image/jpeg\", \"image/webp\"],\n \"textFallbackMaxBase64Bytes\": 524288,\n \"textFallbackMaxImageDimension\": 1280,\n \"textFallbackPreferredMimeType\": \"image/webp\"\n },\n \"memory\": {\n \"enabled\": false,\n \"scopes\": [\"user\", \"workspace\", \"project\"],\n \"maxInjectedRecords\": 8\n }\n }\n}\n"}}, "src/main/services/write-inline-completion-service.test.ts": {"ab1332c56336d43f0780fc6a9fa1fe66582bb7e9": {"t": 1785746520.0576293, "n": 22964, "h": "3b9c7b122a569c25", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n codePromptPrefix: '',\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('OpenCodex Desktop inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('OpenCodex Desktop inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'OpenCodex Desktop',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'OpenCodex Desktop keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('OpenCodex Desktop inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'OpenCodex Desktop',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'OpenCodex Desktop',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "3d7f114890551b5e97f9c5f3be45937862985ef9": {"t": 1785761431.871813, "n": 22964, "h": "3b9c7b122a569c25", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n codePromptPrefix: '',\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('OpenCodex Desktop inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('OpenCodex Desktop inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'OpenCodex Desktop',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'OpenCodex Desktop keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'OpenCodex Desktop keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('OpenCodex Desktop inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'OpenCodex Desktop',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'OpenCodex Desktop',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "c6a1dc3cad44079127354294f2d0d07db36d4c30": {"t": 1785761433.7116637, "n": 22914, "h": "e2ecd974ee304665", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n codePromptPrefix: '',\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "dacbc955a9770141838fcc1193bad40610738cb3": {"t": 1785761435.9755218, "n": 22835, "h": "0fca6b9d6ea15a14", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n codePromptPrefix: '',\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "056cd8d420f1af2b92aa731a9b3b09b303845be1": {"t": 1785761437.5795524, "n": 22888, "h": "02f5dd4e092202ae", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "b88432536f576b0ad8891e6d60d55503a2423537": {"t": 1785761439.463329, "n": 22809, "h": "0ae7a8e3481c90f9", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "0c60469336ccc97eae89bc09c3500603df88bd16": {"t": 1785761441.0080116, "n": 22725, "h": "36f33c1ee7f4561b", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: defaultModelProviderSettings(),\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n apiKey: 'sk-test'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n schedule: defaultScheduleSettings(),\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.kun.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('falls back to the General baseUrl and Kun model when write keeps defaults', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' fallback text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings()\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n settings.write.inlineCompletion.baseUrl = 'https://api.deepseek.com/beta'\n settings.write.inlineCompletion.model = 'deepseek-v4-flash'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-chat'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(url).toContain('/completions')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-chat'\n })\n })\n\n it('uses an explicit flash override when write disables model inheritance', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' explicit flash' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const settings = createSettings({\n inheritModel: false,\n model: 'deepseek-v4-flash'\n })\n settings.provider.baseUrl = 'https://general.example/v1'\n settings.agents.kun.model = 'deepseek-chat'\n\n const result = await requestWriteInlineCompletion(settings, {\n ...createRequest(),\n model: ''\n })\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toContain('https://general.example')\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n\n it('parses JSON action payloads', () => {\n expect(parseWriteInlineAction(JSON.stringify({ kind: 'long', text: 'Continue the paragraph.' }))).toEqual({\n kind: 'long',\n text: 'Continue the paragraph.'\n })\n expect(parseWriteInlineAction(JSON.stringify({ action: 'edit', replacement: 'Rewrite locally.' }), {\n editTarget: {\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite locally.',\n from: 3,\n to: 11,\n original: 'Old text',\n scopeKind: 'paragraph'\n })\n })\n\n it('parses XML-style action wrappers', () => {\n expect(parseWriteInlineAction('<short>next words</short>')).toEqual({\n kind: 'short',\n text: 'next words'\n })\n expect(parseWriteInlineAction('<long>Two sentences.\\nMaybe three.</long>')).toEqual({\n kind: 'long',\n text: 'Two sentences.\\nMaybe three.'\n })\n expect(parseWriteInlineAction('<edit>Replace this scope</edit>', {\n editTarget: {\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Replace this scope',\n from: 12,\n to: 20,\n original: 'old value',\n scopeKind: 'selection'\n })\n })\n\n it('parses labeled plain-text fallbacks', () => {\n expect(parseWriteInlineAction('completion: next sentence')).toEqual({\n kind: 'short',\n text: 'next sentence'\n })\n expect(parseWriteInlineAction('long: A fuller continuation.')).toEqual({\n kind: 'long',\n text: 'A fuller continuation.'\n })\n expect(parseWriteInlineAction('edit: Rewrite this block', {\n editTarget: {\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Rewrite this block',\n from: 1,\n to: 4,\n original: 'old',\n scopeKind: 'paragraph'\n })\n })\n\n it('falls back to the requested mode for unstructured plain text', () => {\n expect(parseWriteInlineAction('Raw continuation text')).toEqual({\n kind: 'short',\n text: 'Raw continuation text'\n })\n expect(parseWriteInlineAction('Raw long continuation', { fallbackKind: 'long' })).toEqual({\n kind: 'long',\n text: 'Raw long continuation'\n })\n expect(parseWriteInlineAction('Raw edit replacement', {\n fallbackKind: 'edit',\n editTarget: {\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Raw edit replacement',\n from: 8,\n to: 15,\n original: 'old text',\n scopeKind: 'selection'\n })\n })\n})\n"}, "cdb2ae09b274f75beee48d6b5a2266514a679689": {"t": 1785761444.0050333, "n": 17905, "h": "765f9ff8e896c80c", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'codewhale',\n agents: {\n codewhale: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.agents.codewhale.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n})\n"}, "af6c07a10fef0f428371bd2e2fa2fe74a5ab7182": {"t": 1785761445.3365643, "n": 17861, "h": "a2ccc1bb27472842", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n parseWriteInlineAction,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'deepseek-runtime',\n deepseek: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n action: {\n kind: 'short',\n text: ' only a test'\n },\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n const body = JSON.parse(String(init.body)) as { prompt: string; suffix: string; max_tokens: number }\n expect(body).toMatchObject({\n model: 'deepseek-v4-flash',\n suffix: ' a test.',\n max_tokens: 64\n })\n expect(body.prompt).toContain('DeepSeek GUI inline completion')\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<SHORT')\n expect(body.prompt).toContain('<<<PREFIX')\n expect(body.prompt).toContain('<<<SUFFIX')\n expect(body.prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.deepseek.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n suffix: ' a test.',\n responseChars: 0\n })\n expect(debugEntries[0].prompt).toContain('DeepSeek GUI inline completion')\n expect(debugEntries[0].prompt.endsWith('# Draft\\n\\nThis is')).toBe(true)\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('Trigger hint: long')\n expect(body.prompt).toContain('paused for inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('records plain long completions from the FIM request', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA fuller continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings(), {\n ...createRequest(),\n mode: 'long'\n })\n\n expect(result).toMatchObject({\n ok: true,\n completion: '\\n\\nA fuller continuation.',\n action: {\n kind: 'long',\n text: '\\n\\nA fuller continuation.'\n },\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Return only the text to insert at the cursor')\n expect(body.prompt).not.toContain('<<<LONG')\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('Reference snippets from the same writing workspace')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('uses chat completions when the unified request may return an edit action', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n },\n recentEdits: [{\n source: 'user' as const,\n ageMs: 1_200,\n filePath: '/tmp/workspace/draft.md',\n from: 9,\n to: 21,\n deletedText: 'DeepSeek GUI',\n insertedText: 'Write mode',\n beforeContext: '',\n afterContext: ' keeps text editing local.'\n }]\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n completion: 'Write mode keeps text editing local.',\n action: {\n kind: 'edit',\n replacement: 'Write mode keeps text editing local.',\n from: 9,\n to: 47,\n original: 'DeepSeek GUI keeps text editing local.',\n scopeKind: 'paragraph'\n }\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n prompt?: string\n suffix?: string\n max_tokens: number\n thinking?: { type: string }\n }\n expect(body.max_tokens).toBe(320)\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.prompt).toBeUndefined()\n expect(body.suffix).toBeUndefined()\n expect(body.messages[1].content).toContain('Recent local edits in this file')\n expect(body.messages[1].content).toContain('Editable local scope if EDIT is the best action')\n expect(body.messages[1].content).toContain('<<<EDIT_SCOPE')\n })\n\n it('uses chat completions for explicit edit mode even without recent edit signals', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'edit',\n completion: 'Write mode keeps text editing local.'\n })\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/v1/chat/completions')\n const body = JSON.parse(String(init.body)) as {\n messages: Array<{ role: string; content: string }>\n suffix?: string\n thinking?: { type: string }\n }\n expect(body.suffix).toBeUndefined()\n expect(body.thinking).toEqual({ type: 'disabled' })\n expect(body.messages[1].content).toContain('Trigger hint: edit')\n expect(body.messages[1].content).toContain('<<<PREFIX')\n expect(body.messages[1].content).toContain('<<<SUFFIX')\n })\n\n it('does not send DeepSeek thinking controls for custom chat completion models', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({\n choices: [{\n message: {\n content: '<<<EDIT\\nWrite mode keeps text editing local.\\n>>>'\n }\n }]\n }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'gpt-4.1-mini',\n mode: 'edit' as const,\n editCandidate: {\n kind: 'paragraph' as const,\n from: 9,\n to: 47,\n startLine: 3,\n startColumn: 1,\n endLine: 3,\n endColumn: 38,\n original: 'DeepSeek GUI keeps text editing local.'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings({ longMaxTokens: 320 }), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'gpt-4.1-mini',\n mode: 'edit'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { thinking?: { type: string } }\n expect(body.thinking).toBeUndefined()\n })\n\n it('builds the unified action prompt without retrieval snippets when none are supplied', () => {\n const request = createRequest()\n\n const prompt = buildWriteInlineCompletionPrompt(request, null)\n expect(prompt).toContain('DeepSeek GUI inline completion')\n expect(prompt).toContain('<<<PREFIX')\n expect(prompt).toContain('<<<SUFFIX')\n expect(prompt).not.toContain('<<<SHORT')\n expect(prompt).not.toContain('Reference snippets from the same writing workspace')\n expect(prompt.endsWith(request.prefix)).toBe(true)\n })\n})\n\ndescribe('parseWriteInlineAction', () => {\n it('parses TextIDE-style marked short, long, and edit blocks', () => {\n expect(parseWriteInlineAction('<<<SHORT\\n next words\\n>>>')).toEqual({\n kind: 'short',\n text: ' next words'\n })\n expect(parseWriteInlineAction('<<<LONG\\n\\nA fuller continuation.\\n>>>')).toEqual({\n kind: 'long',\n text: '\\nA fuller continuation.'\n })\n expect(parseWriteInlineAction('<<<EDIT\\nWrite mode\\n>>>', {\n editTarget: {\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n }\n })).toEqual({\n kind: 'edit',\n replacement: 'Write mode',\n from: 9,\n to: 21,\n original: 'DeepSeek GUI',\n scopeKind: 'selection'\n })\n })\n\n it('suppresses echoed boundary-marker prompts', () => {\n expect(parseWriteInlineAction('<<<PREFIX\\nThis is\\n>>>\\n<<<SUFFIX\\n a test.\\n>>>')).toEqual({\n kind: 'short',\n text: ''\n })\n })\n})\n"}, "df545ec8b148fda7d563d894d5bf09f79928b307": {"t": 1785761446.6210775, "n": 9711, "h": "d9b55d646b304bfc", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'deepseek-runtime',\n deepseek: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash',\n prompt: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n max_tokens: 64\n })\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Inline completion is disabled.',\n completion: '',\n responseChars: 0\n })\n })\n\n it('records missing API key failures in the debug log', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n const settings = createSettings()\n settings.deepseek.apiKey = ''\n\n const result = await requestWriteInlineCompletion(settings, createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Missing API key for inline completion.' })\n expect(fetchMock).not.toHaveBeenCalled()\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: false,\n errorMessage: 'Missing API key for inline completion.',\n mode: 'short',\n prompt: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n responseChars: 0\n })\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('inline completion mode: long inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('DeepSeek GUI inline completion references')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('leaves the prompt untouched when retrieval has no snippets', () => {\n const request = createRequest()\n\n expect(buildWriteInlineCompletionPrompt(request, null)).toBe(request.prefix)\n })\n})\n"}, "04eb10f65703c8e4f96d7a0fe1921bb0a7bb59b4": {"t": 1785761448.1789684, "n": 8657, "h": "f1825897d1c27729", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n clearWriteInlineCompletionDebugEntries,\n listWriteInlineCompletionDebugEntries,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'deepseek-runtime',\n deepseek: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n clearWriteInlineCompletionDebugEntries()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash',\n prompt: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n max_tokens: 64\n })\n const debugEntries = listWriteInlineCompletionDebugEntries()\n expect(debugEntries).toHaveLength(1)\n expect(debugEntries[0]).toMatchObject({\n ok: true,\n completion: ' only a test',\n mode: 'short',\n model: 'deepseek-v4-flash'\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('inline completion mode: long inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('DeepSeek GUI inline completion references')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('leaves the prompt untouched when retrieval has no snippets', () => {\n const request = createRequest()\n\n expect(buildWriteInlineCompletionPrompt(request, null)).toBe(request.prefix)\n })\n})\n"}, "2cc5f5088b0f091c4c30ad04b192857185aae3a6": {"t": 1785761449.4459243, "n": 8270, "h": "db6cf1973c6bd0c7", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport { mkdir, mkdtemp, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport {\n buildWriteInlineCompletionPrompt,\n requestWriteInlineCompletion\n} from './write-inline-completion-service'\nimport { clearWriteRetrievalCache } from './write-retrieval-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'deepseek-runtime',\n deepseek: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n clearWriteRetrievalCache()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n model: 'deepseek-v4-flash',\n mode: 'short'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash',\n prompt: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n max_tokens: 64\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n })\n\n it('preserves an explicit pro completion model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-pro'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-pro'\n })\n })\n\n it('uses the long-completion prompt and token budget for inspiration mode', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: '\\n\\nA longer continuation.' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n mode: 'long' as const,\n suffix: '',\n context: {\n ...createRequest().context,\n currentLineSuffix: ''\n }\n }\n const result = await requestWriteInlineCompletion(\n createSettings({ longMaxTokens: 320 }),\n request\n )\n\n expect(result).toMatchObject({\n ok: true,\n mode: 'long'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string; max_tokens: number }\n expect(body.max_tokens).toBe(320)\n expect(body.prompt).toContain('inline completion mode: long inspiration')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('adds BM25 retrieval snippets to the FIM prompt when workspace context is available', async () => {\n const workspaceRoot = await mkdtemp(join(tmpdir(), 'ds-gui-write-rag-'))\n await mkdir(join(workspaceRoot, 'notes'), { recursive: true })\n await writeFile(\n join(workspaceRoot, 'notes', 'retrieval.md'),\n [\n '# RAG notes',\n '',\n 'BM25 keyword retrieval keeps inline completion grounded in project terminology.',\n 'Use retrieved snippets as reference-only context for local text completion.'\n ].join('\\n'),\n 'utf8'\n )\n await writeFile(\n join(workspaceRoot, 'draft.md'),\n '# Draft\\n\\nBM25 keyword',\n 'utf8'\n )\n\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' retrieval can improve continuity' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n workspaceRoot,\n currentFilePath: join(workspaceRoot, 'draft.md'),\n prefix: '# Draft\\n\\nBM25 keyword',\n suffix: '',\n context: {\n ...createRequest().context,\n currentLinePrefix: 'BM25 keyword',\n currentLineSuffix: '',\n previousNonEmptyLine: '# Draft'\n },\n preview: {\n local: 'BM25 keyword',\n documentTail: '# Draft BM25 keyword'\n }\n }\n\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({ ok: true })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n const body = JSON.parse(String(init.body)) as { prompt: string }\n expect(body.prompt).toContain('DeepSeek GUI inline completion references')\n expect(body.prompt).toContain('notes/retrieval.md')\n expect(body.prompt).toContain('BM25 keyword retrieval keeps inline completion grounded')\n expect(body.prompt.endsWith(request.prefix)).toBe(true)\n })\n\n it('leaves the prompt untouched when retrieval has no snippets', () => {\n const request = createRequest()\n\n expect(buildWriteInlineCompletionPrompt(request, null)).toBe(request.prefix)\n })\n})\n"}, "a932ba88998872a5a0dd913776ee4c69e6a7ac87": {"t": 1785761450.9207723, "n": 4705, "h": "030cc287206e43ff", "c": "import { afterEach, describe, expect, it, vi } from 'vitest'\nimport {\n defaultClawSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../../shared/app-settings'\nimport type { WriteInlineCompletionRequest } from '../../shared/write-inline-completion'\nimport { requestWriteInlineCompletion } from './write-inline-completion-service'\n\nfunction createSettings(patch: Partial<AppSettingsV1['write']['inlineCompletion']> = {}): AppSettingsV1 {\n const write = defaultWriteSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n agentProvider: 'deepseek-runtime',\n deepseek: {\n binaryPath: '',\n port: 7878,\n autoStart: true,\n apiKey: 'sk-test',\n baseUrl: 'https://api.deepseek.com/beta',\n runtimeToken: '',\n extraCorsOrigins: [],\n approvalPolicy: 'auto',\n sandboxMode: 'workspace-write'\n },\n workspaceRoot: '/tmp/workspace',\n log: {\n enabled: true,\n retentionDays: 2\n },\n notifications: {\n turnComplete: true\n },\n write: {\n ...write,\n inlineCompletion: {\n ...write.inlineCompletion,\n ...patch\n }\n },\n guiUpdate: {\n channel: 'stable'\n },\n claw: defaultClawSettings()\n }\n}\n\nfunction createRequest(): WriteInlineCompletionRequest {\n return {\n prefix: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n currentFilePath: '/tmp/workspace/draft.md',\n cursor: {\n line: 3,\n column: 7\n },\n context: {\n language: 'markdown',\n currentLinePrefix: 'This is',\n currentLineSuffix: ' a test.',\n previousLine: '',\n previousNonEmptyLine: '# Draft',\n nextLine: '',\n indentation: '',\n signals: {\n list: false,\n quote: false,\n heading: false,\n table: false,\n atLineEnd: false,\n endsWithSentencePunctuation: false,\n previousLineEndsWithSentencePunctuation: false,\n prefersNewLineCompletion: false,\n paragraphBreakOpportunity: false\n }\n },\n policy: {\n name: 'precision-inline-v2',\n instruction: 'Return only inserted text.',\n acceptanceCriteria: ['Keep it short.'],\n rejectionCriteria: ['Do not ramble.']\n },\n preview: {\n local: 'This is',\n documentTail: '# Draft This is'\n },\n model: 'deepseek-v4-flash'\n }\n}\n\nafterEach(() => {\n vi.unstubAllGlobals()\n})\n\ndescribe('requestWriteInlineCompletion', () => {\n it('calls DeepSeek FIM completions directly instead of chat completions', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' only a test' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ maxTokens: 64 }), createRequest())\n\n expect(result).toEqual({\n ok: true,\n completion: ' only a test',\n model: 'deepseek-v4-flash'\n })\n expect(fetchMock).toHaveBeenCalledTimes(1)\n const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(url).toBe('https://api.deepseek.com/beta/completions')\n expect(url).not.toContain('/chat/completions')\n expect(init.headers).toMatchObject({\n Authorization: 'Bearer sk-test'\n })\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash',\n prompt: '# Draft\\n\\nThis is',\n suffix: ' a test.',\n max_tokens: 64\n })\n })\n\n it('does not request the API when inline completion is disabled', async () => {\n const fetchMock = vi.fn()\n vi.stubGlobal('fetch', fetchMock)\n\n const result = await requestWriteInlineCompletion(createSettings({ enabled: false }), createRequest())\n\n expect(result).toEqual({ ok: false, message: 'Inline completion is disabled.' })\n expect(fetchMock).not.toHaveBeenCalled()\n })\n\n it('normalizes the legacy completion default to the flash model', async () => {\n const fetchMock = vi.fn(async () =>\n new Response(JSON.stringify({ choices: [{ text: ' flash text' }] }), {\n status: 200,\n headers: { 'Content-Type': 'application/json' }\n })\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const request = {\n ...createRequest(),\n model: 'deepseek-v4-pro'\n }\n const result = await requestWriteInlineCompletion(createSettings(), request)\n\n expect(result).toMatchObject({\n ok: true,\n model: 'deepseek-v4-flash'\n })\n const [, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]\n expect(JSON.parse(String(init.body))).toMatchObject({\n model: 'deepseek-v4-flash'\n })\n })\n})\n"}}, "src/main/upstream-models.test.ts": {"ab1332c56336d43f0780fc6a9fa1fe66582bb7e9": {"t": 1785746527.385549, "n": 8116, "h": "befa675e5790a79a", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n DEFAULT_MODEL_ENDPOINT_FORMAT,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport {\n fetchModelProviderCatalog,\n fetchUpstreamModelIds,\n parseOpenRouterModelCatalog,\n readConfiguredKunModelIds\n} from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: ['custom-provider-model'],\n catalogModels: []\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' },\n codePromptPrefix: ''\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n\n it('maps OpenRouter catalog rows into durable model metadata', () => {\n const catalog = parseOpenRouterModelCatalog({\n data: [{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n context_length: 1047576,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'GPT'\n },\n pricing: {\n prompt: '0.0000004',\n completion: '0.0000016',\n input_cache_read: '0.0000001',\n input_cache_write: '0.0000004'\n },\n supported_parameters: [\n 'tools',\n 'tool_choice',\n 'reasoning',\n 'include_reasoning',\n 'response_format'\n ]\n }]\n })\n\n expect(catalog).toEqual([{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n providerId: 'openrouter',\n contextLength: 1047576,\n tokenizer: 'GPT',\n pricingUsdPerMillion: {\n input: 0.4,\n output: 1.6,\n cacheRead: 0.1,\n cacheWrite: 0.4\n },\n capabilities: {\n inputModalities: ['text'],\n outputModalities: ['text'],\n reasoning: true,\n tools: true,\n recommendedUse: ['coding', 'review', 'research']\n }\n }])\n })\n\n it('fetches OpenRouter models from the first-class catalog endpoint', async () => {\n const calls: Array<{ url: string; headers: Record<string, string> }> = []\n const fetchImpl = async (url: URL | RequestInfo, init?: RequestInit): Promise<Response> => {\n calls.push({\n url: String(url),\n headers: Object.fromEntries(new Headers(init?.headers).entries())\n })\n return new Response(JSON.stringify({\n data: [{\n id: 'anthropic/claude-sonnet-4.5',\n name: 'Anthropic: Claude Sonnet 4.5',\n context_length: 200000,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'Claude'\n },\n pricing: {\n prompt: '0.000003',\n completion: '0.000015'\n },\n supported_parameters: ['tools', 'reasoning']\n }]\n }), {\n status: 200,\n headers: { 'content-type': 'application/json' }\n })\n }\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: []\n },\n fetchImpl,\n nowIso: () => '2026-06-09T00:00:00.000Z'\n })\n\n expect(result).toMatchObject({\n ok: true,\n providerId: 'openrouter',\n catalogUpdatedAt: '2026-06-09T00:00:00.000Z'\n })\n if (result.ok) {\n expect(result.catalogModels[0]).toMatchObject({\n id: 'anthropic/claude-sonnet-4.5',\n providerId: 'openrouter',\n pricingUsdPerMillion: {\n input: 3,\n output: 15\n }\n })\n }\n expect(calls).toEqual([{\n url: 'https://openrouter.ai/api/v1/models',\n headers: expect.objectContaining({\n accept: 'application/json',\n authorization: 'Bearer sk-openrouter-secret'\n })\n }])\n })\n\n it('redacts provider secrets from catalog fetch errors', async () => {\n const fetchImpl = async (): Promise<Response> =>\n new Response('Authorization: Bearer sk-openrouter-secret token=sk-openrouter-secret', {\n status: 401,\n headers: { 'content-type': 'text/plain' }\n })\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: []\n },\n fetchImpl\n })\n\n expect(result).toMatchObject({ ok: false, providerId: 'openrouter' })\n if (!result.ok) {\n expect(result.message).toContain('<redacted>')\n expect(result.message).not.toContain('sk-openrouter-secret')\n }\n })\n})\n"}, "7120350c4879848998ad65c19d63e75a7514b4a8": {"t": 1785761430.1876943, "n": 8116, "h": "befa675e5790a79a", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n DEFAULT_MODEL_ENDPOINT_FORMAT,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport {\n fetchModelProviderCatalog,\n fetchUpstreamModelIds,\n parseOpenRouterModelCatalog,\n readConfiguredKunModelIds\n} from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: ['custom-provider-model'],\n catalogModels: []\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' },\n codePromptPrefix: ''\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n\n it('maps OpenRouter catalog rows into durable model metadata', () => {\n const catalog = parseOpenRouterModelCatalog({\n data: [{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n context_length: 1047576,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'GPT'\n },\n pricing: {\n prompt: '0.0000004',\n completion: '0.0000016',\n input_cache_read: '0.0000001',\n input_cache_write: '0.0000004'\n },\n supported_parameters: [\n 'tools',\n 'tool_choice',\n 'reasoning',\n 'include_reasoning',\n 'response_format'\n ]\n }]\n })\n\n expect(catalog).toEqual([{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n providerId: 'openrouter',\n contextLength: 1047576,\n tokenizer: 'GPT',\n pricingUsdPerMillion: {\n input: 0.4,\n output: 1.6,\n cacheRead: 0.1,\n cacheWrite: 0.4\n },\n capabilities: {\n inputModalities: ['text'],\n outputModalities: ['text'],\n reasoning: true,\n tools: true,\n recommendedUse: ['coding', 'review', 'research']\n }\n }])\n })\n\n it('fetches OpenRouter models from the first-class catalog endpoint', async () => {\n const calls: Array<{ url: string; headers: Record<string, string> }> = []\n const fetchImpl = async (url: URL | RequestInfo, init?: RequestInit): Promise<Response> => {\n calls.push({\n url: String(url),\n headers: Object.fromEntries(new Headers(init?.headers).entries())\n })\n return new Response(JSON.stringify({\n data: [{\n id: 'anthropic/claude-sonnet-4.5',\n name: 'Anthropic: Claude Sonnet 4.5',\n context_length: 200000,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'Claude'\n },\n pricing: {\n prompt: '0.000003',\n completion: '0.000015'\n },\n supported_parameters: ['tools', 'reasoning']\n }]\n }), {\n status: 200,\n headers: { 'content-type': 'application/json' }\n })\n }\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: []\n },\n fetchImpl,\n nowIso: () => '2026-06-09T00:00:00.000Z'\n })\n\n expect(result).toMatchObject({\n ok: true,\n providerId: 'openrouter',\n catalogUpdatedAt: '2026-06-09T00:00:00.000Z'\n })\n if (result.ok) {\n expect(result.catalogModels[0]).toMatchObject({\n id: 'anthropic/claude-sonnet-4.5',\n providerId: 'openrouter',\n pricingUsdPerMillion: {\n input: 3,\n output: 15\n }\n })\n }\n expect(calls).toEqual([{\n url: 'https://openrouter.ai/api/v1/models',\n headers: expect.objectContaining({\n accept: 'application/json',\n authorization: 'Bearer sk-openrouter-secret'\n })\n }])\n })\n\n it('redacts provider secrets from catalog fetch errors', async () => {\n const fetchImpl = async (): Promise<Response> =>\n new Response('Authorization: Bearer sk-openrouter-secret token=sk-openrouter-secret', {\n status: 401,\n headers: { 'content-type': 'text/plain' }\n })\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n endpointFormat: DEFAULT_MODEL_ENDPOINT_FORMAT,\n models: []\n },\n fetchImpl\n })\n\n expect(result).toMatchObject({ ok: false, providerId: 'openrouter' })\n if (!result.ok) {\n expect(result.message).toContain('<redacted>')\n expect(result.message).not.toContain('sk-openrouter-secret')\n }\n })\n})\n"}, "55c6ec8a92f1febd6fb698ad191957901e0aaaaf": {"t": 1785761431.440356, "n": 7916, "h": "3bda8d8da40a4e58", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport {\n fetchModelProviderCatalog,\n fetchUpstreamModelIds,\n parseOpenRouterModelCatalog,\n readConfiguredKunModelIds\n} from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model'],\n catalogModels: []\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' },\n codePromptPrefix: ''\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n\n it('maps OpenRouter catalog rows into durable model metadata', () => {\n const catalog = parseOpenRouterModelCatalog({\n data: [{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n context_length: 1047576,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'GPT'\n },\n pricing: {\n prompt: '0.0000004',\n completion: '0.0000016',\n input_cache_read: '0.0000001',\n input_cache_write: '0.0000004'\n },\n supported_parameters: [\n 'tools',\n 'tool_choice',\n 'reasoning',\n 'include_reasoning',\n 'response_format'\n ]\n }]\n })\n\n expect(catalog).toEqual([{\n id: 'openai/gpt-4.1-mini',\n name: 'OpenAI: GPT-4.1 Mini',\n providerId: 'openrouter',\n contextLength: 1047576,\n tokenizer: 'GPT',\n pricingUsdPerMillion: {\n input: 0.4,\n output: 1.6,\n cacheRead: 0.1,\n cacheWrite: 0.4\n },\n capabilities: {\n inputModalities: ['text'],\n outputModalities: ['text'],\n reasoning: true,\n tools: true,\n recommendedUse: ['coding', 'review', 'research']\n }\n }])\n })\n\n it('fetches OpenRouter models from the first-class catalog endpoint', async () => {\n const calls: Array<{ url: string; headers: Record<string, string> }> = []\n const fetchImpl = async (url: URL | RequestInfo, init?: RequestInit): Promise<Response> => {\n calls.push({\n url: String(url),\n headers: Object.fromEntries(new Headers(init?.headers).entries())\n })\n return new Response(JSON.stringify({\n data: [{\n id: 'anthropic/claude-sonnet-4.5',\n name: 'Anthropic: Claude Sonnet 4.5',\n context_length: 200000,\n architecture: {\n input_modalities: ['text'],\n output_modalities: ['text'],\n tokenizer: 'Claude'\n },\n pricing: {\n prompt: '0.000003',\n completion: '0.000015'\n },\n supported_parameters: ['tools', 'reasoning']\n }]\n }), {\n status: 200,\n headers: { 'content-type': 'application/json' }\n })\n }\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n models: []\n },\n fetchImpl,\n nowIso: () => '2026-06-09T00:00:00.000Z'\n })\n\n expect(result).toMatchObject({\n ok: true,\n providerId: 'openrouter',\n catalogUpdatedAt: '2026-06-09T00:00:00.000Z'\n })\n if (result.ok) {\n expect(result.catalogModels[0]).toMatchObject({\n id: 'anthropic/claude-sonnet-4.5',\n providerId: 'openrouter',\n pricingUsdPerMillion: {\n input: 3,\n output: 15\n }\n })\n }\n expect(calls).toEqual([{\n url: 'https://openrouter.ai/api/v1/models',\n headers: expect.objectContaining({\n accept: 'application/json',\n authorization: 'Bearer sk-openrouter-secret'\n })\n }])\n })\n\n it('redacts provider secrets from catalog fetch errors', async () => {\n const fetchImpl = async (): Promise<Response> =>\n new Response('Authorization: Bearer sk-openrouter-secret token=sk-openrouter-secret', {\n status: 401,\n headers: { 'content-type': 'text/plain' }\n })\n\n const result = await fetchModelProviderCatalog({\n provider: {\n id: 'openrouter',\n name: 'OpenRouter',\n apiKey: 'sk-openrouter-secret',\n baseUrl: 'https://openrouter.ai/api/v1',\n models: []\n },\n fetchImpl\n })\n\n expect(result).toMatchObject({ ok: false, providerId: 'openrouter' })\n if (!result.ok) {\n expect(result.message).toContain('<redacted>')\n expect(result.message).not.toContain('sk-openrouter-secret')\n }\n })\n})\n"}, "c6a1dc3cad44079127354294f2d0d07db36d4c30": {"t": 1785761433.1410575, "n": 3813, "h": "75212f9337bff556", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' },\n codePromptPrefix: ''\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n})\n"}, "dacbc955a9770141838fcc1193bad40610738cb3": {"t": 1785761434.4932613, "n": 3734, "h": "b8583e8530f128da", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' },\n codePromptPrefix: ''\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n})\n"}, "056cd8d420f1af2b92aa731a9b3b09b303845be1": {"t": 1785761436.007246, "n": 3787, "h": "0b52cd1c99ce77a9", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKeyboardShortcuts,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n keyboardShortcuts: defaultKeyboardShortcuts(),\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' }\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n})\n"}, "b88432536f576b0ad8891e6d60d55503a2423537": {"t": 1785761437.384989, "n": 3708, "h": "9ca8b60ab68d071a", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n appBehavior: { openAtLogin: false, startMinimized: false, closeToTray: false },\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' }\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n})\n"}, "a2707a2fd992af158c60b5a0f017e973a4aafb4a": {"t": 1785761440.1095278, "n": 3624, "h": "79abe58fea18c02f", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' }\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n models: {\n profiles: {\n 'deepseek-v4-flash': {\n aliases: ['deepseek-chat', 'deepseek-reasoner']\n }\n }\n }\n }),\n 'utf8'\n )\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n }),\n expect.objectContaining({\n providerId: 'deepseek',\n label: 'DeepSeek',\n modelIds: expect.arrayContaining(['deepseek-chat', 'deepseek-reasoner'])\n })\n ]))\n }\n })\n})\n"}, "0c60469336ccc97eae89bc09c3500603df88bd16": {"t": 1785761441.3559895, "n": 3104, "h": "deea5ba33b8e48c4", "c": "import { mkdir, writeFile } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { mkdtempSync } from 'node:fs'\nimport { describe, expect, it } from 'vitest'\nimport {\n defaultClawSettings,\n defaultKunRuntimeSettings,\n defaultModelProviderSettings,\n defaultScheduleSettings,\n defaultWriteSettings,\n type AppSettingsV1\n} from '../shared/app-settings'\nimport { fetchUpstreamModelIds, readConfiguredKunModelIds } from './upstream-models'\n\nfunction settings(dataDir: string, model = 'settings-model'): AppSettingsV1 {\n const provider = defaultModelProviderSettings()\n return {\n version: 1,\n locale: 'en',\n theme: 'system',\n uiFontScale: 'small',\n provider: {\n ...provider,\n providers: [\n ...provider.providers,\n {\n id: 'custom-provider',\n name: 'Custom Provider',\n apiKey: 'sk-custom',\n baseUrl: 'https://custom.example/v1',\n models: ['custom-provider-model']\n }\n ]\n },\n agents: {\n kun: {\n ...defaultKunRuntimeSettings(),\n dataDir,\n model,\n providerId: 'custom-provider'\n }\n },\n workspaceRoot: '/tmp/workspace',\n log: { enabled: false, retentionDays: 7 },\n notifications: { turnComplete: true },\n write: defaultWriteSettings(),\n claw: defaultClawSettings(),\n schedule: defaultScheduleSettings(),\n guiUpdate: { channel: 'stable' }\n }\n}\n\ndescribe('upstream model picker list', () => {\n it('includes Kun config model profiles, aliases, and the configured agent model', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n await mkdir(dataDir, { recursive: true })\n await writeFile(\n join(dataDir, 'config.json'),\n JSON.stringify({\n contextCompaction: {\n modelProfiles: {\n 'legacy-model': {}\n }\n },\n models: {\n profiles: {\n 'custom-model': {\n aliases: ['vendor/custom-model']\n }\n }\n }\n }),\n 'utf8'\n )\n\n const ids = await readConfiguredKunModelIds(settings(dataDir))\n\n expect(ids).toEqual(expect.arrayContaining([\n 'auto',\n 'deepseek-v4-pro',\n 'deepseek-v4-flash',\n 'settings-model',\n 'legacy-model',\n 'custom-model',\n 'vendor/custom-model'\n ]))\n })\n\n it('falls back to configured model ids when upstream cannot be queried', async () => {\n const dataDir = mkdtempSync(join(tmpdir(), 'deepseek-gui-models-'))\n const result = await fetchUpstreamModelIds(settings(dataDir, 'local-only-model'), '')\n\n expect(result).toMatchObject({ ok: true })\n if (result.ok) {\n expect(result.modelIds).toContain('local-only-model')\n expect(result.modelIds).toContain('custom-provider-model')\n expect(result.modelGroups).toEqual(expect.arrayContaining([\n expect.objectContaining({\n providerId: 'custom-provider',\n label: 'Custom Provider',\n modelIds: expect.arrayContaining(['custom-provider-model'])\n })\n ]))\n }\n })\n})\n"}}}}