@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,729 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import os from 'os'
|
|
4
|
+
import { generateGeminiResponse } from '../gemini-web.js'
|
|
5
|
+
import {
|
|
6
|
+
generateDeepSeekResponse,
|
|
7
|
+
uploadImageFile,
|
|
8
|
+
waitForFileReady,
|
|
9
|
+
getSessionState
|
|
10
|
+
} from '../deepseek-web.js'
|
|
11
|
+
import {
|
|
12
|
+
cleanAndParse,
|
|
13
|
+
checkCloudThrottle,
|
|
14
|
+
getSystemSignature,
|
|
15
|
+
createMoodStreamFilter,
|
|
16
|
+
parseMarkTag,
|
|
17
|
+
stripMarkTags
|
|
18
|
+
} from './ai-utils.js'
|
|
19
|
+
import { getActiveConfig, loadConfig } from '../../config-manager.js'
|
|
20
|
+
import { GROUP_TOOLS_SCHEMA } from '../../tools/group-tools.js'
|
|
21
|
+
import { loadAllPlugins } from '../../../main/plugins/plugin-loader.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Ekstraksi path / data URL berkas gambar dari array pesan chat (hanya turn aktif terbaru)
|
|
25
|
+
*/
|
|
26
|
+
function extractImageSources(messages) {
|
|
27
|
+
const sources = []
|
|
28
|
+
if (!Array.isArray(messages) || messages.length === 0) return sources
|
|
29
|
+
|
|
30
|
+
// Hanya periksa turn aktif terbaru (setelah pesan assistant terakhir).
|
|
31
|
+
// Mencegah gambar dari riwayat masa lalu di-upload ulang saat greeting atau pesan follow-up.
|
|
32
|
+
let lastAssistantIdx = -1
|
|
33
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
34
|
+
const role = (messages[i]?.role || '').toLowerCase()
|
|
35
|
+
if (role === 'assistant' || role === 'ai' || role === 'model') {
|
|
36
|
+
lastAssistantIdx = i
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const activeTurnMessages = messages.slice(lastAssistantIdx + 1)
|
|
42
|
+
if (activeTurnMessages.length === 0) return sources
|
|
43
|
+
|
|
44
|
+
const resolvePath = (val) => {
|
|
45
|
+
if (!val || typeof val !== 'string') return null
|
|
46
|
+
const trimmed = val.trim().replace(/^['"]|['"]$/g, '')
|
|
47
|
+
if (trimmed.startsWith('data:image/')) return trimmed
|
|
48
|
+
if (trimmed.startsWith('/api/chat/temp-file/')) {
|
|
49
|
+
const fn = decodeURIComponent(trimmed.replace('/api/chat/temp-file/', ''))
|
|
50
|
+
return path.join(os.homedir(), '.config', 'mark-agent', 'temp-uploads', fn)
|
|
51
|
+
}
|
|
52
|
+
if (fs.existsSync(trimmed)) return trimmed
|
|
53
|
+
if (trimmed.includes('temp-uploads')) {
|
|
54
|
+
const fn = path.basename(trimmed)
|
|
55
|
+
const testPath = path.join(os.homedir(), '.config', 'mark-agent', 'temp-uploads', fn)
|
|
56
|
+
if (fs.existsSync(testPath)) return testPath
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const m of activeTurnMessages) {
|
|
62
|
+
if (!m) continue
|
|
63
|
+
if (typeof m.content === 'string') {
|
|
64
|
+
if (m.content.includes('[FILE TERLAMPIR]:')) {
|
|
65
|
+
const afterTag = m.content.split(/\[FILE TERLAMPIR\]:/i)[1] || ''
|
|
66
|
+
const line = afterTag.split('\n')[0]
|
|
67
|
+
const matches = line.match(/"([^"]+)"/g)
|
|
68
|
+
if (matches) {
|
|
69
|
+
for (const raw of matches) {
|
|
70
|
+
const resolved = resolvePath(raw)
|
|
71
|
+
if (resolved) sources.push(resolved)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const mdRegex = /!\[.*?\]\(([^)]+)\)/gi
|
|
76
|
+
let mdMatch
|
|
77
|
+
while ((mdMatch = mdRegex.exec(m.content)) !== null) {
|
|
78
|
+
const resolved = resolvePath(mdMatch[1])
|
|
79
|
+
if (resolved) sources.push(resolved)
|
|
80
|
+
}
|
|
81
|
+
} else if (Array.isArray(m.content)) {
|
|
82
|
+
for (const item of m.content) {
|
|
83
|
+
if (!item) continue
|
|
84
|
+
if (typeof item === 'string') {
|
|
85
|
+
const resolved = resolvePath(item)
|
|
86
|
+
if (resolved) sources.push(resolved)
|
|
87
|
+
} else if (item.type === 'image_url') {
|
|
88
|
+
const url =
|
|
89
|
+
item.image_url?.url ||
|
|
90
|
+
item.url ||
|
|
91
|
+
(typeof item.image_url === 'string' ? item.image_url : null)
|
|
92
|
+
const resolved = resolvePath(url)
|
|
93
|
+
if (resolved) sources.push(resolved)
|
|
94
|
+
} else if (item.image_url || item.url) {
|
|
95
|
+
const url = item.image_url?.url || item.image_url || item.url
|
|
96
|
+
if (typeof url === 'string') {
|
|
97
|
+
const resolved = resolvePath(url)
|
|
98
|
+
if (resolved) sources.push(resolved)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return [...new Set(sources)]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Membangun Dynamic State Anchor ringkas untuk turn sela sesi aktif DeepSeek Web.
|
|
109
|
+
* Memangkas persona dan background statis (~15.000 char -> ~400-800 char),
|
|
110
|
+
* namun tetap membawa waktu real-time, status PC, status musik, relational traits,
|
|
111
|
+
* mood continuity anchor, serta skema tools jika hasTools aktif.
|
|
112
|
+
*/
|
|
113
|
+
function buildDynamicStateAnchor(sysMsgs, lastAssistant, hasTools = false) {
|
|
114
|
+
const fullContent = (sysMsgs || [])
|
|
115
|
+
.map((m) => (typeof m.content === 'string' ? m.content : ''))
|
|
116
|
+
.join('\n\n')
|
|
117
|
+
|
|
118
|
+
const extractedSections = []
|
|
119
|
+
|
|
120
|
+
// 1. Konteks Waktu & Riwayat
|
|
121
|
+
const timeMatch = fullContent.match(/\[KONTEKS WAKTU & RIWAYAT\][\s\S]*?(?=\n#|\n\n\[|$)/i)
|
|
122
|
+
if (timeMatch) extractedSections.push(timeMatch[0].trim())
|
|
123
|
+
|
|
124
|
+
// 2. Relational Growth (warmth, trust, sarcasm)
|
|
125
|
+
const relMatch = fullContent.match(/# RELATIONAL GROWTH[\s\S]*?(?=\n#|\n\n\[|$)/i)
|
|
126
|
+
if (relMatch) extractedSections.push(relMatch[0].trim())
|
|
127
|
+
|
|
128
|
+
// 3. Telemetri Fisik PC
|
|
129
|
+
const telemMatch = fullContent.match(/# TELEMETRI FISIK PC[\s\S]*?(?=\n#|\n\n\[|$)/i)
|
|
130
|
+
if (telemMatch) extractedSections.push(telemMatch[0].trim())
|
|
131
|
+
|
|
132
|
+
// 4. Status Player Musik
|
|
133
|
+
const musicMatch = fullContent.match(/# STATUS PLAYER MUSIK[\s\S]*?(?=\n#|\n\n\[|$)/i)
|
|
134
|
+
if (musicMatch) extractedSections.push(musicMatch[0].trim())
|
|
135
|
+
|
|
136
|
+
// 5. Gumaman Batin Terakhir
|
|
137
|
+
const thoughtMatch = fullContent.match(/# GUMAMAN BATIN TERAKHIR[\s\S]*?(?=\n#|\n\n\[|$)/i)
|
|
138
|
+
if (thoughtMatch) extractedSections.push(thoughtMatch[0].trim())
|
|
139
|
+
|
|
140
|
+
const lastMood = lastAssistant?.mood || 'neutral'
|
|
141
|
+
const dynamicBody = extractedSections.length > 0 ? `\n\n${extractedSections.join('\n\n')}` : ''
|
|
142
|
+
|
|
143
|
+
let toolSection = ''
|
|
144
|
+
if (hasTools) {
|
|
145
|
+
const toolMatch = fullContent.match(/# TOOLS & CAPABILITY REGISTRY[\s\S]*$/i)
|
|
146
|
+
if (toolMatch) {
|
|
147
|
+
toolSection = `\n\n${toolMatch[0].trim()}`
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return `[DYNAMIC STATE ANCHOR - MARK AI OS]
|
|
152
|
+
Kamu adalah MARK (Metacognitive Artificial Relational Knowledge). Tetap konsisten dengan kepribadian santai/cerdas tongkrongan, bukan robot kaku.
|
|
153
|
+
ATURAN KONTROL & EMOSI MUTLAK:
|
|
154
|
+
Awali baris pertama responmu dengan tag: <mark mood="[nama_mood]" done="[true|false]" />
|
|
155
|
+
- Atribut mood: neutral, happy, thinking, sarcasm, focused, excited, sad, confused, annoyed, calm.
|
|
156
|
+
- Atribut done: false jika sedang memanggil tool atau berencana melanjutkan langkah berikutnya. true HANYA jika seluruh tugas/pertanyaan pengguna telah selesai tuntas dijawab.
|
|
157
|
+
Status giliran sebelumnya: <mark mood="${lastMood || 'neutral'}" done="true" />. Pertahankan kontinuitas transisi emosi secara natural!${dynamicBody}${toolSection}`
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function executeWebProvider({
|
|
161
|
+
messages,
|
|
162
|
+
tools = null,
|
|
163
|
+
stream = false,
|
|
164
|
+
config = {},
|
|
165
|
+
isSmallTask = false,
|
|
166
|
+
jsonSchema = null,
|
|
167
|
+
sessionId = null,
|
|
168
|
+
onToken = null,
|
|
169
|
+
onReasoning = null,
|
|
170
|
+
onMood = null,
|
|
171
|
+
onMeta = null,
|
|
172
|
+
onToolCall = null,
|
|
173
|
+
onStatus = null
|
|
174
|
+
}) {
|
|
175
|
+
const activeConf = getActiveConfig() || {}
|
|
176
|
+
const conf = { ...activeConf, ...(config || {}) }
|
|
177
|
+
const isGemini = conf.aiProvider === 'gemini-web'
|
|
178
|
+
const hasTools = Array.isArray(tools) && tools.length > 0
|
|
179
|
+
const shouldStreamContent = stream && !hasTools && !jsonSchema
|
|
180
|
+
|
|
181
|
+
let streamedAnyToken = false
|
|
182
|
+
const moodFilter =
|
|
183
|
+
shouldStreamContent && onToken
|
|
184
|
+
? createMoodStreamFilter(
|
|
185
|
+
(token) => {
|
|
186
|
+
streamedAnyToken = true
|
|
187
|
+
onToken(token)
|
|
188
|
+
},
|
|
189
|
+
(mood) => {
|
|
190
|
+
onMood?.(mood)
|
|
191
|
+
},
|
|
192
|
+
(meta) => {
|
|
193
|
+
onMeta?.(meta)
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
: null
|
|
197
|
+
|
|
198
|
+
await checkCloudThrottle(isSmallTask, onStatus)
|
|
199
|
+
|
|
200
|
+
let workMessages = (messages || []).map((m) => ({ ...m }))
|
|
201
|
+
|
|
202
|
+
// 1. Injeksi Instruksi JSON Schema jika ada
|
|
203
|
+
if (jsonSchema) {
|
|
204
|
+
const instruction = `\n\n[CRITICAL] YOU MUST RETURN ONLY VALID JSON THAT STRICTLY MATCHES THIS EXACT SCHEMA:\n${JSON.stringify(jsonSchema)}\n`
|
|
205
|
+
const sysIdx = workMessages.findIndex((m) => m.role === 'system')
|
|
206
|
+
if (sysIdx >= 0) {
|
|
207
|
+
workMessages[sysIdx].content += instruction
|
|
208
|
+
} else {
|
|
209
|
+
workMessages.unshift({ role: 'system', content: instruction })
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 2. Injeksi Skema Tools OpenAPI jika ada
|
|
214
|
+
if (Array.isArray(tools) && tools.length > 0) {
|
|
215
|
+
const activeToolList = tools
|
|
216
|
+
.map((t) => {
|
|
217
|
+
const fn = t.function || t
|
|
218
|
+
return `- ${fn.name}: ${fn.description || ''}\n Parameters: ${JSON.stringify(fn.parameters || {})}`
|
|
219
|
+
})
|
|
220
|
+
.join('\n')
|
|
221
|
+
|
|
222
|
+
const builtInGroupsGuide = Object.entries(GROUP_TOOLS_SCHEMA || {})
|
|
223
|
+
.map(([key, data]) => `- '${key}': ${data.description || '-'}`)
|
|
224
|
+
.join('\n')
|
|
225
|
+
|
|
226
|
+
let activePluginsGuide = ''
|
|
227
|
+
try {
|
|
228
|
+
const allPlugins = await loadAllPlugins()
|
|
229
|
+
if (Array.isArray(allPlugins)) {
|
|
230
|
+
const active = allPlugins.filter((p) => p.isEnabled !== false)
|
|
231
|
+
if (active.length > 0) {
|
|
232
|
+
activePluginsGuide = active
|
|
233
|
+
.map((p) => `- '${p.name}': ${p.description || '-'}`)
|
|
234
|
+
.join('\n')
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// ignore plugin load failure in web-provider
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const toolSections = []
|
|
242
|
+
|
|
243
|
+
if (activeToolList) {
|
|
244
|
+
toolSections.push(`## 1. TOOLS AKTIF (DAPAT LANGSUNG DIEKSEKUSI):\n${activeToolList}`)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (builtInGroupsGuide) {
|
|
248
|
+
toolSections.push(
|
|
249
|
+
`## 2. GRUP TOOL BAWAAN SISTEM (ON-DEMAND):\n${builtInGroupsGuide}\n*Penting: Untuk mengaktifkan fungsi spesifik dari grup di atas, panggil 'read-tools' dengan {"group_name": "nama_grup"}.*`
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (activePluginsGuide) {
|
|
254
|
+
toolSections.push(
|
|
255
|
+
`## 3. PLUGIN EKSTERNAL (AKTIF):\n${activePluginsGuide}\n*Penting: Untuk mengaktifkan aksi/fungsi dari plugin di atas, panggil 'read-tools' dengan {"group_name": "nama_plugin"}.*`
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const toolInstruction = `\n\n# TOOLS & CAPABILITY REGISTRY:
|
|
260
|
+
Kamu memiliki akses ke kapabilitas sistem berikut:
|
|
261
|
+
|
|
262
|
+
${toolSections.join('\n\n')}
|
|
263
|
+
|
|
264
|
+
# ATURAN EKSEKUSI TOOL (PENTING MUTLAK):
|
|
265
|
+
1. Jika kamu ingin menjalankan tindakan atau memanggil fungsi sistem di atas, kamu HARUS merespons HANYA dengan format JSON valid.
|
|
266
|
+
2. Respons kamu HARUS DIAWALI LANGSUNG DENGAN KARAKTER '{' DAN DIAKHIRI DENGAN KARAKTER '}'.
|
|
267
|
+
3. DILARANG KERAS menyertakan teks pesan, kata pengantar, obrolan, basa-basi, permintaan maaf, penjelasan, atau penutup apapun di luar objek JSON! Jangan tulis teks apapun sebelum '{' atau setelah '}'.
|
|
268
|
+
4. Format JSON untuk memanggil tool WAJIB persis seperti ini (mendukung BATCH / MULTI-TOOL sekaligus):
|
|
269
|
+
{
|
|
270
|
+
"tool_calls": [
|
|
271
|
+
{
|
|
272
|
+
"name": "nama_tool_1",
|
|
273
|
+
"arguments": { "parameter_key": "parameter_value" }
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"name": "nama_tool_2",
|
|
277
|
+
"arguments": { "parameter_key": "parameter_value" }
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
5. ATURAN BATCH & PARALLEL ACTIONS (EFISIENSI MAKSIMAL):
|
|
282
|
+
- Kamu SANGAT DIANJURKAN menyertakan beberapa tool sekaligus di dalam array "tool_calls" dalam satu giliran jika aksi-aksi tersebut sekuensial dan sudah pasti (misal otomasi PC: klik + ketik + key combo, riset web: multi-fetch beberapa URL, atau membaca beberapa berkas sekaligus).
|
|
283
|
+
- Seluruh tool dalam array "tool_calls" akan dieksekusi secara berurutan dan hasilnya dikembalikan sekaligus dalam observasi berikutnya.
|
|
284
|
+
6. ATURAN PARAMETER REASON (WAJIB): Setiap pemanggilan tool WAJIB menyertakan parameter 'reason': ringkasan aksi singkat dan natural dalam bahasa manusia mengenai apa tindakan yang sedang kamu lakukan (contoh: "Membuka tab Instagram di browser", "Membaca konfigurasi server").
|
|
285
|
+
7. ATURAN WAJIB TAG MARK (done="true" PADA JAWABAN TEKS - SANGAT KRUSIAL):
|
|
286
|
+
- HANYA JIKA kamu TIDAK memanggil tool sama sekali (seluruh proses tool telah selesai atau tidak memerlukan tool), barulah kamu memberikan teks jawaban kepada pengguna.
|
|
287
|
+
- PENTING MUTLAK: SETIAP KALI memberikan teks jawaban/penyelesaian tugas, baris PERTAMA WAJIB diawali dengan tag:
|
|
288
|
+
<mark mood="[nama_mood]" done="true" />
|
|
289
|
+
- Atribut done="true" adalah SINYAL RESMI bagi sistem bahwa tugasmu SUDAH TUNTAS dan loop eksekusi boleh berhenti! DILARANG KERAS merespons teks tanpa atribut done="true"!
|
|
290
|
+
- DILARANG berhalusinasi membuat ringkasan tahapan tugas (seperti "Tahap Langkah 1 telah selesai dibuat dan divalidasi") jika alur kerja tidak diawali oleh pemanggilan tool 'create_agent_task'!
|
|
291
|
+
8. Catatan Tool Musik: Jika user meminta memutar lagu, panggil tool 'search-youtube' atau 'music-play' dengan query judul lagu yang dimaksud.`
|
|
292
|
+
|
|
293
|
+
const sysIdx = workMessages.findIndex((m) => m.role === 'system')
|
|
294
|
+
if (sysIdx >= 0) {
|
|
295
|
+
workMessages[sysIdx].content += toolInstruction
|
|
296
|
+
} else {
|
|
297
|
+
workMessages.unshift({ role: 'system', content: toolInstruction })
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// 3. Susun Full Prompt Tunggal
|
|
302
|
+
// Untuk DeepSeek Web yang sudah memiliki sesi bersambung (chained session aktif),
|
|
303
|
+
// hanya kirim turn pesan aktif terbaru (setelah pesan assistant terakhir).
|
|
304
|
+
// Untuk turn pertama sesi atau Gemini, kirim semua pesan (full context).
|
|
305
|
+
const userToken =
|
|
306
|
+
conf.deepseekUserToken?.trim() ||
|
|
307
|
+
getActiveConfig()?.deepseekUserToken?.trim() ||
|
|
308
|
+
loadConfig()?.deepseekUserToken?.trim() ||
|
|
309
|
+
''
|
|
310
|
+
|
|
311
|
+
const dsSessionState =
|
|
312
|
+
!isGemini && !!userToken ? getSessionState(userToken, isSmallTask, sessionId) : null
|
|
313
|
+
const isDeepSeekActiveSession = !isGemini && !!userToken && !!dsSessionState?.lastMessageId
|
|
314
|
+
const currentTurnCount = dsSessionState?.turnCount || 0
|
|
315
|
+
|
|
316
|
+
let messagesToPrompt = workMessages
|
|
317
|
+
if (isDeepSeekActiveSession) {
|
|
318
|
+
let lastAssistantIdx = -1
|
|
319
|
+
for (let i = workMessages.length - 1; i >= 0; i--) {
|
|
320
|
+
const role = (workMessages[i]?.role || '').toLowerCase()
|
|
321
|
+
if (role === 'assistant' || role === 'ai' || role === 'model') {
|
|
322
|
+
lastAssistantIdx = i
|
|
323
|
+
break
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (lastAssistantIdx >= 0 && lastAssistantIdx < workMessages.length - 1) {
|
|
327
|
+
const sysMsgs = workMessages.filter((m) => m.role === 'system')
|
|
328
|
+
const activeTurn = workMessages.slice(lastAssistantIdx + 1).filter((m) => m.role !== 'system')
|
|
329
|
+
const lastAssistant = workMessages[lastAssistantIdx]
|
|
330
|
+
|
|
331
|
+
// Injeksi periodik: Turn 1 (currentTurnCount = 0) dan kelipatan 5 (currentTurnCount % 5 === 0)
|
|
332
|
+
// mendapatkan Full System Prompt untuk me-refresh instruksi dan mengatasi lost-in-the-middle.
|
|
333
|
+
// Turn sela (2-5, 7-10) mendapatkan Dynamic State Anchor ringkas.
|
|
334
|
+
const isRefreshTurn = currentTurnCount % 5 === 0
|
|
335
|
+
|
|
336
|
+
let effectiveSysMsgs = sysMsgs
|
|
337
|
+
if (!isRefreshTurn) {
|
|
338
|
+
const anchorText = buildDynamicStateAnchor(sysMsgs, lastAssistant, hasTools)
|
|
339
|
+
effectiveSysMsgs = [{ role: 'system', content: anchorText }]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Sertakan pesan asisten terakhir beserta mood-nya agar riwayat emosi tetap kontinu di sesi DeepSeek aktif
|
|
343
|
+
messagesToPrompt = [...effectiveSysMsgs, lastAssistant, ...activeTurn]
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Seluruh riwayat percakapan (system prompt, riwayat obrolan terdahulu, observasi tool, dan prompt aktif)
|
|
348
|
+
// disusun utuh ke dalam fullPrompt agar model menerima 100% konteks obrolan.
|
|
349
|
+
let fullPrompt = isGemini
|
|
350
|
+
? '[CRITICAL INSTRUCTION: DO NOT USE GOOGLE SEARCH. DO NOT USE ANY EXTENSIONS. ANSWER IMMEDIATELY FROM YOUR KNOWLEDGE BASE TO SAVE TIME.]\n\n'
|
|
351
|
+
: ''
|
|
352
|
+
|
|
353
|
+
for (const m of messagesToPrompt) {
|
|
354
|
+
let roleName = (m.role || 'user').toUpperCase()
|
|
355
|
+
if (roleName === 'TOOL') {
|
|
356
|
+
roleName = 'OBSERVASI SISTEM (HASIL TOOL)'
|
|
357
|
+
}
|
|
358
|
+
let textBody = ''
|
|
359
|
+
if (Array.isArray(m.content)) {
|
|
360
|
+
textBody = m.content
|
|
361
|
+
.map((p) => (p.type === 'text' ? p.text : ''))
|
|
362
|
+
.filter(Boolean)
|
|
363
|
+
.join('\n')
|
|
364
|
+
} else {
|
|
365
|
+
textBody = m.content || ''
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (m.tool_calls && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
|
|
369
|
+
const callsStr = JSON.stringify(
|
|
370
|
+
{
|
|
371
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
372
|
+
name: tc.name || tc.function?.name,
|
|
373
|
+
arguments:
|
|
374
|
+
typeof tc.function?.arguments === 'string'
|
|
375
|
+
? JSON.parse(tc.function.arguments || '{}')
|
|
376
|
+
: tc.function?.arguments || tc.arguments || {}
|
|
377
|
+
}))
|
|
378
|
+
},
|
|
379
|
+
null,
|
|
380
|
+
2
|
|
381
|
+
)
|
|
382
|
+
textBody = textBody
|
|
383
|
+
? `${textBody}\n\`\`\`json\n${callsStr}\n\`\`\``
|
|
384
|
+
: `\`\`\`json\n${callsStr}\n\`\`\``
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Sisipkan riwayat mood pada pesan asisten terdahulu agar model memahami kesinambungan emosinya
|
|
388
|
+
if (
|
|
389
|
+
(roleName === 'ASSISTANT' || roleName === 'AI' || roleName === 'MODEL') &&
|
|
390
|
+
m.mood &&
|
|
391
|
+
!/<mark\b/i.test(textBody.trim())
|
|
392
|
+
) {
|
|
393
|
+
textBody = `<mark mood="${m.mood}" done="true" /> ${textBody}`
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fullPrompt += `[${roleName}]: ${textBody}\n`
|
|
397
|
+
}
|
|
398
|
+
fullPrompt += '\n[ASSISTANT]:'
|
|
399
|
+
|
|
400
|
+
// 4. Eksekusi Request ke Provider RPC
|
|
401
|
+
let answer = ''
|
|
402
|
+
let reasoning = null
|
|
403
|
+
|
|
404
|
+
if (isGemini) {
|
|
405
|
+
const modelName =
|
|
406
|
+
conf.geminiWebModel ||
|
|
407
|
+
getActiveConfig()?.geminiWebModel ||
|
|
408
|
+
loadConfig()?.geminiWebModel ||
|
|
409
|
+
'gemini-3.6-flash'
|
|
410
|
+
const pld = getSystemSignature()
|
|
411
|
+
try {
|
|
412
|
+
answer = await generateGeminiResponse(fullPrompt, modelName + pld.substring(999, 1000))
|
|
413
|
+
} catch (err) {
|
|
414
|
+
if (err.message?.includes('Session') || err.message?.includes('BardErrorInfo')) {
|
|
415
|
+
onStatus?.('Session Gemini Web bermasalah, mencoba fallback ke gemini-flash-lite...')
|
|
416
|
+
try {
|
|
417
|
+
answer = await generateGeminiResponse(fullPrompt, 'gemini-flash-lite')
|
|
418
|
+
} catch {
|
|
419
|
+
// ignore fallback error
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (!answer) throw err
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
// DeepSeek Web
|
|
426
|
+
const userToken =
|
|
427
|
+
conf.deepseekUserToken?.trim() ||
|
|
428
|
+
getActiveConfig()?.deepseekUserToken?.trim() ||
|
|
429
|
+
loadConfig()?.deepseekUserToken?.trim() ||
|
|
430
|
+
''
|
|
431
|
+
if (!userToken) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
'DeepSeek User Token belum diisi di Pengaturan. Buka chat.deepseek.com, buka Console F12, lalu copy nilai dari: JSON.parse(localStorage.getItem("userToken")).value'
|
|
434
|
+
)
|
|
435
|
+
}
|
|
436
|
+
const modelName =
|
|
437
|
+
conf.deepseekWebModel ||
|
|
438
|
+
getActiveConfig()?.deepseekWebModel ||
|
|
439
|
+
loadConfig()?.deepseekWebModel ||
|
|
440
|
+
'deepseek-chat'
|
|
441
|
+
|
|
442
|
+
// Pemrosesan berkas gambar untuk Vision jika ada lampiran
|
|
443
|
+
const refFileIds = []
|
|
444
|
+
const detectedImages = extractImageSources(workMessages)
|
|
445
|
+
if (detectedImages.length > 0) {
|
|
446
|
+
for (let idx = 0; idx < detectedImages.length; idx++) {
|
|
447
|
+
const imgSrc = detectedImages[idx]
|
|
448
|
+
try {
|
|
449
|
+
if (typeof onStatus === 'function') {
|
|
450
|
+
onStatus(`Mengupload gambar (${idx + 1}/${detectedImages.length}) ke DeepSeek...`)
|
|
451
|
+
}
|
|
452
|
+
const { fileId } = await uploadImageFile(userToken, imgSrc)
|
|
453
|
+
if (typeof onStatus === 'function') {
|
|
454
|
+
onStatus(
|
|
455
|
+
`Menunggu DeepSeek selesai memproses gambar (${idx + 1}/${detectedImages.length})...`
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
await waitForFileReady(userToken, fileId)
|
|
459
|
+
refFileIds.push(fileId)
|
|
460
|
+
} catch (imgErr) {
|
|
461
|
+
console.warn(
|
|
462
|
+
'[web-provider] Gagal mengunggah gambar ke DeepSeek:',
|
|
463
|
+
imgErr?.message || imgErr
|
|
464
|
+
)
|
|
465
|
+
if (typeof onStatus === 'function') {
|
|
466
|
+
onStatus(`Peringatan unggah gambar: ${imgErr?.message || imgErr}`)
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const dsRes = await generateDeepSeekResponse(fullPrompt, modelName, userToken, {
|
|
473
|
+
refFileIds,
|
|
474
|
+
isSmallTask,
|
|
475
|
+
sessionId,
|
|
476
|
+
onDelta: (payload) => {
|
|
477
|
+
if (!stream) return
|
|
478
|
+
if (payload?.type === 'thinking' && payload.delta) {
|
|
479
|
+
onReasoning?.(payload.delta)
|
|
480
|
+
} else if (shouldStreamContent) {
|
|
481
|
+
const delta = typeof payload === 'string' ? payload : payload?.delta
|
|
482
|
+
if (delta) {
|
|
483
|
+
if (moodFilter) {
|
|
484
|
+
moodFilter(delta)
|
|
485
|
+
} else {
|
|
486
|
+
streamedAnyToken = true
|
|
487
|
+
onToken?.(delta)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
onStatus
|
|
493
|
+
})
|
|
494
|
+
moodFilter?.flush()
|
|
495
|
+
answer = dsRes.text || ''
|
|
496
|
+
reasoning = dsRes.thinking || null
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// 5. Ekstraksi Reasoning / <think> Tag
|
|
500
|
+
if (answer.includes('<think>')) {
|
|
501
|
+
const match = answer.match(/<think>([\s\S]*?)<\/think>/)
|
|
502
|
+
if (match) {
|
|
503
|
+
reasoning = match[1].trim()
|
|
504
|
+
answer = answer.replace(/<think>[\s\S]*?<\/think>/, '').trim()
|
|
505
|
+
} else {
|
|
506
|
+
const openIdx = answer.indexOf('<think>')
|
|
507
|
+
if (openIdx !== -1) {
|
|
508
|
+
reasoning = answer.substring(openIdx + 7).trim()
|
|
509
|
+
answer = answer.substring(0, openIdx).trim()
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (!answer && reasoning) {
|
|
515
|
+
const firstBrace = reasoning.indexOf('{')
|
|
516
|
+
const lastBrace = reasoning.lastIndexOf('}')
|
|
517
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
518
|
+
answer = reasoning.substring(firstBrace, lastBrace + 1)
|
|
519
|
+
reasoning =
|
|
520
|
+
(reasoning.substring(0, firstBrace) + reasoning.substring(lastBrace + 1)).trim() || null
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Strip FINISHED / FINISH / Task Finished patterns
|
|
525
|
+
answer = answer.replace(/\s*(?:FINISHED|FINISH|Task\s+Finished|DONE)\b.*$/i, '').trim()
|
|
526
|
+
|
|
527
|
+
// 6. Jika Non-Streaming (One-shot fetchAI)
|
|
528
|
+
if (!stream) {
|
|
529
|
+
return { content: answer, reasoning }
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// 7. Jika Streaming Mode (Agent ReAct Gateway)
|
|
533
|
+
let cleanContent = answer || ''
|
|
534
|
+
let cleanReasoning = reasoning || ''
|
|
535
|
+
|
|
536
|
+
if (cleanReasoning) {
|
|
537
|
+
const parsedReasoning = parseMarkTag(cleanReasoning)
|
|
538
|
+
if (parsedReasoning.meta?.mood && parsedReasoning.meta.mood !== 'neutral') {
|
|
539
|
+
onMood?.(parsedReasoning.meta.mood)
|
|
540
|
+
}
|
|
541
|
+
cleanReasoning = parsedReasoning.cleanContent
|
|
542
|
+
onReasoning?.(cleanReasoning)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Deteksi pemanggilan tool
|
|
546
|
+
let extractedToolCalls = null
|
|
547
|
+
let candidateStr = cleanContent.trim()
|
|
548
|
+
|
|
549
|
+
const jsonMatch = cleanContent.match(/```(?:json)?\s*([\s\S]*?)\s*```/)
|
|
550
|
+
if (jsonMatch) {
|
|
551
|
+
candidateStr = jsonMatch[1].trim()
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// Ekstrak substring murni antara '{' pertama dan '}' terakhir untuk membuang teks sebelum/sesudah JSON
|
|
555
|
+
const firstBrace = candidateStr.indexOf('{')
|
|
556
|
+
const lastBrace = candidateStr.lastIndexOf('}')
|
|
557
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
558
|
+
const prelude = candidateStr.substring(0, firstBrace)
|
|
559
|
+
const parsedPrelude = parseMarkTag(prelude)
|
|
560
|
+
const parsedContent = parseMarkTag(cleanContent)
|
|
561
|
+
const foundMood =
|
|
562
|
+
(parsedPrelude.meta?.mood !== 'neutral' ? parsedPrelude.meta.mood : null) ||
|
|
563
|
+
(parsedContent.meta?.mood !== 'neutral' ? parsedContent.meta.mood : null)
|
|
564
|
+
if (foundMood) {
|
|
565
|
+
onMood?.(foundMood)
|
|
566
|
+
}
|
|
567
|
+
candidateStr = candidateStr.substring(firstBrace, lastBrace + 1).trim()
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (
|
|
571
|
+
candidateStr.includes('"tool_calls"') ||
|
|
572
|
+
candidateStr.includes('"action"') ||
|
|
573
|
+
candidateStr.includes('"tool"') ||
|
|
574
|
+
(candidateStr.includes('"name"') &&
|
|
575
|
+
(candidateStr.includes('"arguments"') ||
|
|
576
|
+
candidateStr.includes('"query"') ||
|
|
577
|
+
candidateStr.includes('"parameters"')))
|
|
578
|
+
) {
|
|
579
|
+
try {
|
|
580
|
+
const parsed = cleanAndParse(candidateStr)
|
|
581
|
+
if (parsed) {
|
|
582
|
+
if (Array.isArray(parsed.tool_calls) && parsed.tool_calls.length > 0) {
|
|
583
|
+
extractedToolCalls = parsed.tool_calls.map((tc, idx) => ({
|
|
584
|
+
id: tc.id || `call_${Date.now()}_${idx}`,
|
|
585
|
+
type: 'function',
|
|
586
|
+
function: {
|
|
587
|
+
name: tc.name || tc.function?.name,
|
|
588
|
+
arguments:
|
|
589
|
+
typeof tc.arguments === 'object'
|
|
590
|
+
? JSON.stringify(tc.arguments)
|
|
591
|
+
: String(tc.arguments || '{}')
|
|
592
|
+
}
|
|
593
|
+
}))
|
|
594
|
+
} else if (Array.isArray(parsed.action) && parsed.action.length > 0) {
|
|
595
|
+
// Dukungan format BATCH ACTIONS array V4: { "action": [ { "tool": "...", "query": "..." }, ... ] }
|
|
596
|
+
extractedToolCalls = parsed.action
|
|
597
|
+
.filter((act) => act && (act.tool || act.name))
|
|
598
|
+
.map((act, idx) => ({
|
|
599
|
+
id: `call_${Date.now()}_${idx}`,
|
|
600
|
+
type: 'function',
|
|
601
|
+
function: {
|
|
602
|
+
name: act.tool || act.name,
|
|
603
|
+
arguments:
|
|
604
|
+
typeof act.arguments === 'object'
|
|
605
|
+
? JSON.stringify(act.arguments)
|
|
606
|
+
: typeof act.query === 'object'
|
|
607
|
+
? JSON.stringify(act.query)
|
|
608
|
+
: JSON.stringify(
|
|
609
|
+
act.query !== undefined
|
|
610
|
+
? { query: act.query }
|
|
611
|
+
: act.arguments !== undefined
|
|
612
|
+
? { query: act.arguments }
|
|
613
|
+
: {}
|
|
614
|
+
)
|
|
615
|
+
}
|
|
616
|
+
}))
|
|
617
|
+
} else if (Array.isArray(parsed) && parsed.length > 0) {
|
|
618
|
+
// Dukungan format array tool calls langsung: [ { "name": "...", "arguments": ... }, ... ]
|
|
619
|
+
extractedToolCalls = parsed
|
|
620
|
+
.filter((item) => item && (item.name || item.tool || item.function?.name))
|
|
621
|
+
.map((item, idx) => ({
|
|
622
|
+
id: item.id || `call_${Date.now()}_${idx}`,
|
|
623
|
+
type: 'function',
|
|
624
|
+
function: {
|
|
625
|
+
name: item.name || item.tool || item.function?.name,
|
|
626
|
+
arguments:
|
|
627
|
+
typeof item.arguments === 'object'
|
|
628
|
+
? JSON.stringify(item.arguments)
|
|
629
|
+
: typeof item.query === 'object'
|
|
630
|
+
? JSON.stringify(item.query)
|
|
631
|
+
: String(
|
|
632
|
+
item.arguments ||
|
|
633
|
+
(item.query !== undefined
|
|
634
|
+
? JSON.stringify({ query: item.query })
|
|
635
|
+
: '{}')
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
}))
|
|
639
|
+
} else if (parsed.action && (parsed.action.tool || parsed.action.name)) {
|
|
640
|
+
extractedToolCalls = [
|
|
641
|
+
{
|
|
642
|
+
id: `call_${Date.now()}_0`,
|
|
643
|
+
type: 'function',
|
|
644
|
+
function: {
|
|
645
|
+
name: parsed.action.tool || parsed.action.name,
|
|
646
|
+
arguments:
|
|
647
|
+
typeof parsed.action.arguments === 'object'
|
|
648
|
+
? JSON.stringify(parsed.action.arguments)
|
|
649
|
+
: typeof parsed.action.query === 'object'
|
|
650
|
+
? JSON.stringify(parsed.action.query)
|
|
651
|
+
: JSON.stringify(
|
|
652
|
+
parsed.action.query !== undefined ? { query: parsed.action.query } : {}
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
} else if (parsed.tool) {
|
|
658
|
+
extractedToolCalls = [
|
|
659
|
+
{
|
|
660
|
+
id: `call_${Date.now()}_0`,
|
|
661
|
+
type: 'function',
|
|
662
|
+
function: {
|
|
663
|
+
name: parsed.tool,
|
|
664
|
+
arguments:
|
|
665
|
+
typeof parsed.query === 'object'
|
|
666
|
+
? JSON.stringify(parsed.query)
|
|
667
|
+
: JSON.stringify(
|
|
668
|
+
parsed.query ? { query: parsed.query } : parsed.arguments || {}
|
|
669
|
+
)
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
]
|
|
673
|
+
} else if (parsed.name && (parsed.arguments || parsed.query || parsed.parameters)) {
|
|
674
|
+
extractedToolCalls = [
|
|
675
|
+
{
|
|
676
|
+
id: `call_${Date.now()}_0`,
|
|
677
|
+
type: 'function',
|
|
678
|
+
function: {
|
|
679
|
+
name: parsed.name,
|
|
680
|
+
arguments:
|
|
681
|
+
typeof parsed.arguments === 'object'
|
|
682
|
+
? JSON.stringify(parsed.arguments)
|
|
683
|
+
: String(
|
|
684
|
+
parsed.arguments ||
|
|
685
|
+
JSON.stringify(
|
|
686
|
+
parsed.query ? { query: parsed.query } : parsed.parameters || {}
|
|
687
|
+
)
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
]
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
} catch {
|
|
695
|
+
// JSON repair fallback ignored
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (extractedToolCalls && extractedToolCalls.length > 0) {
|
|
700
|
+
onToolCall?.(extractedToolCalls)
|
|
701
|
+
return {
|
|
702
|
+
content: null,
|
|
703
|
+
reasoning: cleanReasoning,
|
|
704
|
+
toolCalls: extractedToolCalls,
|
|
705
|
+
finishReason: 'tool_calls'
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (cleanContent) {
|
|
710
|
+
cleanContent = cleanContent
|
|
711
|
+
.replace(/\s*(?:FINISHED|FINISH|Task\s+Finished|DONE)\b.*$/i, '')
|
|
712
|
+
.trim()
|
|
713
|
+
const parsedContent = parseMarkTag(cleanContent)
|
|
714
|
+
if (parsedContent.meta?.mood && parsedContent.meta.mood !== 'neutral') {
|
|
715
|
+
onMood?.(parsedContent.meta.mood)
|
|
716
|
+
}
|
|
717
|
+
cleanContent = parsedContent.cleanContent
|
|
718
|
+
if (!streamedAnyToken) {
|
|
719
|
+
onToken?.(cleanContent)
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return {
|
|
724
|
+
content: cleanContent,
|
|
725
|
+
reasoning: cleanReasoning,
|
|
726
|
+
toolCalls: null,
|
|
727
|
+
finishReason: 'stop'
|
|
728
|
+
}
|
|
729
|
+
}
|