@mazees/mark 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- package/src/shared/parsers/mark-tag-parser.js +275 -0
|
@@ -0,0 +1,956 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek Web RPC Engine for MARK Node.js Core
|
|
3
|
+
* Reverse-engineered chat.deepseek.com client with native WASM PoW Solver
|
|
4
|
+
*/
|
|
5
|
+
import https from 'https'
|
|
6
|
+
import fs from 'fs'
|
|
7
|
+
import path from 'path'
|
|
8
|
+
import zlib from 'zlib'
|
|
9
|
+
import crypto from 'crypto'
|
|
10
|
+
import { fileURLToPath } from 'url'
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
13
|
+
const __dirname = path.dirname(__filename)
|
|
14
|
+
|
|
15
|
+
export const DEEPSEEK_WEB_MODELS = {
|
|
16
|
+
'deepseek-chat': {
|
|
17
|
+
modelType: 'default',
|
|
18
|
+
thinking: false,
|
|
19
|
+
search: false,
|
|
20
|
+
name: 'deepseek-chat',
|
|
21
|
+
desc: 'DeepSeek-V3/Chat model cepat & responsif'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BASE_HOST = 'chat.deepseek.com'
|
|
26
|
+
|
|
27
|
+
let wasmInstanceCache = null
|
|
28
|
+
const activeSessionCache = new Map()
|
|
29
|
+
const utilitySessionCache = new Map()
|
|
30
|
+
const sessionStateMap = new Map() // sessionId -> { lastMessageId, turnCount, createdAt, lastUsedAt }
|
|
31
|
+
let lastUsedToken = null
|
|
32
|
+
|
|
33
|
+
export function getSessionCacheKey(token, markSessionId = '1', isSmallTask = false) {
|
|
34
|
+
const prefix = isSmallTask ? 'util_' : 'sess_'
|
|
35
|
+
return `${token}:${prefix}${markSessionId || '1'}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function ensureTokenSession(token) {
|
|
39
|
+
// Jika token berubah, clear session cache lama
|
|
40
|
+
if (lastUsedToken !== token) {
|
|
41
|
+
if (lastUsedToken) {
|
|
42
|
+
clearDeepSeekSession(lastUsedToken)
|
|
43
|
+
}
|
|
44
|
+
lastUsedToken = token
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function clearDeepSeekSession(token = null, markSessionId = null) {
|
|
49
|
+
if (token) {
|
|
50
|
+
if (markSessionId) {
|
|
51
|
+
for (const isSmall of [false, true]) {
|
|
52
|
+
const cache = isSmall ? utilitySessionCache : activeSessionCache
|
|
53
|
+
const key = getSessionCacheKey(token, markSessionId, isSmall)
|
|
54
|
+
const sId = cache.get(key)
|
|
55
|
+
if (sId) sessionStateMap.delete(sId)
|
|
56
|
+
cache.delete(key)
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
for (const [key, sId] of [...activeSessionCache.entries()]) {
|
|
60
|
+
if (key.startsWith(`${token}:`)) {
|
|
61
|
+
sessionStateMap.delete(sId)
|
|
62
|
+
activeSessionCache.delete(key)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
for (const [key, sId] of [...utilitySessionCache.entries()]) {
|
|
66
|
+
if (key.startsWith(`${token}:`)) {
|
|
67
|
+
sessionStateMap.delete(sId)
|
|
68
|
+
utilitySessionCache.delete(key)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
activeSessionCache.clear()
|
|
74
|
+
utilitySessionCache.clear()
|
|
75
|
+
sessionStateMap.clear()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getSessionState(token, isSmallTask = false, markSessionId = '1') {
|
|
80
|
+
const cache = isSmallTask ? utilitySessionCache : activeSessionCache
|
|
81
|
+
const key = getSessionCacheKey(token, markSessionId, isSmallTask)
|
|
82
|
+
const sessionId = cache.get(key)
|
|
83
|
+
if (!sessionId) return null
|
|
84
|
+
return sessionStateMap.get(sessionId) || null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Inisialisasi WebAssembly PoW Solver bawaan DeepSeek
|
|
89
|
+
*/
|
|
90
|
+
async function getWasmSolver(wasmBuffer = null) {
|
|
91
|
+
if (wasmInstanceCache) return wasmInstanceCache
|
|
92
|
+
|
|
93
|
+
let buf = wasmBuffer
|
|
94
|
+
if (!buf) {
|
|
95
|
+
const localWasmPaths = [
|
|
96
|
+
path.resolve(__dirname, '../bin/sha3_wasm_bg.wasm'),
|
|
97
|
+
path.resolve(__dirname, '../assets/sha3_wasm_bg.wasm'),
|
|
98
|
+
path.resolve(__dirname, '../../../resources/sha3_wasm_bg.wasm')
|
|
99
|
+
]
|
|
100
|
+
for (const p of localWasmPaths) {
|
|
101
|
+
if (fs.existsSync(p)) {
|
|
102
|
+
try {
|
|
103
|
+
const fileBuf = fs.readFileSync(p)
|
|
104
|
+
buf = fileBuf.buffer.slice(fileBuf.byteOffset, fileBuf.byteOffset + fileBuf.byteLength)
|
|
105
|
+
break
|
|
106
|
+
} catch {
|
|
107
|
+
// ignore error reading local wasm
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!buf) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
"Berkas WASM DeepSeek ('src/server/bin/sha3_wasm_bg.wasm') tidak ditemukan di disk lokal."
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const wasmModule = await WebAssembly.instantiate(buf, {})
|
|
120
|
+
const exp = wasmModule.instance.exports
|
|
121
|
+
|
|
122
|
+
function writeStr(text) {
|
|
123
|
+
const encoder = new TextEncoder()
|
|
124
|
+
const data = encoder.encode(text)
|
|
125
|
+
const ptr = exp.__wbindgen_export_0(data.length, 1)
|
|
126
|
+
const mem = new Uint8Array(exp.memory.buffer)
|
|
127
|
+
mem.set(data, ptr)
|
|
128
|
+
return [ptr, data.length]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function solve(challenge, prefix, difficulty) {
|
|
132
|
+
const retptr = exp.__wbindgen_add_to_stack_pointer(-16)
|
|
133
|
+
try {
|
|
134
|
+
const [cPtr, cLen] = writeStr(challenge)
|
|
135
|
+
const [pPtr, pLen] = writeStr(prefix)
|
|
136
|
+
exp.wasm_solve(retptr, cPtr, cLen, pPtr, pLen, Number(difficulty))
|
|
137
|
+
|
|
138
|
+
const view = new DataView(exp.memory.buffer)
|
|
139
|
+
const status = view.getInt32(retptr, true)
|
|
140
|
+
const value = view.getFloat64(retptr + 8, true)
|
|
141
|
+
if (status === 0) return null
|
|
142
|
+
return Math.floor(value)
|
|
143
|
+
} finally {
|
|
144
|
+
exp.__wbindgen_add_to_stack_pointer(16)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
wasmInstanceCache = { solve }
|
|
149
|
+
return wasmInstanceCache
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Utility HTTP POST JSON request
|
|
154
|
+
*/
|
|
155
|
+
function httpPostJson(path, headers, bodyObj) {
|
|
156
|
+
return new Promise((resolve, reject) => {
|
|
157
|
+
const postData = JSON.stringify(bodyObj)
|
|
158
|
+
const options = {
|
|
159
|
+
hostname: BASE_HOST,
|
|
160
|
+
port: 443,
|
|
161
|
+
path: path,
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
'content-type': 'application/json',
|
|
165
|
+
'content-length': Buffer.byteLength(postData),
|
|
166
|
+
...headers
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const req = https.request(options, (res) => {
|
|
171
|
+
let data = ''
|
|
172
|
+
res.on('data', (chunk) => (data += chunk.toString()))
|
|
173
|
+
res.on('end', () => {
|
|
174
|
+
try {
|
|
175
|
+
const json = JSON.parse(data)
|
|
176
|
+
resolve(json)
|
|
177
|
+
} catch {
|
|
178
|
+
resolve(data)
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
req.on('error', reject)
|
|
184
|
+
req.write(postData)
|
|
185
|
+
req.end()
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Utility HTTP GET JSON request
|
|
191
|
+
*/
|
|
192
|
+
function httpGetJson(path, headers) {
|
|
193
|
+
return new Promise((resolve, reject) => {
|
|
194
|
+
const options = {
|
|
195
|
+
hostname: BASE_HOST,
|
|
196
|
+
port: 443,
|
|
197
|
+
path: path,
|
|
198
|
+
method: 'GET',
|
|
199
|
+
headers: {
|
|
200
|
+
...headers
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const req = https.request(options, (res) => {
|
|
205
|
+
let data = ''
|
|
206
|
+
res.on('data', (chunk) => (data += chunk.toString()))
|
|
207
|
+
res.on('end', () => {
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(data)
|
|
210
|
+
resolve(json)
|
|
211
|
+
} catch {
|
|
212
|
+
resolve(data)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
req.on('error', reject)
|
|
218
|
+
req.end()
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Header standar untuk menyerupai peramban web asli
|
|
224
|
+
*/
|
|
225
|
+
function getBaseHeaders(token, userAgent = null) {
|
|
226
|
+
return {
|
|
227
|
+
authorization: `Bearer ${token}`,
|
|
228
|
+
accept: '*/*',
|
|
229
|
+
'accept-language': 'en-US,en;q=0.9,id;q=0.8',
|
|
230
|
+
'user-agent':
|
|
231
|
+
userAgent ||
|
|
232
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
|
|
233
|
+
origin: `https://${BASE_HOST}`,
|
|
234
|
+
referer: `https://${BASE_HOST}/`,
|
|
235
|
+
'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"',
|
|
236
|
+
'sec-ch-ua-mobile': '?0',
|
|
237
|
+
'sec-ch-ua-platform': '"Windows"',
|
|
238
|
+
'sec-fetch-dest': 'empty',
|
|
239
|
+
'sec-fetch-mode': 'cors',
|
|
240
|
+
'sec-fetch-site': 'same-origin',
|
|
241
|
+
'x-app-version': '2.0.2',
|
|
242
|
+
'x-client-version': '2.0.2',
|
|
243
|
+
'x-client-platform': 'web',
|
|
244
|
+
'x-client-locale': 'en_US',
|
|
245
|
+
'x-client-bundle-id': 'com.deepseek.chat'
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Ekstraksi envelope biz_data DeepSeek
|
|
251
|
+
*/
|
|
252
|
+
function unwrapBizData(resJson) {
|
|
253
|
+
if (!resJson || resJson.code !== 0) {
|
|
254
|
+
throw new Error(`DeepSeek API Error: ${resJson?.msg || JSON.stringify(resJson)}`)
|
|
255
|
+
}
|
|
256
|
+
const biz = resJson.data?.biz_data
|
|
257
|
+
if (!biz) throw new Error(`Envelope biz_data tidak ditemukan: ${JSON.stringify(resJson)}`)
|
|
258
|
+
return biz
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Utility HTTP POST multipart/form-data untuk upload berkas mentah.
|
|
263
|
+
*/
|
|
264
|
+
function httpPostMultipart(reqPath, headers, fields, fileField) {
|
|
265
|
+
return new Promise((resolve, reject) => {
|
|
266
|
+
const boundary = '----MarkAgentBoundary' + crypto.randomBytes(16).toString('hex')
|
|
267
|
+
const CRLF = '\r\n'
|
|
268
|
+
const chunks = []
|
|
269
|
+
|
|
270
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
271
|
+
chunks.push(
|
|
272
|
+
Buffer.from(
|
|
273
|
+
`--${boundary}${CRLF}` +
|
|
274
|
+
`Content-Disposition: form-data; name="${key}"${CRLF}${CRLF}` +
|
|
275
|
+
`${value}${CRLF}`
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
chunks.push(
|
|
281
|
+
Buffer.from(
|
|
282
|
+
`--${boundary}${CRLF}` +
|
|
283
|
+
`Content-Disposition: form-data; name="${fileField.fieldName}"; filename="${fileField.filename}"${CRLF}` +
|
|
284
|
+
`Content-Type: ${fileField.mimeType}${CRLF}${CRLF}`
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
chunks.push(fileField.buffer)
|
|
288
|
+
chunks.push(Buffer.from(CRLF))
|
|
289
|
+
chunks.push(Buffer.from(`--${boundary}--${CRLF}`))
|
|
290
|
+
|
|
291
|
+
const body = Buffer.concat(chunks)
|
|
292
|
+
|
|
293
|
+
const options = {
|
|
294
|
+
hostname: BASE_HOST,
|
|
295
|
+
port: 443,
|
|
296
|
+
path: reqPath,
|
|
297
|
+
method: 'POST',
|
|
298
|
+
headers: {
|
|
299
|
+
...headers,
|
|
300
|
+
'content-type': `multipart/form-data; boundary=${boundary}`,
|
|
301
|
+
'content-length': body.length
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const req = https.request(options, (res) => {
|
|
306
|
+
let data = ''
|
|
307
|
+
res.on('data', (chunk) => (data += chunk.toString()))
|
|
308
|
+
res.on('end', () => {
|
|
309
|
+
try {
|
|
310
|
+
resolve({ statusCode: res.statusCode, json: JSON.parse(data) })
|
|
311
|
+
} catch {
|
|
312
|
+
resolve({ statusCode: res.statusCode, json: null, raw: data })
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
req.on('error', reject)
|
|
318
|
+
req.write(body)
|
|
319
|
+
req.end()
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Upload gambar mentah ke DeepSeek Web RPC.
|
|
325
|
+
* Mendukung Buffer, Base64 data URL, atau path file lokal.
|
|
326
|
+
*/
|
|
327
|
+
export async function uploadImageFile(token, imagePathOrBuffer, filename = 'image.png') {
|
|
328
|
+
let buffer
|
|
329
|
+
if (Buffer.isBuffer(imagePathOrBuffer)) {
|
|
330
|
+
buffer = imagePathOrBuffer
|
|
331
|
+
} else if (typeof imagePathOrBuffer === 'string') {
|
|
332
|
+
if (imagePathOrBuffer.startsWith('data:')) {
|
|
333
|
+
const commaIdx = imagePathOrBuffer.indexOf(',')
|
|
334
|
+
const headerPart = imagePathOrBuffer.slice(0, commaIdx)
|
|
335
|
+
const base64Data = imagePathOrBuffer.slice(commaIdx + 1)
|
|
336
|
+
buffer = Buffer.from(base64Data, 'base64')
|
|
337
|
+
const matchMime = headerPart.match(/data:([^;]+)/)
|
|
338
|
+
if (matchMime) {
|
|
339
|
+
const ext = matchMime[1].split('/')[1] || 'png'
|
|
340
|
+
filename = `upload_${Date.now()}.${ext}`
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
buffer = fs.readFileSync(imagePathOrBuffer)
|
|
344
|
+
filename = path.basename(imagePathOrBuffer)
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
throw new Error(
|
|
348
|
+
'Format berkas gambar tidak valid (harus Buffer, Base64 data URL, atau file path).'
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const ext = path.extname(filename).toLowerCase()
|
|
353
|
+
const mimeMap = {
|
|
354
|
+
'.png': 'image/png',
|
|
355
|
+
'.jpg': 'image/jpeg',
|
|
356
|
+
'.jpeg': 'image/jpeg',
|
|
357
|
+
'.webp': 'image/webp',
|
|
358
|
+
'.gif': 'image/gif'
|
|
359
|
+
}
|
|
360
|
+
const mimeType = mimeMap[ext] || 'image/png'
|
|
361
|
+
const powHeader = await generatePowHeader(token, '/api/v0/file/upload_file')
|
|
362
|
+
const headers = {
|
|
363
|
+
...getBaseHeaders(token),
|
|
364
|
+
'x-ds-pow-response': powHeader
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const { statusCode, json, raw } = await httpPostMultipart(
|
|
368
|
+
'/api/v0/file/upload_file',
|
|
369
|
+
headers,
|
|
370
|
+
{},
|
|
371
|
+
{ fieldName: 'file', filename, mimeType, buffer }
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
if (statusCode !== 200 || !json || json.code !== 0) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`Upload gambar ke DeepSeek gagal (${statusCode}): ${JSON.stringify(json || raw)}`
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const biz = unwrapBizData(json)
|
|
381
|
+
const fileId = biz.file_id || biz.id || biz.file?.id
|
|
382
|
+
if (!fileId) {
|
|
383
|
+
throw new Error(`Tidak menemukan file_id pada respon upload: ${JSON.stringify(biz)}`)
|
|
384
|
+
}
|
|
385
|
+
return { fileId, raw: biz }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Fork file ke target task 'vision' jika diperlukan oleh DeepSeek.
|
|
390
|
+
* Menggunakan fallback ke file_id awal bila endpoint fork gagal/tidak dibutuhkan.
|
|
391
|
+
*/
|
|
392
|
+
export async function forkFileToVision(token, fileId) {
|
|
393
|
+
const headers = getBaseHeaders(token)
|
|
394
|
+
try {
|
|
395
|
+
const res = await httpPostJson('/api/v0/file/fork_file_task', headers, {
|
|
396
|
+
file_id: fileId,
|
|
397
|
+
target_type: 'vision'
|
|
398
|
+
})
|
|
399
|
+
if (res?.code === 0 && res.data?.biz_data) {
|
|
400
|
+
const biz = res.data.biz_data
|
|
401
|
+
return biz.file_id || biz.id || fileId
|
|
402
|
+
}
|
|
403
|
+
return fileId
|
|
404
|
+
} catch (err) {
|
|
405
|
+
console.warn('[DeepSeek-Web] forkFileToVision fallback ke fileId awal:', err?.message || err)
|
|
406
|
+
return fileId
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Polling status parsing/readiness file di DeepSeek Web via /api/v0/file/fetch_files.
|
|
412
|
+
*/
|
|
413
|
+
export async function waitForFileReady(
|
|
414
|
+
token,
|
|
415
|
+
fileId,
|
|
416
|
+
{ maxAttempts = 30, intervalMs = 1000, settleMs = 1200 } = {}
|
|
417
|
+
) {
|
|
418
|
+
const headers = getBaseHeaders(token)
|
|
419
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
420
|
+
try {
|
|
421
|
+
const res = await httpGetJson(
|
|
422
|
+
`/api/v0/file/fetch_files?file_ids=${encodeURIComponent(fileId)}`,
|
|
423
|
+
headers
|
|
424
|
+
)
|
|
425
|
+
const biz = res?.data?.biz_data
|
|
426
|
+
let file = null
|
|
427
|
+
if (Array.isArray(biz)) {
|
|
428
|
+
file = biz.find((f) => f.id === fileId || f.file_id === fileId) || biz[0]
|
|
429
|
+
} else if (Array.isArray(biz?.files)) {
|
|
430
|
+
file = biz.files.find((f) => f.id === fileId || f.file_id === fileId) || biz.files[0]
|
|
431
|
+
} else {
|
|
432
|
+
file = biz
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const status = String(file?.status || '').toUpperCase()
|
|
436
|
+
if (['SUCCESS', 'READY', 'DONE', 'COMPLETED', 'FINISHED', 'OK'].includes(status)) {
|
|
437
|
+
if (settleMs > 0) {
|
|
438
|
+
await new Promise((r) => setTimeout(r, settleMs))
|
|
439
|
+
}
|
|
440
|
+
return file
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (['FAILED', 'ERROR', 'REJECTED', 'CONTENT_EMPTY'].includes(status)) {
|
|
444
|
+
throw new Error(`Pemrosesan berkas DeepSeek gagal (status: ${status})`)
|
|
445
|
+
}
|
|
446
|
+
} catch (err) {
|
|
447
|
+
if (err.message?.includes('Pemrosesan berkas DeepSeek gagal')) {
|
|
448
|
+
throw err
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
await new Promise((r) => setTimeout(r, intervalMs))
|
|
452
|
+
}
|
|
453
|
+
return { id: fileId, status: 'ready_assumed' }
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Generate Header Proof-of-Work (x-ds-pow-response)
|
|
458
|
+
*/
|
|
459
|
+
async function generatePowHeader(token, targetPath = '/api/v0/chat/completion', wasmBuf = null) {
|
|
460
|
+
const solver = await getWasmSolver(wasmBuf)
|
|
461
|
+
const challengeRes = await httpPostJson(
|
|
462
|
+
'/api/v0/chat/create_pow_challenge',
|
|
463
|
+
getBaseHeaders(token),
|
|
464
|
+
{ target_path: targetPath }
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
const challenge = unwrapBizData(challengeRes).challenge
|
|
468
|
+
const prefix = `${challenge.salt}_${challenge.expire_at}_`
|
|
469
|
+
const answer = solver.solve(challenge.challenge, prefix, challenge.difficulty)
|
|
470
|
+
|
|
471
|
+
if (answer === null) {
|
|
472
|
+
throw new Error('Gagal menyelesaikan Proof-of-Work DeepSeek (Challenge kadaluarsa/gagal)')
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const payload = {
|
|
476
|
+
algorithm: challenge.algorithm,
|
|
477
|
+
challenge: challenge.challenge,
|
|
478
|
+
salt: challenge.salt,
|
|
479
|
+
answer: answer,
|
|
480
|
+
signature: challenge.signature,
|
|
481
|
+
target_path: challenge.target_path
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return Buffer.from(JSON.stringify(payload)).toString('base64')
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Membuat chat session ID baru
|
|
489
|
+
*/
|
|
490
|
+
export async function createChatSession(token) {
|
|
491
|
+
const res = await httpPostJson('/api/v0/chat_session/create', getBaseHeaders(token), {})
|
|
492
|
+
const biz = unwrapBizData(res)
|
|
493
|
+
return biz.chat_session.id
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Eksekusi tunggal request ke DeepSeek Web RPC (langsung instan tanpa delay jika normal)
|
|
498
|
+
*/
|
|
499
|
+
async function _executeSingleDeepSeekCall(
|
|
500
|
+
prompt,
|
|
501
|
+
modelName = 'deepseek-chat',
|
|
502
|
+
token = '',
|
|
503
|
+
options = {}
|
|
504
|
+
) {
|
|
505
|
+
// Auto-clear session cache jika token berubah
|
|
506
|
+
ensureTokenSession(token)
|
|
507
|
+
|
|
508
|
+
const isSmallTask = !!options.isSmallTask
|
|
509
|
+
const targetCache = isSmallTask ? utilitySessionCache : activeSessionCache
|
|
510
|
+
const markSessionId = options.sessionId || '1'
|
|
511
|
+
const cacheKey = getSessionCacheKey(token, markSessionId, isSmallTask)
|
|
512
|
+
|
|
513
|
+
let sessionId = targetCache.get(cacheKey)
|
|
514
|
+
let sessionState = sessionId ? sessionStateMap.get(sessionId) : null
|
|
515
|
+
|
|
516
|
+
const now = Date.now()
|
|
517
|
+
const MAX_TURNS_PER_SESSION = 10
|
|
518
|
+
const MAX_IDLE_MS = 2 * 60 * 60 * 1000 // 2 jam
|
|
519
|
+
|
|
520
|
+
// Rotasi otomatis jika melebihi batas turn atau idle > 2 jam
|
|
521
|
+
if (
|
|
522
|
+
sessionId &&
|
|
523
|
+
sessionState &&
|
|
524
|
+
(sessionState.turnCount >= MAX_TURNS_PER_SESSION || now - sessionState.lastUsedAt > MAX_IDLE_MS)
|
|
525
|
+
) {
|
|
526
|
+
console.log(
|
|
527
|
+
`[DeepSeek-Web] Merotasi sesi ${isSmallTask ? '(utility)' : `(${markSessionId})`} (turnCount: ${sessionState.turnCount}). Membuat chat session baru...`
|
|
528
|
+
)
|
|
529
|
+
targetCache.delete(cacheKey)
|
|
530
|
+
sessionStateMap.delete(sessionId)
|
|
531
|
+
sessionId = null
|
|
532
|
+
sessionState = null
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Buat session baru jika tidak ada
|
|
536
|
+
if (!sessionId) {
|
|
537
|
+
sessionId = await createChatSession(token)
|
|
538
|
+
targetCache.set(cacheKey, sessionId)
|
|
539
|
+
sessionState = { lastMessageId: null, turnCount: 0, createdAt: now, lastUsedAt: now }
|
|
540
|
+
sessionStateMap.set(sessionId, sessionState)
|
|
541
|
+
} else if (!sessionState) {
|
|
542
|
+
sessionState = { lastMessageId: null, turnCount: 0, createdAt: now, lastUsedAt: now }
|
|
543
|
+
sessionStateMap.set(sessionId, sessionState)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Tentukan parentMessageId: prioritaskan options, jika undefined ambil dari state chaining
|
|
547
|
+
let parentMessageId = null
|
|
548
|
+
if (options.parentMessageId !== undefined) {
|
|
549
|
+
parentMessageId = options.parentMessageId
|
|
550
|
+
} else if (sessionState?.lastMessageId) {
|
|
551
|
+
parentMessageId = sessionState.lastMessageId
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const { onDelta = null, wasmBuffer = null, refFileIds = [] } = options
|
|
555
|
+
|
|
556
|
+
// Jeda acak manusiawi (jitter delay 350-800ms) untuk menghindari deteksi burst
|
|
557
|
+
const jitterMs = Math.floor(Math.random() * (800 - 350 + 1)) + 350
|
|
558
|
+
await new Promise((resolve) => setTimeout(resolve, jitterMs))
|
|
559
|
+
|
|
560
|
+
const reqModel = (modelName || 'deepseek-chat').toLowerCase()
|
|
561
|
+
let selected = DEEPSEEK_WEB_MODELS[reqModel] || DEEPSEEK_WEB_MODELS['deepseek-chat']
|
|
562
|
+
const powHeader = await generatePowHeader(token, '/api/v0/chat/completion', wasmBuffer)
|
|
563
|
+
|
|
564
|
+
const bodyData = {
|
|
565
|
+
chat_session_id: sessionId,
|
|
566
|
+
parent_message_id: parentMessageId,
|
|
567
|
+
prompt: prompt,
|
|
568
|
+
ref_file_ids: Array.isArray(refFileIds) ? refFileIds : [],
|
|
569
|
+
thinking_enabled: selected.thinking,
|
|
570
|
+
search_enabled: selected.search,
|
|
571
|
+
action: null,
|
|
572
|
+
preempt: false
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (!parentMessageId) {
|
|
576
|
+
bodyData.model_type = selected.modelType
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const payloadStr = JSON.stringify(bodyData)
|
|
580
|
+
const headers = {
|
|
581
|
+
...getBaseHeaders(token),
|
|
582
|
+
accept: 'text/event-stream, */*',
|
|
583
|
+
'cache-control': 'no-cache',
|
|
584
|
+
'content-type': 'application/json',
|
|
585
|
+
'content-length': Buffer.byteLength(payloadStr),
|
|
586
|
+
'x-ds-pow-response': powHeader
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return new Promise((resolve, reject) => {
|
|
590
|
+
const reqOptions = {
|
|
591
|
+
hostname: BASE_HOST,
|
|
592
|
+
port: 443,
|
|
593
|
+
path: '/api/v0/chat/completion',
|
|
594
|
+
method: 'POST',
|
|
595
|
+
headers: headers
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const req = https.request(reqOptions, (res) => {
|
|
599
|
+
const encoding = (res.headers['content-encoding'] || '').toLowerCase()
|
|
600
|
+
console.log(
|
|
601
|
+
`[DeepSeek-Web] Status: ${res.statusCode}, encoding: "${encoding}", type: "${res.headers['content-type']}"`
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
let stream = res
|
|
605
|
+
if (encoding === 'gzip') {
|
|
606
|
+
stream = res.pipe(zlib.createGunzip())
|
|
607
|
+
} else if (encoding === 'br') {
|
|
608
|
+
stream = res.pipe(zlib.createBrotliDecompress())
|
|
609
|
+
} else if (encoding === 'deflate') {
|
|
610
|
+
stream = res.pipe(zlib.createInflate())
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (res.statusCode !== 200) {
|
|
614
|
+
let errBody = ''
|
|
615
|
+
stream.on('data', (d) => (errBody += d.toString()))
|
|
616
|
+
stream.on('end', () => {
|
|
617
|
+
reject(new Error(`DeepSeek Server menolak permintaan (${res.statusCode}): ${errBody}`))
|
|
618
|
+
})
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
let buffer = ''
|
|
623
|
+
let fullContent = ''
|
|
624
|
+
let reasoningContent = ''
|
|
625
|
+
let activePath = null
|
|
626
|
+
let lastReceivedPayload = ''
|
|
627
|
+
let responseMessageId = null
|
|
628
|
+
|
|
629
|
+
const processLine = (line) => {
|
|
630
|
+
const trimmed = line.trim()
|
|
631
|
+
if (!trimmed) return
|
|
632
|
+
|
|
633
|
+
// Skip SSE event lines that are not "data"
|
|
634
|
+
if (trimmed.startsWith('event:')) return
|
|
635
|
+
|
|
636
|
+
let payload = trimmed
|
|
637
|
+
if (trimmed.startsWith('data:')) {
|
|
638
|
+
payload = trimmed.slice(5).trim()
|
|
639
|
+
}
|
|
640
|
+
if (!payload || payload === '[DONE]') return
|
|
641
|
+
lastReceivedPayload = payload
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
const obj = JSON.parse(payload)
|
|
645
|
+
|
|
646
|
+
// Tangkap message_id respon assistant
|
|
647
|
+
if (obj.v?.response?.message_id) {
|
|
648
|
+
responseMessageId = obj.v.response.message_id
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Abaikan frame status seperti response/status: "FINISHED" atau "WIP"
|
|
652
|
+
if (
|
|
653
|
+
obj.p === 'response/status' ||
|
|
654
|
+
obj.p?.includes('status') ||
|
|
655
|
+
obj.v === 'FINISHED' ||
|
|
656
|
+
obj.v === 'WIP'
|
|
657
|
+
) {
|
|
658
|
+
return
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// 0. Deteksi error resmi dari server DeepSeek
|
|
662
|
+
if (obj.click_behavior !== undefined || obj.auto_resume !== undefined) {
|
|
663
|
+
// Jika sudah ada content, ini bukan error - akhir stream normal
|
|
664
|
+
if (fullContent) return
|
|
665
|
+
// Content kosong + click_behavior = session expired
|
|
666
|
+
targetCache.delete(token)
|
|
667
|
+
targetCache.delete(cacheKey)
|
|
668
|
+
if (sessionId) sessionStateMap.delete(sessionId)
|
|
669
|
+
reject(
|
|
670
|
+
new Error(
|
|
671
|
+
'DeepSeek Web session expired atau tidak valid. Session di-clear, silakan coba lagi.'
|
|
672
|
+
)
|
|
673
|
+
)
|
|
674
|
+
return
|
|
675
|
+
}
|
|
676
|
+
if (obj.code !== undefined && obj.code !== 0) {
|
|
677
|
+
const errMsg = obj.msg || obj.message || JSON.stringify(obj)
|
|
678
|
+
const isFrequent =
|
|
679
|
+
errMsg.toLowerCase().includes('frequent') ||
|
|
680
|
+
errMsg.toLowerCase().includes('too many') ||
|
|
681
|
+
errMsg.toLowerCase().includes('terlalu sering')
|
|
682
|
+
if (!isFrequent) {
|
|
683
|
+
targetCache.delete(token)
|
|
684
|
+
targetCache.delete(cacheKey)
|
|
685
|
+
if (sessionId) sessionStateMap.delete(sessionId)
|
|
686
|
+
}
|
|
687
|
+
reject(new Error(`DeepSeek Server Error (${obj.code}): ${errMsg}`))
|
|
688
|
+
return
|
|
689
|
+
}
|
|
690
|
+
if (obj.data?.biz_code !== undefined && obj.data.biz_code !== 0) {
|
|
691
|
+
const muteUntil = obj.data.biz_data?.mute_until
|
|
692
|
+
? ` (sampai ${new Date(obj.data.biz_data.mute_until * 1000).toLocaleTimeString('id-ID')})`
|
|
693
|
+
: ''
|
|
694
|
+
const errMsg = obj.data.biz_msg || `Kode bisnis ${obj.data.biz_code}`
|
|
695
|
+
const isFrequent =
|
|
696
|
+
errMsg.toLowerCase().includes('frequent') ||
|
|
697
|
+
errMsg.toLowerCase().includes('too many') ||
|
|
698
|
+
errMsg.toLowerCase().includes('terlalu sering')
|
|
699
|
+
if (!isFrequent) {
|
|
700
|
+
targetCache.delete(token)
|
|
701
|
+
targetCache.delete(cacheKey)
|
|
702
|
+
if (sessionId) sessionStateMap.delete(sessionId)
|
|
703
|
+
}
|
|
704
|
+
reject(
|
|
705
|
+
new Error(
|
|
706
|
+
`Akun DeepSeek Web kamu sedang dibatasi sementara oleh DeepSeek: "${errMsg}"${muteUntil}. Tunggu beberapa saat atau ganti token akun baru.`
|
|
707
|
+
)
|
|
708
|
+
)
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
if (obj.error || obj.error_msg) {
|
|
712
|
+
const errMsg = obj.error?.message || obj.error_msg || JSON.stringify(obj)
|
|
713
|
+
reject(new Error(`DeepSeek Server Error: ${errMsg}`))
|
|
714
|
+
return
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const v = obj.v
|
|
718
|
+
|
|
719
|
+
// 1. Snapshot Response Frame
|
|
720
|
+
if (v && typeof v === 'object') {
|
|
721
|
+
if (v.response) {
|
|
722
|
+
for (const frag of v.response.fragments || []) {
|
|
723
|
+
if (frag.content) {
|
|
724
|
+
if (frag.type === 'THINKING') {
|
|
725
|
+
reasoningContent = frag.content
|
|
726
|
+
onDelta?.({ type: 'thinking', delta: frag.content, full: reasoningContent })
|
|
727
|
+
} else {
|
|
728
|
+
activePath = 'response/fragments/-1/content'
|
|
729
|
+
// FIX: Gabungkan fragment, jangan overwrite
|
|
730
|
+
if (fullContent && fullContent !== frag.content) {
|
|
731
|
+
fullContent += frag.content
|
|
732
|
+
} else {
|
|
733
|
+
fullContent = frag.content
|
|
734
|
+
}
|
|
735
|
+
onDelta?.({ type: 'content', delta: frag.content, full: fullContent })
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
if (v.content && typeof v.content === 'string') {
|
|
742
|
+
fullContent += v.content
|
|
743
|
+
onDelta?.({ type: 'content', delta: v.content, full: fullContent })
|
|
744
|
+
return
|
|
745
|
+
}
|
|
746
|
+
if (v.thinking_content && typeof v.thinking_content === 'string') {
|
|
747
|
+
reasoningContent += v.thinking_content
|
|
748
|
+
onDelta?.({ type: 'thinking', delta: v.thinking_content, full: reasoningContent })
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// 2. Path-Setting Frame
|
|
754
|
+
if (obj.p) {
|
|
755
|
+
activePath = obj.p
|
|
756
|
+
if (typeof v === 'string') {
|
|
757
|
+
if (activePath.includes('thinking')) {
|
|
758
|
+
reasoningContent += v
|
|
759
|
+
onDelta?.({ type: 'thinking', delta: v, full: reasoningContent })
|
|
760
|
+
} else {
|
|
761
|
+
fullContent += v
|
|
762
|
+
onDelta?.({ type: 'content', delta: v, full: fullContent })
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// 3. Continuous Append Frame
|
|
769
|
+
if (typeof v === 'string') {
|
|
770
|
+
if (activePath && activePath.includes('thinking')) {
|
|
771
|
+
reasoningContent += v
|
|
772
|
+
onDelta?.({ type: 'thinking', delta: v, full: reasoningContent })
|
|
773
|
+
} else {
|
|
774
|
+
fullContent += v
|
|
775
|
+
onDelta?.({ type: 'content', delta: v, full: fullContent })
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
} catch {
|
|
779
|
+
// Abaikan chunk malformed
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
stream.on('data', (chunk) => {
|
|
784
|
+
buffer += chunk.toString()
|
|
785
|
+
const lines = buffer.split('\n')
|
|
786
|
+
buffer = lines.pop()
|
|
787
|
+
|
|
788
|
+
for (const line of lines) {
|
|
789
|
+
processLine(line)
|
|
790
|
+
}
|
|
791
|
+
})
|
|
792
|
+
|
|
793
|
+
stream.on('end', () => {
|
|
794
|
+
if (buffer && buffer.trim()) {
|
|
795
|
+
processLine(buffer.trim())
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Sanitasi trailing status jika ada yang lolos
|
|
799
|
+
if (fullContent) {
|
|
800
|
+
fullContent = fullContent.replace(/\s*(?:FINISHED|FINISH|DONE)\b.*$/i, '').trim()
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Fallback: Jika content kosong tapi thinking terisi, gunakan thinking sebagai jawaban
|
|
804
|
+
if (!fullContent && reasoningContent) {
|
|
805
|
+
fullContent = reasoningContent
|
|
806
|
+
reasoningContent = null
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (!fullContent) {
|
|
810
|
+
console.warn(
|
|
811
|
+
`[DeepSeek-Web] Empty response on end. Last payload:`,
|
|
812
|
+
lastReceivedPayload,
|
|
813
|
+
`Buffer:`,
|
|
814
|
+
buffer
|
|
815
|
+
)
|
|
816
|
+
reject(
|
|
817
|
+
new Error(
|
|
818
|
+
`Gagal mengekstrak teks balasan dari streaming DeepSeek Web. Status: ${res.statusCode}, Tipe: ${res.headers['content-type']}, Respons server: ${lastReceivedPayload || buffer || 'tidak ada data streaming'}`
|
|
819
|
+
)
|
|
820
|
+
)
|
|
821
|
+
return
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Simpan progress chaining sesi jika berhasil
|
|
825
|
+
if (sessionId && sessionStateMap.has(sessionId)) {
|
|
826
|
+
const state = sessionStateMap.get(sessionId)
|
|
827
|
+
if (responseMessageId) {
|
|
828
|
+
state.lastMessageId = responseMessageId
|
|
829
|
+
}
|
|
830
|
+
state.turnCount = (state.turnCount || 0) + 1
|
|
831
|
+
state.lastUsedAt = Date.now()
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
resolve({
|
|
835
|
+
text: fullContent,
|
|
836
|
+
thinking: reasoningContent || null,
|
|
837
|
+
sessionId: sessionId,
|
|
838
|
+
messageId: responseMessageId || null
|
|
839
|
+
})
|
|
840
|
+
})
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
req.on('error', reject)
|
|
844
|
+
req.write(payloadStr)
|
|
845
|
+
req.end()
|
|
846
|
+
})
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Fungsi Utama: Generate respons dari DeepSeek Web RPC dengan auto-retry hingga 50x dan jeda proteksi rate limit
|
|
851
|
+
*
|
|
852
|
+
* @param {string} prompt - Pertanyaan/pesan pengguna
|
|
853
|
+
* @param {string} modelName - 'deepseek-chat' | 'deepseek-reasoner' | 'deepseek-search'
|
|
854
|
+
* @param {string} token - DeepSeek Bearer token (dari localStorage userToken)
|
|
855
|
+
* @param {object} options - Opsi tambahan: sessionId, parentMessageId, onDelta, onStatus, wasmBuffer, maxAttempts, retryDelayMs
|
|
856
|
+
*/
|
|
857
|
+
export async function generateDeepSeekResponse(
|
|
858
|
+
prompt,
|
|
859
|
+
modelName = 'deepseek-chat',
|
|
860
|
+
token = '',
|
|
861
|
+
options = {}
|
|
862
|
+
) {
|
|
863
|
+
if (!token) {
|
|
864
|
+
throw new Error(
|
|
865
|
+
'DeepSeek User Token (Bearer) dibutuhkan. Silakan ambil dari localStorage.userToken.'
|
|
866
|
+
)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const maxAttempts = options.maxAttempts ?? 50
|
|
870
|
+
const retryDelayMs = options.retryDelayMs ?? 5000
|
|
871
|
+
|
|
872
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
873
|
+
try {
|
|
874
|
+
return await _executeSingleDeepSeekCall(prompt, modelName, token, options)
|
|
875
|
+
} catch (err) {
|
|
876
|
+
const errMsg = err?.message || ''
|
|
877
|
+
const isRetryable =
|
|
878
|
+
errMsg.toLowerCase().includes('session expired') ||
|
|
879
|
+
errMsg.toLowerCase().includes('tidak valid') ||
|
|
880
|
+
errMsg.toLowerCase().includes('invalid ref file id') ||
|
|
881
|
+
errMsg.toLowerCase().includes('frequent') ||
|
|
882
|
+
errMsg.toLowerCase().includes('too many') ||
|
|
883
|
+
errMsg.toLowerCase().includes('terlalu sering') ||
|
|
884
|
+
errMsg.toLowerCase().includes('rate limit') ||
|
|
885
|
+
errMsg.toLowerCase().includes('dibatasi') ||
|
|
886
|
+
errMsg.toLowerCase().includes('menolak permintaan') ||
|
|
887
|
+
errMsg.toLowerCase().includes('tidak ada data streaming') ||
|
|
888
|
+
errMsg.toLowerCase().includes('503') ||
|
|
889
|
+
errMsg.toLowerCase().includes('502') ||
|
|
890
|
+
errMsg.toLowerCase().includes('429')
|
|
891
|
+
|
|
892
|
+
if (isRetryable && attempt < maxAttempts) {
|
|
893
|
+
// Setiap 5 kali kegagalan berulang, reset session cache agar membuat session baru
|
|
894
|
+
if (attempt % 5 === 0) {
|
|
895
|
+
activeSessionCache.delete(token)
|
|
896
|
+
utilitySessionCache.delete(token)
|
|
897
|
+
const markSessionId = options.sessionId || '1'
|
|
898
|
+
const key = getSessionCacheKey(token, markSessionId, !!options.isSmallTask)
|
|
899
|
+
const targetCache = options.isSmallTask ? utilitySessionCache : activeSessionCache
|
|
900
|
+
const sId = targetCache.get(key)
|
|
901
|
+
if (sId) sessionStateMap.delete(sId)
|
|
902
|
+
targetCache.delete(key)
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
console.warn(
|
|
906
|
+
`[DeepSeek-Web] Kendala DeepSeek: "${errMsg}". Mencoba ulang (${attempt}/${maxAttempts}) dalam ${retryDelayMs / 1000}s...`
|
|
907
|
+
)
|
|
908
|
+
if (typeof options.onStatus === 'function') {
|
|
909
|
+
options.onStatus(
|
|
910
|
+
`DeepSeek sibuk (${errMsg.substring(0, 35)}...). Jeda ${retryDelayMs / 1000}s (Percobaan ${attempt}/${maxAttempts})...`
|
|
911
|
+
)
|
|
912
|
+
}
|
|
913
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs))
|
|
914
|
+
continue
|
|
915
|
+
}
|
|
916
|
+
throw err
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Fungsi tingkat tinggi Vision: upload gambar -> wait ready -> generate respons chat DeepSeek.
|
|
923
|
+
*
|
|
924
|
+
* @param {string} prompt - Pertanyaan tentang gambar
|
|
925
|
+
* @param {string|Buffer} imagePathOrBuffer - Path file gambar atau Buffer
|
|
926
|
+
* @param {string} token - DeepSeek Bearer token
|
|
927
|
+
* @param {object} options - Opsi tambahan (filename, modelName, onDelta, onStatus, dll)
|
|
928
|
+
*/
|
|
929
|
+
export async function generateDeepSeekVisionResponse(
|
|
930
|
+
prompt,
|
|
931
|
+
imagePathOrBuffer,
|
|
932
|
+
token,
|
|
933
|
+
options = {}
|
|
934
|
+
) {
|
|
935
|
+
if (!token) {
|
|
936
|
+
throw new Error('DeepSeek User Token (Bearer) dibutuhkan.')
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (typeof options.onStatus === 'function') {
|
|
940
|
+
options.onStatus('Mengupload gambar ke DeepSeek...')
|
|
941
|
+
}
|
|
942
|
+
const { fileId } = await uploadImageFile(token, imagePathOrBuffer, options.filename)
|
|
943
|
+
|
|
944
|
+
if (typeof options.onStatus === 'function') {
|
|
945
|
+
options.onStatus('Menunggu DeepSeek selesai memproses gambar...')
|
|
946
|
+
}
|
|
947
|
+
await waitForFileReady(token, fileId)
|
|
948
|
+
|
|
949
|
+
const modelName = options.modelName || 'deepseek-chat'
|
|
950
|
+
const existingRefs = Array.isArray(options.refFileIds) ? options.refFileIds : []
|
|
951
|
+
|
|
952
|
+
return generateDeepSeekResponse(prompt, modelName, token, {
|
|
953
|
+
...options,
|
|
954
|
+
refFileIds: [...new Set([...existingRefs, fileId])]
|
|
955
|
+
})
|
|
956
|
+
}
|