@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,757 @@
|
|
|
1
|
+
import { jsonrepair } from 'jsonrepair'
|
|
2
|
+
import {
|
|
3
|
+
getSystemSignature,
|
|
4
|
+
activeAbortControllers,
|
|
5
|
+
createMoodStreamFilter,
|
|
6
|
+
createLMStudioOfflineError,
|
|
7
|
+
isLMStudioOfflineError,
|
|
8
|
+
parseMarkTag,
|
|
9
|
+
stripMarkTags
|
|
10
|
+
} from './ai-utils.js'
|
|
11
|
+
|
|
12
|
+
export async function executeOpenAIProvider({
|
|
13
|
+
messages,
|
|
14
|
+
tools = null,
|
|
15
|
+
stream = false,
|
|
16
|
+
config = {},
|
|
17
|
+
isSmallTask = false,
|
|
18
|
+
jsonSchema = null,
|
|
19
|
+
signal = null,
|
|
20
|
+
onToken = null,
|
|
21
|
+
onReasoning = null,
|
|
22
|
+
onMood = null,
|
|
23
|
+
onMeta = null,
|
|
24
|
+
onToolCall = null,
|
|
25
|
+
onStatus = null
|
|
26
|
+
}) {
|
|
27
|
+
const conf = config || {}
|
|
28
|
+
|
|
29
|
+
let formattedMessages = (messages || []).map((m, index) => {
|
|
30
|
+
let sanitizedContent = m.content
|
|
31
|
+
if (Array.isArray(m.content)) {
|
|
32
|
+
if (index < messages.length - 1) {
|
|
33
|
+
sanitizedContent = m.content.find((c) => c.type === 'text')?.text || '[Gambar terlampir]'
|
|
34
|
+
} else {
|
|
35
|
+
sanitizedContent = m.content
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (
|
|
40
|
+
(m.role === 'assistant' || m.role === 'model') &&
|
|
41
|
+
typeof sanitizedContent === 'string' &&
|
|
42
|
+
m.mood &&
|
|
43
|
+
!/<mark\b/i.test(sanitizedContent.trim())
|
|
44
|
+
) {
|
|
45
|
+
sanitizedContent = `<mark mood="${m.mood}" done="true" /> ${sanitizedContent}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (
|
|
49
|
+
(m.role === 'assistant' || m.role === 'model') &&
|
|
50
|
+
!sanitizedContent &&
|
|
51
|
+
(!m.tool_calls || m.tool_calls.length === 0)
|
|
52
|
+
) {
|
|
53
|
+
return {
|
|
54
|
+
...m,
|
|
55
|
+
role: 'user',
|
|
56
|
+
content: stream
|
|
57
|
+
? '[Catatan Sistem]: Lanjutkan analisis dan eksekusi tugas berikutnya.'
|
|
58
|
+
: '[Catatan Sistem]: Lanjutkan analisis dan langkah kerja berikutnya.'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { ...m, content: sanitizedContent }
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Sanitasi trailing assistant/model message untuk mencegah error 400 di endpoint OpenAI-compat
|
|
66
|
+
while (
|
|
67
|
+
formattedMessages.length > 0 &&
|
|
68
|
+
(formattedMessages[formattedMessages.length - 1].role === 'assistant' ||
|
|
69
|
+
formattedMessages[formattedMessages.length - 1].role === 'model') &&
|
|
70
|
+
!formattedMessages[formattedMessages.length - 1].content &&
|
|
71
|
+
(!formattedMessages[formattedMessages.length - 1].tool_calls ||
|
|
72
|
+
formattedMessages[formattedMessages.length - 1].tool_calls.length === 0)
|
|
73
|
+
) {
|
|
74
|
+
formattedMessages.pop()
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
formattedMessages.length > 0 &&
|
|
79
|
+
(formattedMessages[formattedMessages.length - 1].role === 'assistant' ||
|
|
80
|
+
formattedMessages[formattedMessages.length - 1].role === 'model')
|
|
81
|
+
) {
|
|
82
|
+
formattedMessages.push({
|
|
83
|
+
role: 'user',
|
|
84
|
+
content: stream
|
|
85
|
+
? '[Instruksi Lanjutan]: Lanjutkan analisis dan eksekusi tugas berikutnya.'
|
|
86
|
+
: 'Lanjutkan analisis dan langkah kerja berikutnya.'
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (!isSmallTask) {
|
|
91
|
+
const pld = getSystemSignature()
|
|
92
|
+
const sysIdx = formattedMessages.findIndex((m) => m.role === 'system')
|
|
93
|
+
if (sysIdx >= 0) {
|
|
94
|
+
formattedMessages[sysIdx].content += `\n\n${pld}`
|
|
95
|
+
} else {
|
|
96
|
+
formattedMessages.unshift({ role: 'system', content: pld })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let endpoint = 'http://localhost:1234/v1/chat/completions'
|
|
101
|
+
const headers = {
|
|
102
|
+
'Content-Type': 'application/json'
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (conf.aiProvider === 'custom') {
|
|
106
|
+
endpoint = conf.customEndpoint || 'http://localhost:1234/v1/chat/completions'
|
|
107
|
+
if (conf.customApiKey) {
|
|
108
|
+
headers['Authorization'] = `Bearer ${conf.customApiKey}`
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const model =
|
|
113
|
+
conf.aiProvider === 'custom' ? conf.customModel || 'default-model' : conf.model || 'local-model'
|
|
114
|
+
|
|
115
|
+
// ==========================================
|
|
116
|
+
// 1. STREAMING MODE (Agent ReAct Gateway)
|
|
117
|
+
// ==========================================
|
|
118
|
+
if (stream) {
|
|
119
|
+
const body = {
|
|
120
|
+
stream: true,
|
|
121
|
+
stream_options: { include_usage: true },
|
|
122
|
+
model,
|
|
123
|
+
temperature: Number(conf.temperature) || 0,
|
|
124
|
+
messages: formattedMessages
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (tools && Array.isArray(tools) && tools.length > 0) {
|
|
128
|
+
body.tools = tools
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const abortController = new AbortController()
|
|
132
|
+
activeAbortControllers.add(abortController)
|
|
133
|
+
|
|
134
|
+
if (signal) {
|
|
135
|
+
if (signal.aborted) {
|
|
136
|
+
abortController.abort()
|
|
137
|
+
} else {
|
|
138
|
+
signal.addEventListener('abort', () => abortController.abort())
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let moodExtracted = false
|
|
143
|
+
const extractMood = (text) => {
|
|
144
|
+
if (!moodExtracted && text && onMood) {
|
|
145
|
+
const parsed = parseMarkTag(text)
|
|
146
|
+
if (parsed.meta?.mood && parsed.meta.mood !== 'neutral') {
|
|
147
|
+
onMood(parsed.meta.mood)
|
|
148
|
+
moodExtracted = true
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let accumulatedContent = ''
|
|
154
|
+
let accumulatedReasoning = ''
|
|
155
|
+
const accumulatedToolCalls = {}
|
|
156
|
+
let finishReason = 'stop'
|
|
157
|
+
let serverUsage = null
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
let response = await fetch(endpoint, {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers,
|
|
163
|
+
body: JSON.stringify(body),
|
|
164
|
+
signal: abortController.signal
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
if (!response.ok && body.stream_options && response.status === 400) {
|
|
168
|
+
const errCloned = response.clone()
|
|
169
|
+
const textData = await errCloned.text().catch(() => '')
|
|
170
|
+
if (
|
|
171
|
+
textData.toLowerCase().includes('stream_options') ||
|
|
172
|
+
textData.toLowerCase().includes('unknown')
|
|
173
|
+
) {
|
|
174
|
+
const fallbackBody = { ...body }
|
|
175
|
+
delete fallbackBody.stream_options
|
|
176
|
+
const retryRes = await fetch(endpoint, {
|
|
177
|
+
method: 'POST',
|
|
178
|
+
headers,
|
|
179
|
+
body: JSON.stringify(fallbackBody),
|
|
180
|
+
signal: abortController.signal
|
|
181
|
+
})
|
|
182
|
+
if (retryRes.ok) {
|
|
183
|
+
response = retryRes
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
const textData = await response.text()
|
|
190
|
+
let errorMsg = textData
|
|
191
|
+
try {
|
|
192
|
+
const json = JSON.parse(textData)
|
|
193
|
+
errorMsg = json.error?.message || json.message || textData
|
|
194
|
+
} catch (_) {}
|
|
195
|
+
throw new Error(`API Error (${response.status}): ${errorMsg}`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const sseMoodFilter = createMoodStreamFilter(
|
|
199
|
+
onToken,
|
|
200
|
+
(mood) => {
|
|
201
|
+
onMood?.(mood)
|
|
202
|
+
moodExtracted = true
|
|
203
|
+
},
|
|
204
|
+
(meta) => {
|
|
205
|
+
onMeta?.(meta)
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
const processContentToken = (token) => {
|
|
210
|
+
accumulatedContent += token
|
|
211
|
+
sseMoodFilter(token)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const handleChunkText = (jsonStr) => {
|
|
215
|
+
if (!jsonStr || jsonStr === '[DONE]') return
|
|
216
|
+
try {
|
|
217
|
+
const parsed = JSON.parse(jsonStr)
|
|
218
|
+
if (parsed.usage) {
|
|
219
|
+
serverUsage = parsed.usage
|
|
220
|
+
}
|
|
221
|
+
const choice = parsed.choices?.[0]
|
|
222
|
+
if (!choice) return
|
|
223
|
+
|
|
224
|
+
if (choice.finish_reason) {
|
|
225
|
+
finishReason = choice.finish_reason
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const delta = choice.delta || {}
|
|
229
|
+
|
|
230
|
+
if (delta.reasoning_content || delta.reasoning) {
|
|
231
|
+
const rToken = delta.reasoning_content || delta.reasoning
|
|
232
|
+
accumulatedReasoning += rToken
|
|
233
|
+
extractMood(accumulatedReasoning)
|
|
234
|
+
onReasoning?.(rToken)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (delta.content) {
|
|
238
|
+
processContentToken(delta.content)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (delta.tool_calls && Array.isArray(delta.tool_calls)) {
|
|
242
|
+
for (const tc of delta.tool_calls) {
|
|
243
|
+
const idx = tc.index ?? 0
|
|
244
|
+
if (!accumulatedToolCalls[idx]) {
|
|
245
|
+
accumulatedToolCalls[idx] = {
|
|
246
|
+
id: tc.id || `call_${Date.now()}_${idx}`,
|
|
247
|
+
type: 'function',
|
|
248
|
+
function: {
|
|
249
|
+
name: tc.function?.name || '',
|
|
250
|
+
arguments: ''
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (tc.function?.name && !accumulatedToolCalls[idx].function.name) {
|
|
255
|
+
accumulatedToolCalls[idx].function.name = tc.function.name
|
|
256
|
+
}
|
|
257
|
+
if (tc.function?.arguments) {
|
|
258
|
+
accumulatedToolCalls[idx].function.arguments += tc.function.arguments
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch (_) {}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (response.body && (response.body.getReader || response.body[Symbol.asyncIterator])) {
|
|
266
|
+
if (response.body[Symbol.asyncIterator]) {
|
|
267
|
+
const decoder = new TextDecoder()
|
|
268
|
+
let lineBuffer = ''
|
|
269
|
+
for await (const chunk of response.body) {
|
|
270
|
+
lineBuffer +=
|
|
271
|
+
typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true })
|
|
272
|
+
const lines = lineBuffer.split('\n')
|
|
273
|
+
lineBuffer = lines.pop() || ''
|
|
274
|
+
for (const line of lines) {
|
|
275
|
+
const trimmed = line.trim()
|
|
276
|
+
if (trimmed.startsWith('data:')) {
|
|
277
|
+
handleChunkText(trimmed.slice(5).trim())
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (lineBuffer.trim().startsWith('data:')) {
|
|
282
|
+
handleChunkText(lineBuffer.trim().slice(5).trim())
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
const reader = response.body.getReader()
|
|
286
|
+
const decoder = new TextDecoder()
|
|
287
|
+
let lineBuffer = ''
|
|
288
|
+
while (true) {
|
|
289
|
+
const { done, value } = await reader.read()
|
|
290
|
+
if (done) break
|
|
291
|
+
lineBuffer +=
|
|
292
|
+
typeof value === 'string' ? value : decoder.decode(value, { stream: true })
|
|
293
|
+
const lines = lineBuffer.split('\n')
|
|
294
|
+
lineBuffer = lines.pop() || ''
|
|
295
|
+
for (const line of lines) {
|
|
296
|
+
const trimmed = line.trim()
|
|
297
|
+
if (trimmed.startsWith('data:')) {
|
|
298
|
+
handleChunkText(trimmed.slice(5).trim())
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (lineBuffer.trim().startsWith('data:')) {
|
|
303
|
+
handleChunkText(lineBuffer.trim().slice(5).trim())
|
|
304
|
+
}
|
|
305
|
+
sseMoodFilter.flush()
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
const raw = await response.text()
|
|
309
|
+
const parsed = JSON.parse(raw)
|
|
310
|
+
if (parsed.usage) {
|
|
311
|
+
serverUsage = parsed.usage
|
|
312
|
+
}
|
|
313
|
+
const choice = parsed.choices?.[0]
|
|
314
|
+
if (choice) {
|
|
315
|
+
accumulatedContent = choice.message?.content || ''
|
|
316
|
+
accumulatedReasoning =
|
|
317
|
+
choice.message?.reasoning || choice.message?.reasoning_content || ''
|
|
318
|
+
if (choice.message?.tool_calls) {
|
|
319
|
+
choice.message.tool_calls.forEach((tc, i) => {
|
|
320
|
+
accumulatedToolCalls[i] = tc
|
|
321
|
+
})
|
|
322
|
+
finishReason = 'tool_calls'
|
|
323
|
+
}
|
|
324
|
+
if (accumulatedContent) onToken?.(accumulatedContent)
|
|
325
|
+
if (accumulatedReasoning) onReasoning?.(accumulatedReasoning)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const toolCallsList = Object.values(accumulatedToolCalls)
|
|
330
|
+
if (toolCallsList.length > 0) {
|
|
331
|
+
finishReason = 'tool_calls'
|
|
332
|
+
onToolCall?.(toolCallsList)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
let finalMood = null
|
|
336
|
+
const fullText = (accumulatedReasoning + '\n' + accumulatedContent).trim()
|
|
337
|
+
const parsedFull = parseMarkTag(fullText)
|
|
338
|
+
if (parsedFull.meta?.mood) {
|
|
339
|
+
finalMood = parsedFull.meta.mood
|
|
340
|
+
if (!moodExtracted) {
|
|
341
|
+
onMood?.(finalMood)
|
|
342
|
+
moodExtracted = true
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
content: accumulatedContent,
|
|
348
|
+
reasoning: accumulatedReasoning,
|
|
349
|
+
mood: finalMood,
|
|
350
|
+
toolCalls: toolCallsList.length > 0 ? toolCallsList : null,
|
|
351
|
+
finishReason,
|
|
352
|
+
usage: serverUsage
|
|
353
|
+
}
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (conf.aiProvider !== 'custom' && isLMStudioOfflineError(error)) {
|
|
356
|
+
throw createLMStudioOfflineError(error)
|
|
357
|
+
}
|
|
358
|
+
throw error
|
|
359
|
+
} finally {
|
|
360
|
+
activeAbortControllers.delete(abortController)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ==========================================
|
|
365
|
+
// 2. NON-STREAMING MODE (Standard fetchAI)
|
|
366
|
+
// ==========================================
|
|
367
|
+
const body = {
|
|
368
|
+
stream: false,
|
|
369
|
+
model,
|
|
370
|
+
temperature: Number(conf.temperature) || 0,
|
|
371
|
+
messages: formattedMessages
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (jsonSchema) {
|
|
375
|
+
if (conf.aiProvider === 'custom') {
|
|
376
|
+
body.messages = body.messages.map((m) => ({ ...m }))
|
|
377
|
+
const sysIdx = body.messages.findIndex((m) => m.role === 'system')
|
|
378
|
+
const instruction = `\n\n[CRITICAL] YOU MUST RETURN ONLY VALID JSON THAT STRICTLY MATCHES THIS EXACT SCHEMA:\n${JSON.stringify(jsonSchema)}\n`
|
|
379
|
+
if (sysIdx >= 0) {
|
|
380
|
+
body.messages[sysIdx].content += instruction
|
|
381
|
+
} else {
|
|
382
|
+
body.messages.unshift({ role: 'system', content: instruction })
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
body.response_format = {
|
|
386
|
+
type: 'json_schema',
|
|
387
|
+
json_schema: {
|
|
388
|
+
name: 'mark_schema',
|
|
389
|
+
strict: true,
|
|
390
|
+
schema: jsonSchema
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const parentAbortController = new AbortController()
|
|
397
|
+
activeAbortControllers.add(parentAbortController)
|
|
398
|
+
|
|
399
|
+
if (signal) {
|
|
400
|
+
if (signal.aborted) parentAbortController.abort()
|
|
401
|
+
else signal.addEventListener('abort', () => parentAbortController.abort())
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const executeFetch = async (currentBody, isRetry = false, trafficRetryCount = 0) => {
|
|
405
|
+
if (parentAbortController.signal.aborted) {
|
|
406
|
+
throw new Error('AbortError')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const timeoutMs = 300000
|
|
410
|
+
const abortController = new AbortController()
|
|
411
|
+
activeAbortControllers.add(abortController)
|
|
412
|
+
const timeoutId = setTimeout(
|
|
413
|
+
() => abortController.abort(new Error('Request Timeout (Tidak ada respon dari server)')),
|
|
414
|
+
timeoutMs
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
let response
|
|
418
|
+
try {
|
|
419
|
+
response = await fetch(endpoint, {
|
|
420
|
+
method: 'POST',
|
|
421
|
+
headers,
|
|
422
|
+
body: JSON.stringify(currentBody),
|
|
423
|
+
signal: abortController.signal
|
|
424
|
+
})
|
|
425
|
+
clearTimeout(timeoutId)
|
|
426
|
+
} catch (err) {
|
|
427
|
+
clearTimeout(timeoutId)
|
|
428
|
+
if (parentAbortController.signal.aborted) {
|
|
429
|
+
throw new Error('AbortError')
|
|
430
|
+
}
|
|
431
|
+
if (
|
|
432
|
+
abortController.signal.reason?.message === 'Request Timeout (Tidak ada respon dari server)'
|
|
433
|
+
) {
|
|
434
|
+
throw new Error('Request Timeout: AI memakan waktu terlalu lama untuk membalas.')
|
|
435
|
+
}
|
|
436
|
+
if (err.name === 'AbortError' || (err.message && err.message.includes('Timeout'))) {
|
|
437
|
+
throw new Error(`Koneksi Timeout: Server API (${endpoint}) nge-gantung lebih dari 5 menit.`)
|
|
438
|
+
}
|
|
439
|
+
const causeStr = err.cause ? ` (${err.cause.message || err.cause.code || err.cause})` : ''
|
|
440
|
+
const enrichedError = new Error(
|
|
441
|
+
`Gagal menghubungi server AI di ${endpoint}: ${err.message}${causeStr}`
|
|
442
|
+
)
|
|
443
|
+
enrichedError.code = err.code || err.cause?.code || 'FETCH_FAILED'
|
|
444
|
+
enrichedError.cause = err
|
|
445
|
+
throw enrichedError
|
|
446
|
+
} finally {
|
|
447
|
+
clearTimeout(timeoutId)
|
|
448
|
+
parentAbortController.signal.removeEventListener(
|
|
449
|
+
'abort',
|
|
450
|
+
abortController.abort.bind(abortController)
|
|
451
|
+
)
|
|
452
|
+
activeAbortControllers.delete(abortController)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!response.ok) {
|
|
456
|
+
const textData = await response.text()
|
|
457
|
+
let errorData = null
|
|
458
|
+
try {
|
|
459
|
+
errorData = JSON.parse(textData)
|
|
460
|
+
} catch (_) {}
|
|
461
|
+
|
|
462
|
+
const errorMsg =
|
|
463
|
+
errorData?.error?.message || errorData?.message || response.statusText || textData
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
!isRetry &&
|
|
467
|
+
currentBody.response_format?.type === 'json_schema' &&
|
|
468
|
+
(String(errorMsg).toLowerCase().includes('schema') ||
|
|
469
|
+
String(errorMsg).toLowerCase().includes('json') ||
|
|
470
|
+
response.status === 400 ||
|
|
471
|
+
response.status === 422)
|
|
472
|
+
) {
|
|
473
|
+
const fallbackBody = { ...currentBody }
|
|
474
|
+
fallbackBody.response_format = { type: 'json_object' }
|
|
475
|
+
|
|
476
|
+
const fallbackMessages = fallbackBody.messages.map((m) => ({ ...m }))
|
|
477
|
+
const sysIdx = fallbackMessages.findIndex((m) => m.role === 'system')
|
|
478
|
+
const instruction = `\n\n[CRITICAL] YOU MUST RETURN ONLY VALID JSON THAT STRICTLY MATCHES THIS EXACT SCHEMA:\n${JSON.stringify(jsonSchema)}\n`
|
|
479
|
+
|
|
480
|
+
if (sysIdx >= 0) {
|
|
481
|
+
fallbackMessages[sysIdx].content += instruction
|
|
482
|
+
} else {
|
|
483
|
+
fallbackMessages.unshift({ role: 'system', content: instruction })
|
|
484
|
+
}
|
|
485
|
+
fallbackBody.messages = fallbackMessages
|
|
486
|
+
|
|
487
|
+
return executeFetch(fallbackBody, true, trafficRetryCount)
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const errorProvider = conf.aiProvider === 'custom' ? 'Custom API' : 'LM Studio'
|
|
491
|
+
const finalErrorMessage = typeof errorMsg === 'string' ? errorMsg : JSON.stringify(errorMsg)
|
|
492
|
+
|
|
493
|
+
const err = new Error(`Gagal memuat AI (${errorProvider}): ${finalErrorMessage}`)
|
|
494
|
+
err.status = response.status
|
|
495
|
+
throw err
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const rawText = await response.text()
|
|
499
|
+
const cleanText = rawText.trim()
|
|
500
|
+
|
|
501
|
+
// 1. Tangani jika response berupa JSON Array chunk
|
|
502
|
+
if (cleanText.startsWith('[') && cleanText.endsWith(']')) {
|
|
503
|
+
try {
|
|
504
|
+
const parsedArray = JSON.parse(cleanText)
|
|
505
|
+
if (Array.isArray(parsedArray) && parsedArray.length > 0) {
|
|
506
|
+
let combinedContent = ''
|
|
507
|
+
let reasoning = ''
|
|
508
|
+
const lastId = parsedArray[0]?.id || 'chatcmpl-array-stream'
|
|
509
|
+
const modelName = parsedArray[0]?.model || model
|
|
510
|
+
let isChunkArray = false
|
|
511
|
+
|
|
512
|
+
for (const item of parsedArray) {
|
|
513
|
+
if (item?.object === 'chat.completion.chunk' || item?.choices?.[0]?.delta) {
|
|
514
|
+
isChunkArray = true
|
|
515
|
+
const delta = item.choices?.[0]?.delta
|
|
516
|
+
if (delta) {
|
|
517
|
+
if (delta.content) combinedContent += delta.content
|
|
518
|
+
if (delta.reasoning_content) reasoning += delta.reasoning_content
|
|
519
|
+
else if (delta.reasoning) reasoning += delta.reasoning
|
|
520
|
+
}
|
|
521
|
+
} else if (item?.choices?.[0]?.message?.content) {
|
|
522
|
+
isChunkArray = true
|
|
523
|
+
combinedContent += item.choices[0].message.content
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (isChunkArray && combinedContent) {
|
|
528
|
+
return {
|
|
529
|
+
id: lastId,
|
|
530
|
+
model: modelName,
|
|
531
|
+
choices: [
|
|
532
|
+
{
|
|
533
|
+
message: {
|
|
534
|
+
role: 'assistant',
|
|
535
|
+
content: combinedContent,
|
|
536
|
+
reasoning: reasoning || null
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
]
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
} catch (_) {}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// 2. Tangani jika response berupa SSE Stream
|
|
547
|
+
if (cleanText.includes('data:') || cleanText.includes('[DONE]')) {
|
|
548
|
+
const lines = cleanText.split('\n')
|
|
549
|
+
let combinedContent = ''
|
|
550
|
+
let reasoning = ''
|
|
551
|
+
let lastId = 'chatcmpl-stream'
|
|
552
|
+
let modelName = model
|
|
553
|
+
|
|
554
|
+
for (const line of lines) {
|
|
555
|
+
const trimmed = line.trim()
|
|
556
|
+
if (!trimmed || !trimmed.startsWith('data:')) continue
|
|
557
|
+
const dataStr = trimmed.slice(5).trim()
|
|
558
|
+
if (!dataStr || dataStr === '[DONE]') continue
|
|
559
|
+
|
|
560
|
+
try {
|
|
561
|
+
const chunk = JSON.parse(dataStr)
|
|
562
|
+
if (chunk.id) lastId = chunk.id
|
|
563
|
+
if (chunk.model) modelName = chunk.model
|
|
564
|
+
const delta = chunk.choices?.[0]?.delta
|
|
565
|
+
if (delta) {
|
|
566
|
+
if (delta.content) combinedContent += delta.content
|
|
567
|
+
if (delta.reasoning_content) reasoning += delta.reasoning_content
|
|
568
|
+
else if (delta.reasoning) reasoning += delta.reasoning
|
|
569
|
+
} else if (chunk.choices?.[0]?.message?.content) {
|
|
570
|
+
combinedContent += chunk.choices[0].message.content
|
|
571
|
+
}
|
|
572
|
+
} catch (_) {}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (combinedContent) {
|
|
576
|
+
return {
|
|
577
|
+
id: lastId,
|
|
578
|
+
model: modelName,
|
|
579
|
+
choices: [
|
|
580
|
+
{
|
|
581
|
+
message: {
|
|
582
|
+
role: 'assistant',
|
|
583
|
+
content: combinedContent,
|
|
584
|
+
reasoning: reasoning || null
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// 3. Tangani jika response berupa NDJSON (JSON Lines)
|
|
593
|
+
if (cleanText.includes('\n{')) {
|
|
594
|
+
const lines = cleanText
|
|
595
|
+
.split('\n')
|
|
596
|
+
.map((l) => l.trim())
|
|
597
|
+
.filter(Boolean)
|
|
598
|
+
let combinedContent = ''
|
|
599
|
+
let isValidNdjson = false
|
|
600
|
+
for (const line of lines) {
|
|
601
|
+
try {
|
|
602
|
+
const parsed = JSON.parse(line)
|
|
603
|
+
if (parsed.choices?.[0]?.delta?.content) {
|
|
604
|
+
combinedContent += parsed.choices[0].delta.content
|
|
605
|
+
isValidNdjson = true
|
|
606
|
+
} else if (parsed.choices?.[0]?.message?.content) {
|
|
607
|
+
combinedContent += parsed.choices[0].message.content
|
|
608
|
+
isValidNdjson = true
|
|
609
|
+
}
|
|
610
|
+
} catch (_) {}
|
|
611
|
+
}
|
|
612
|
+
if (isValidNdjson && combinedContent) {
|
|
613
|
+
return {
|
|
614
|
+
choices: [{ message: { role: 'assistant', content: combinedContent } }]
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// 4. Tangani JSON standar atau dengan jsonrepair
|
|
620
|
+
try {
|
|
621
|
+
return JSON.parse(rawText)
|
|
622
|
+
} catch (_) {
|
|
623
|
+
const firstBrace = cleanText.indexOf('{')
|
|
624
|
+
const lastBrace = cleanText.lastIndexOf('}')
|
|
625
|
+
if (firstBrace !== -1 && lastBrace !== -1 && lastBrace >= firstBrace) {
|
|
626
|
+
const sub = cleanText.substring(firstBrace, lastBrace + 1)
|
|
627
|
+
try {
|
|
628
|
+
return JSON.parse(sub)
|
|
629
|
+
} catch (_) {
|
|
630
|
+
try {
|
|
631
|
+
return JSON.parse(jsonrepair(sub))
|
|
632
|
+
} catch (_) {}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
try {
|
|
637
|
+
return JSON.parse(jsonrepair(rawText))
|
|
638
|
+
} catch (parseError) {
|
|
639
|
+
throw new Error(`API mengembalikan format respon tidak valid: ${parseError.message}`)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
try {
|
|
645
|
+
const data = await executeFetch(body)
|
|
646
|
+
|
|
647
|
+
if (!data) {
|
|
648
|
+
throw new Error('API tidak mengembalikan data respon.')
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
let processedData = data
|
|
652
|
+
if (Array.isArray(processedData)) {
|
|
653
|
+
let combinedContent = ''
|
|
654
|
+
let combinedReasoning = ''
|
|
655
|
+
for (const item of processedData) {
|
|
656
|
+
const delta = item.choices?.[0]?.delta || item.choices?.[0]?.message
|
|
657
|
+
if (delta) {
|
|
658
|
+
if (delta.content) combinedContent += delta.content
|
|
659
|
+
if (delta.reasoning_content) combinedReasoning += delta.reasoning_content
|
|
660
|
+
else if (delta.reasoning) combinedReasoning += delta.reasoning
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
processedData = {
|
|
664
|
+
choices: [
|
|
665
|
+
{
|
|
666
|
+
message: {
|
|
667
|
+
role: 'assistant',
|
|
668
|
+
content: combinedContent,
|
|
669
|
+
reasoning: combinedReasoning || null
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
]
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const choice =
|
|
677
|
+
Array.isArray(processedData.choices) && processedData.choices.length > 0
|
|
678
|
+
? processedData.choices[0]
|
|
679
|
+
: null
|
|
680
|
+
const message =
|
|
681
|
+
choice?.message ||
|
|
682
|
+
choice?.delta ||
|
|
683
|
+
(processedData.message
|
|
684
|
+
? processedData.message
|
|
685
|
+
: processedData.response
|
|
686
|
+
? { content: processedData.response }
|
|
687
|
+
: processedData.result
|
|
688
|
+
? { content: processedData.result }
|
|
689
|
+
: null)
|
|
690
|
+
|
|
691
|
+
let content = ''
|
|
692
|
+
let reasoning = null
|
|
693
|
+
|
|
694
|
+
if (message) {
|
|
695
|
+
content = typeof message === 'string' ? message : message.content || message.text || ''
|
|
696
|
+
reasoning = message.reasoning || message.reasoning_content || null
|
|
697
|
+
} else if (typeof processedData === 'string') {
|
|
698
|
+
content = processedData
|
|
699
|
+
} else if (
|
|
700
|
+
processedData.content ||
|
|
701
|
+
processedData.text ||
|
|
702
|
+
processedData.response ||
|
|
703
|
+
processedData.result
|
|
704
|
+
) {
|
|
705
|
+
content =
|
|
706
|
+
processedData.content ||
|
|
707
|
+
processedData.text ||
|
|
708
|
+
processedData.response ||
|
|
709
|
+
processedData.result ||
|
|
710
|
+
''
|
|
711
|
+
reasoning = processedData.reasoning || processedData.reasoning_content || null
|
|
712
|
+
} else if (processedData.error) {
|
|
713
|
+
const errMsg =
|
|
714
|
+
typeof processedData.error === 'object'
|
|
715
|
+
? processedData.error.message || JSON.stringify(processedData.error)
|
|
716
|
+
: processedData.error
|
|
717
|
+
throw new Error(`API mengembalikan error: ${errMsg}`)
|
|
718
|
+
} else {
|
|
719
|
+
throw new Error(
|
|
720
|
+
`Format data API tidak dikenali atau kosong. Balasan mentah: ${JSON.stringify(processedData).slice(0, 150)}`
|
|
721
|
+
)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
if (!reasoning && content.includes('<think>')) {
|
|
725
|
+
const match = content.match(/<think>([\s\S]*?)<\/think>/)
|
|
726
|
+
if (match) {
|
|
727
|
+
reasoning = match[1].trim()
|
|
728
|
+
content = content.replace(/<think>[\s\S]*?<\/think>/, '').trim()
|
|
729
|
+
} else {
|
|
730
|
+
const openIdx = content.indexOf('<think>')
|
|
731
|
+
if (openIdx !== -1) {
|
|
732
|
+
reasoning = content.substring(openIdx + 7).trim()
|
|
733
|
+
content = content.substring(0, openIdx).trim()
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!content && reasoning) {
|
|
739
|
+
const firstBrace = reasoning.indexOf('{')
|
|
740
|
+
const lastBrace = reasoning.lastIndexOf('}')
|
|
741
|
+
if (firstBrace !== -1 && lastBrace > firstBrace) {
|
|
742
|
+
content = reasoning.substring(firstBrace, lastBrace + 1)
|
|
743
|
+
reasoning =
|
|
744
|
+
(reasoning.substring(0, firstBrace) + reasoning.substring(lastBrace + 1)).trim() || null
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return { content, reasoning, usage: processedData?.usage || null }
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (conf.aiProvider !== 'custom' && isLMStudioOfflineError(error)) {
|
|
751
|
+
throw createLMStudioOfflineError(error)
|
|
752
|
+
}
|
|
753
|
+
throw error
|
|
754
|
+
} finally {
|
|
755
|
+
activeAbortControllers.delete(parentAbortController)
|
|
756
|
+
}
|
|
757
|
+
}
|