onbuzz 4.9.13 → 4.10.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/node_modules/glob/README.md +31 -5
- package/node_modules/glob/dist/commonjs/glob.d.ts +8 -0
- package/node_modules/glob/dist/commonjs/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/glob.js +2 -1
- package/node_modules/glob/dist/commonjs/glob.js.map +1 -1
- package/node_modules/glob/dist/commonjs/index.min.js +3 -3
- package/node_modules/glob/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/glob/dist/commonjs/pattern.d.ts +3 -0
- package/node_modules/glob/dist/commonjs/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/commonjs/pattern.js +4 -0
- package/node_modules/glob/dist/commonjs/pattern.js.map +1 -1
- package/node_modules/glob/dist/esm/glob.d.ts +8 -0
- package/node_modules/glob/dist/esm/glob.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/glob.js +2 -1
- package/node_modules/glob/dist/esm/glob.js.map +1 -1
- package/node_modules/glob/dist/esm/index.min.js +3 -3
- package/node_modules/glob/dist/esm/index.min.js.map +4 -4
- package/node_modules/glob/dist/esm/pattern.d.ts +3 -0
- package/node_modules/glob/dist/esm/pattern.d.ts.map +1 -1
- package/node_modules/glob/dist/esm/pattern.js +4 -0
- package/node_modules/glob/dist/esm/pattern.js.map +1 -1
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/README.md +7 -10
- package/node_modules/{@isaacs → glob/node_modules}/balanced-match/package.json +7 -18
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/README.md +3 -6
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/commonjs/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.js +6 -4
- package/node_modules/glob/node_modules/brace-expansion/dist/esm/index.js.map +1 -0
- package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/package.json +11 -7
- package/node_modules/glob/node_modules/minimatch/README.md +76 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/commonjs/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/assert-valid-pattern.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts +4 -2
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js +309 -55
- package/node_modules/glob/node_modules/minimatch/dist/esm/ast.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js +2 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/brace-expressions.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js +4 -4
- package/node_modules/glob/node_modules/minimatch/dist/esm/escape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts +81 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js +232 -134
- package/node_modules/glob/node_modules/minimatch/dist/esm/index.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.d.ts.map +1 -1
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js +8 -8
- package/node_modules/glob/node_modules/minimatch/dist/esm/unescape.js.map +1 -1
- package/node_modules/glob/node_modules/minimatch/package.json +17 -11
- package/node_modules/glob/package.json +10 -13
- package/node_modules/minipass/LICENSE.md +55 -0
- package/node_modules/minipass/dist/commonjs/index.d.ts +12 -16
- package/node_modules/minipass/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/commonjs/index.js +13 -3
- package/node_modules/minipass/dist/commonjs/index.js.map +1 -1
- package/node_modules/minipass/dist/esm/index.d.ts +12 -16
- package/node_modules/minipass/dist/esm/index.d.ts.map +1 -1
- package/node_modules/minipass/dist/esm/index.js +3 -1
- package/node_modules/minipass/dist/esm/index.js.map +1 -1
- package/node_modules/minipass/package.json +9 -14
- package/node_modules/path-scurry/node_modules/lru-cache/README.md +96 -10
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.cjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel-cjs.d.cts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/diagnostics-channel.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js +334 -197
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/diagnostics-channel.js +9 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js +1726 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js +10 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/commonjs/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel-browser.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/diagnostics-channel.js +4 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/browser/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.d.mts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel-esm.mjs.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/diagnostics-channel.js +19 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts +109 -32
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js +333 -196
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js +1 -1
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/index.min.js.map +4 -4
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel-node.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.d.ts +5 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/diagnostics-channel.js +6 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts +1400 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js +1722 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js +2 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/index.min.js.map +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/node/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts +12 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.d.ts.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js +7 -0
- package/node_modules/path-scurry/node_modules/lru-cache/dist/esm/perf.js.map +1 -0
- package/node_modules/path-scurry/node_modules/lru-cache/package.json +71 -18
- package/node_modules/path-scurry/package.json +8 -24
- package/package.json +1 -1
- package/scripts/debug-balance-probe.mjs +35 -35
- package/scripts/push-image.sh +43 -43
- package/scripts/setup-acr.sh +65 -65
- package/scripts/verify-optional-deps.js +96 -1
- package/src/__tests__/composioCliFlags.test.js +239 -239
- package/src/analyzers/CSSAnalyzer.js +298 -297
- package/src/analyzers/ConfigValidator.js +691 -690
- package/src/analyzers/ESLintAnalyzer.js +320 -320
- package/src/analyzers/JavaScriptAnalyzer.js +260 -261
- package/src/analyzers/PrettierFormatter.js +246 -247
- package/src/analyzers/PythonAnalyzer.js +283 -283
- package/src/analyzers/SecurityAnalyzer.js +729 -729
- package/src/analyzers/SparrowAnalyzer.js +341 -341
- package/src/analyzers/TypeScriptAnalyzer.js +247 -247
- package/src/analyzers/__tests__/CSSAnalyzer.test.js +41 -41
- package/src/analyzers/__tests__/ConfigValidator.test.js +362 -362
- package/src/analyzers/__tests__/JavaScriptAnalyzer.test.js +40 -40
- package/src/analyzers/__tests__/PythonAnalyzer.test.js +205 -208
- package/src/analyzers/__tests__/SecurityAnalyzer.test.js +303 -303
- package/src/analyzers/__tests__/TypeScriptAnalyzer.test.js +187 -187
- package/src/analyzers/codeCloneDetector/analyzer.js +344 -344
- package/src/analyzers/codeCloneDetector/detector.js +250 -250
- package/src/analyzers/codeCloneDetector/index.js +194 -192
- package/src/analyzers/codeCloneDetector/parser.js +199 -199
- package/src/core/__tests__/agentPool.test.js +866 -866
- package/src/core/__tests__/agentPoolAutoResume.test.js +209 -209
- package/src/core/__tests__/agentPoolWakeOnMessage.test.js +315 -315
- package/src/core/__tests__/agentScheduler.emptyResponseChatStall.test.js +213 -213
- package/src/core/__tests__/agentScheduler.errorCategorisation.test.js +246 -246
- package/src/core/__tests__/agentScheduler.firstChunkTimeout.test.js +138 -138
- package/src/core/__tests__/agentScheduler.modeTransitions.test.js +233 -233
- package/src/core/__tests__/agentScheduler.nativePromptPick.test.js +319 -319
- package/src/core/__tests__/agentScheduler.taskLifecycleInstruction.test.js +78 -78
- package/src/core/__tests__/agentScheduler.visualizer.test.js +258 -258
- package/src/core/__tests__/flowCheckpointStore.test.js +140 -140
- package/src/core/__tests__/flowEndToEnd.test.js +565 -565
- package/src/core/__tests__/flowFieldMapping.test.js +188 -189
- package/src/core/__tests__/flowLintClientMirror.test.js +96 -98
- package/src/core/__tests__/flowSavePayload.test.js +170 -169
- package/src/core/__tests__/flowTemplates.test.js +311 -311
- package/src/core/__tests__/flowVersionStore.test.js +123 -123
- package/src/core/__tests__/messageProcessor.test.js +669 -669
- package/src/core/__tests__/stateManager.test.js +0 -1
- package/src/core/agentPool.js +2474 -2475
- package/src/core/agentScheduler.js +1 -4
- package/src/core/contextManager.js +708 -708
- package/src/core/flowExecutor.js +1510 -1510
- package/src/core/flowFieldMapping.js +136 -138
- package/src/core/messageProcessor.js +953 -954
- package/src/core/orchestrator.js +593 -595
- package/src/core/stateManager.js +1765 -1752
- package/src/index.js +1221 -1221
- package/src/interfaces/__tests__/archivedAgentDelete.test.js +207 -207
- package/src/interfaces/__tests__/bulkAgentRoute.test.js +361 -361
- package/src/interfaces/__tests__/imageServing.test.js +228 -228
- package/src/interfaces/__tests__/remoteSessionAuth.test.js +308 -308
- package/src/interfaces/__tests__/videoJobsRoutes.test.js +178 -179
- package/src/interfaces/__tests__/webServer.marketplace.test.js +629 -629
- package/src/interfaces/schedulerRoutes.js +50 -50
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +341 -350
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +156 -156
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +325 -330
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +385 -388
- package/src/interfaces/terminal/api/session.js +265 -266
- package/src/interfaces/terminal/api/websocket.js +496 -497
- package/src/interfaces/terminal/components/AgentCreator.js +691 -705
- package/src/interfaces/terminal/components/AgentEditor.js +676 -678
- package/src/interfaces/terminal/components/AgentSwitcher.js +331 -330
- package/src/interfaces/terminal/components/ErrorPanel.js +263 -264
- package/src/interfaces/terminal/components/Header.js +28 -28
- package/src/interfaces/terminal/components/Layout.js +598 -603
- package/src/interfaces/terminal/components/MessageList.js +280 -281
- package/src/interfaces/terminal/components/SettingsPanel.js +410 -415
- package/src/interfaces/terminal/components/StatusBar.js +2 -0
- package/src/interfaces/terminal/index.js +168 -168
- package/src/interfaces/terminal/state/useAgentControl.js +496 -496
- package/src/interfaces/terminal/state/useAgents.js +537 -537
- package/src/interfaces/terminal/state/useMessages.js +629 -630
- package/src/interfaces/terminal/state/useTools.js +554 -554
- package/src/interfaces/terminal/utils/debugLogger.js +44 -44
- package/src/interfaces/terminal/utils/settingsStorage.js +232 -232
- package/src/interfaces/webServer.js +7578 -7579
- package/src/interfaces/webServer.js.bak +7046 -7046
- package/src/modules/fileExplorer/__tests__/zipDownload.test.js +237 -237
- package/src/modules/fileExplorer/controller.js +470 -469
- package/src/modules/fileExplorer/routes.js +285 -286
- package/src/modules/widget/__tests__/isDisabled.test.js +41 -41
- package/src/modules/widget/__tests__/routes.test.js +677 -678
- package/src/modules/widget/__tests__/runtime.test.js +401 -401
- package/src/modules/widget/__tests__/versioning.test.js +309 -309
- package/src/modules/widget/__tests__/webComponentRuntime.test.js +565 -565
- package/src/modules/widget/__tests__/widgetTool.test.js +316 -316
- package/src/modules/widget/routes.js +435 -435
- package/src/modules/widget/runtime/bundle.js +640 -640
- package/src/modules/widget/runtime/webComponentBundle.js +470 -470
- package/src/modules/widget/schema.js +182 -181
- package/src/modules/widget/widgetTool.js +1389 -1389
- package/src/services/__tests__/agentActivityService.test.js +401 -402
- package/src/services/__tests__/benchmarkService.test.js +184 -184
- package/src/services/__tests__/contextInjectionService.test.js +246 -246
- package/src/services/__tests__/conversationQuery.test.js +721 -723
- package/src/services/__tests__/credentialVault.test.js +469 -469
- package/src/services/__tests__/discordService.integration.test.js +638 -639
- package/src/services/__tests__/flowContextService.test.js +590 -590
- package/src/services/__tests__/memoryService.test.js +1 -1
- package/src/services/__tests__/messageSource.test.js +380 -380
- package/src/services/__tests__/modelRouterNaming.test.js +111 -111
- package/src/services/__tests__/projectDetector.test.js +34 -34
- package/src/services/__tests__/promptService.test.js +242 -242
- package/src/services/__tests__/telegramService.test.js +941 -941
- package/src/services/__tests__/tokenCountingService.test.js +48 -48
- package/src/services/agentActivityService.js +419 -420
- package/src/services/aiService.js +2997 -3001
- package/src/services/apiKeyManager.js +359 -359
- package/src/services/benchmarkService.js +196 -196
- package/src/services/codebaseKnowledgeService.js +2 -2
- package/src/services/composioService.js +738 -738
- package/src/services/conversationCompactionService.js +1258 -1257
- package/src/services/credentialVault.js +685 -685
- package/src/services/discordService.js +792 -793
- package/src/services/embeddings/__tests__/azureCustomProvider.test.js +232 -232
- package/src/services/embeddings/__tests__/embeddingService.test.js +417 -417
- package/src/services/embeddings/__tests__/localProvider.test.js +263 -263
- package/src/services/embeddings/autoRecall.js +218 -219
- package/src/services/embeddings/indexers/__tests__/agentIndexer.test.js +232 -232
- package/src/services/embeddings/indexers/__tests__/memoryIndexer.test.js +418 -418
- package/src/services/embeddings/indexers/__tests__/reminisceIndexer.test.js +356 -357
- package/src/services/embeddings/indexers/__tests__/skillsIndexer.test.js +145 -145
- package/src/services/embeddings/indexers/__tests__/taskIndexer.test.js +146 -146
- package/src/services/embeddings/indexers/composioIndexer.js +279 -279
- package/src/services/embeddings/providerInterface.js +206 -206
- package/src/services/embeddings/providers/localProvider.js +11 -7
- package/src/services/embeddings/providers/openaiProvider.js +101 -101
- package/src/services/embeddings/vectorStore/inMemoryJsonStore.js +356 -356
- package/src/services/errorHandler.js +809 -809
- package/src/services/flowContextService.js +586 -586
- package/src/services/grounding/MockAdapter.js +125 -125
- package/src/services/modelRouterService.js +26 -31
- package/src/services/modelsService.js +322 -322
- package/src/services/ollamaService.js +452 -452
- package/src/services/projectDetector.js +403 -404
- package/src/services/promptService.js +418 -418
- package/src/services/qualityInspector.js +795 -795
- package/src/services/scheduleService.js +726 -726
- package/src/services/serviceRegistry.js +386 -386
- package/src/services/telegrafBot.js +174 -174
- package/src/services/telegramService.js +1972 -1972
- package/src/services/visualEditorBridge.js +1033 -1033
- package/src/services/visualEditorServer.js +1769 -1774
- package/src/services/whatsappService.js +667 -668
- package/src/tools/__tests__/agentCommunicationTool.findAgent.test.js +226 -226
- package/src/tools/__tests__/agentCommunicationTool.test.js +3 -3
- package/src/tools/__tests__/agentDelayTool.test.js +342 -342
- package/src/tools/__tests__/baseTool.test.js +3 -3
- package/src/tools/__tests__/codeMapTool.test.js +915 -915
- package/src/tools/__tests__/fileContentReplaceTool.test.js +309 -309
- package/src/tools/__tests__/fileTreeTool.test.js +274 -274
- package/src/tools/__tests__/filesystemTool.test.js +815 -815
- package/src/tools/__tests__/foundryWebSearchTool.test.js +252 -252
- package/src/tools/__tests__/imageTool.validator.test.js +194 -194
- package/src/tools/__tests__/jobDoneTool.test.js +580 -581
- package/src/tools/__tests__/memoryTool.forgetStale.test.js +272 -272
- package/src/tools/__tests__/memoryTool.reminisce.test.js +2 -2
- package/src/tools/__tests__/memoryTool.reminisceSemanticSearch.test.js +301 -301
- package/src/tools/__tests__/memoryTool.semanticSearch.test.js +405 -405
- package/src/tools/__tests__/memoryTool.teamPool.test.js +293 -293
- package/src/tools/__tests__/memoryTool.test.js +1 -1
- package/src/tools/__tests__/seekTool.test.js +282 -282
- package/src/tools/__tests__/skillsTool.search.test.js +164 -164
- package/src/tools/__tests__/skillsTool.test.js +226 -226
- package/src/tools/__tests__/staticAnalysisTool.test.js +509 -509
- package/src/tools/__tests__/taskManagerTool.discipline.test.js +137 -137
- package/src/tools/__tests__/taskManagerTool.search.test.js +143 -143
- package/src/tools/__tests__/taskManagerTool.test.js +866 -866
- package/src/tools/__tests__/terminalTool.test.js +448 -448
- package/src/tools/__tests__/toolShapeForgiveness.test.js +259 -260
- package/src/tools/__tests__/userPromptTool.test.js +297 -297
- package/src/tools/__tests__/videoTool.jobs.test.js +147 -147
- package/src/tools/__tests__/webTool.e2e.test.js +609 -603
- package/src/tools/__tests__/webTool.unit.test.js +195 -195
- package/src/tools/__tests__/webTool.visionModel.test.js +75 -75
- package/src/tools/agentCommunicationTool.js +8 -10
- package/src/tools/agentDelayTool.js +496 -497
- package/src/tools/asyncToolManager.js +602 -603
- package/src/tools/baseTool.js +12 -11
- package/src/tools/cloneDetectionTool.js +576 -581
- package/src/tools/codeMapTool.js +0 -6
- package/src/tools/composioTool.js +617 -617
- package/src/tools/dependencyResolverTool.js +1211 -1212
- package/src/tools/desktop/DesktopTool.js +629 -638
- package/src/tools/desktop/__tests__/DesktopTool.e2e.test.js +306 -306
- package/src/tools/desktop/__tests__/DesktopTool.test.js +507 -507
- package/src/tools/desktop/__tests__/osController.test.js +364 -364
- package/src/tools/desktop/osController.js +491 -491
- package/src/tools/docxTool.js +623 -623
- package/src/tools/excelTool.js +636 -636
- package/src/tools/fileContentReplaceTool.js +5 -7
- package/src/tools/fileSystemTool.js +12 -19
- package/src/tools/fileTreeTool.js +840 -840
- package/src/tools/foundryWebSearchTool.js +273 -273
- package/src/tools/helpTool.js +198 -198
- package/src/tools/imageTool.js +1397 -1397
- package/src/tools/importAnalyzerTool.js +1056 -1056
- package/src/tools/jobDoneTool.js +495 -495
- package/src/tools/memoryTool.js +1 -1
- package/src/tools/office/pres/__tests__/presSystem.test.js +365 -365
- package/src/tools/office/pres/archetypes/agenda.js +61 -61
- package/src/tools/office/pres/archetypes/bentoGrid.js +218 -219
- package/src/tools/office/pres/archetypes/bigStat.js +140 -142
- package/src/tools/office/pres/archetypes/closing.js +70 -70
- package/src/tools/office/pres/archetypes/hero.js +70 -70
- package/src/tools/office/pres/archetypes/productHero.js +93 -94
- package/src/tools/office/pres/archetypes/table.js +73 -74
- package/src/tools/office/pres/backgrounds/orb.js +66 -66
- package/src/tools/office/pres/components.js +422 -423
- package/src/tools/officeTool.js +441 -441
- package/src/tools/pdfTool.js +625 -627
- package/src/tools/platformControlTool.js +1081 -1081
- package/src/tools/seekTool.js +917 -918
- package/src/tools/skillsTool.js +1 -1
- package/src/tools/staticAnalysisTool.js +2143 -2146
- package/src/tools/taskManagerTool.js +3324 -3324
- package/src/tools/terminalTool.js +2615 -2618
- package/src/tools/videoTool.js +1303 -1303
- package/src/tools/visionTool.js +508 -508
- package/src/tools/visualEditorTool.js +1289 -1290
- package/src/tools/webTool.js +3368 -3368
- package/src/tools/whatsappTool.js +464 -464
- package/src/types/__tests__/agent.test.js +499 -499
- package/src/types/__tests__/contextReference.test.js +606 -606
- package/src/types/__tests__/conversation.test.js +555 -555
- package/src/types/__tests__/toolCommand.test.js +584 -584
- package/src/types/contextReference.js +974 -971
- package/src/types/conversation.js +729 -729
- package/src/types/toolCommand.js +746 -746
- package/src/utilities/__tests__/attachmentValidator.test.js +80 -80
- package/src/utilities/__tests__/auditReport.test.js +328 -328
- package/src/utilities/__tests__/directoryAccessManager.test.js +388 -388
- package/src/utilities/__tests__/jsonRepair.test.js +103 -104
- package/src/utilities/__tests__/modeTransitionReasons.test.js +105 -105
- package/src/utilities/__tests__/platformUtils.test.js +80 -87
- package/src/utilities/__tests__/structuredFileValidator.test.js +261 -263
- package/src/utilities/__tests__/toolConstants.test.js +92 -94
- package/src/utilities/__tests__/useIsTouchDevice.detect.test.js +114 -114
- package/src/utilities/__tests__/webUiUtilSync.test.js +117 -117
- package/src/utilities/attachmentValidator.js +284 -288
- package/src/utilities/authCache.js.backup-1779570472481 +121 -121
- package/src/utilities/browserStealth.js +631 -630
- package/src/utilities/configManager.js +616 -617
- package/src/utilities/directoryAccessManager.js +564 -565
- package/src/utilities/fileProcessor.js +308 -307
- package/src/utilities/humanBehavior.js +454 -453
- package/src/utilities/logger.js +479 -479
- package/src/utilities/structuredFileValidator.js +696 -699
- package/src/utilities/tagParser.js +5 -10
- package/src/utilities/userDataDir.js +308 -308
- package/node_modules/@isaacs/brace-expansion/dist/commonjs/index.js.map +0 -1
- package/node_modules/@isaacs/brace-expansion/dist/esm/index.js.map +0 -1
- package/node_modules/minipass/LICENSE +0 -15
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/LICENSE.md +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/index.js.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/balanced-match/dist/esm/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/LICENSE +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/commonjs/package.json +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/index.d.ts.map +0 -0
- /package/node_modules/{@isaacs → glob/node_modules}/brace-expansion/dist/esm/package.json +0 -0
|
@@ -1,452 +1,452 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file ollamaService.js
|
|
3
|
-
* @description Ollama integration service for local/offline LLM inference.
|
|
4
|
-
* Wraps the ollama-js library to provide chat, streaming, and model discovery
|
|
5
|
-
* capabilities that integrate with the existing AI service architecture.
|
|
6
|
-
*
|
|
7
|
-
* Ollama models are free to use (no billing) and run entirely offline.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Ollama } from 'ollama';
|
|
11
|
-
|
|
12
|
-
const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434';
|
|
13
|
-
const MODEL_CACHE_TTL = 60_000; // 1 minute cache for model list
|
|
14
|
-
const HEALTH_CHECK_TIMEOUT = 3000; // 3 second timeout for health check
|
|
15
|
-
const OLLAMA_MODEL_PREFIX = 'ollama-';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* OllamaService - Local LLM inference via Ollama
|
|
19
|
-
*/
|
|
20
|
-
class OllamaService {
|
|
21
|
-
constructor(config = {}, logger = null) {
|
|
22
|
-
this.logger = logger;
|
|
23
|
-
this.host = config.ollamaHost || DEFAULT_OLLAMA_HOST;
|
|
24
|
-
this.client = new Ollama({ host: this.host });
|
|
25
|
-
this.enabled = config.ollamaEnabled !== false; // Enabled by default
|
|
26
|
-
|
|
27
|
-
// Model cache
|
|
28
|
-
this._modelCache = null;
|
|
29
|
-
this._modelCacheTime = 0;
|
|
30
|
-
|
|
31
|
-
// Connection state
|
|
32
|
-
this._isAvailable = null; // null = unknown
|
|
33
|
-
this._lastHealthCheck = 0;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Update Ollama host URL
|
|
38
|
-
*/
|
|
39
|
-
setHost(host) {
|
|
40
|
-
this.host = host || DEFAULT_OLLAMA_HOST;
|
|
41
|
-
this.client = new Ollama({ host: this.host });
|
|
42
|
-
this._isAvailable = null;
|
|
43
|
-
this._modelCache = null;
|
|
44
|
-
this.logger?.info(`[Ollama] Host updated to ${this.host}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if Ollama server is reachable
|
|
49
|
-
*/
|
|
50
|
-
async isAvailable() {
|
|
51
|
-
// Cache health check for 10 seconds
|
|
52
|
-
if (this._isAvailable !== null && Date.now() - this._lastHealthCheck < 10_000) {
|
|
53
|
-
return this._isAvailable;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const controller = new AbortController();
|
|
58
|
-
const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT);
|
|
59
|
-
const response = await fetch(`${this.host}/api/version`, {
|
|
60
|
-
signal: controller.signal
|
|
61
|
-
});
|
|
62
|
-
clearTimeout(timeoutId);
|
|
63
|
-
this._isAvailable = response.ok;
|
|
64
|
-
} catch {
|
|
65
|
-
this._isAvailable = false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
this._lastHealthCheck = Date.now();
|
|
69
|
-
return this._isAvailable;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* List available local models from Ollama
|
|
74
|
-
* @returns {Array<Object>} Model list with normalized specs
|
|
75
|
-
*/
|
|
76
|
-
async listModels() {
|
|
77
|
-
// Return cached if fresh
|
|
78
|
-
if (this._modelCache && Date.now() - this._modelCacheTime < MODEL_CACHE_TTL) {
|
|
79
|
-
return this._modelCache;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!this.enabled) return [];
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
const available = await this.isAvailable();
|
|
86
|
-
if (!available) return [];
|
|
87
|
-
|
|
88
|
-
const response = await this.client.list();
|
|
89
|
-
const models = (response.models || []).map(m => this._normalizeModel(m));
|
|
90
|
-
|
|
91
|
-
this._modelCache = models;
|
|
92
|
-
this._modelCacheTime = Date.now();
|
|
93
|
-
|
|
94
|
-
this.logger?.info(`[Ollama] Discovered ${models.length} local models`);
|
|
95
|
-
return models;
|
|
96
|
-
} catch (err) {
|
|
97
|
-
this.logger?.warn(`[Ollama] Failed to list models: ${err.message}`);
|
|
98
|
-
return this._modelCache || [];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Normalize Ollama model info to match platform model schema
|
|
104
|
-
* @private
|
|
105
|
-
*/
|
|
106
|
-
_normalizeModel(ollamaModel) {
|
|
107
|
-
const name = ollamaModel.name; // e.g. "llama3.1:8b", "codellama:13b"
|
|
108
|
-
const modelId = OLLAMA_MODEL_PREFIX + name.replace(/[:/]/g, '-'); // ollama-llama3.1-8b
|
|
109
|
-
const details = ollamaModel.details || {};
|
|
110
|
-
const sizeGB = ollamaModel.size ? (ollamaModel.size / 1e9).toFixed(1) : '?';
|
|
111
|
-
|
|
112
|
-
// Estimate context window from parameter size and quantization
|
|
113
|
-
const paramSize = details.parameter_size || '';
|
|
114
|
-
const contextWindow = this._estimateContextWindow(paramSize, name);
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
name: modelId,
|
|
118
|
-
ollamaName: name, // Original name for API calls
|
|
119
|
-
displayName: `${name} (${sizeGB}GB)`,
|
|
120
|
-
provider: 'ollama',
|
|
121
|
-
type: 'chat',
|
|
122
|
-
api_type: ['chat'],
|
|
123
|
-
contextWindow,
|
|
124
|
-
maxTokens: Math.min(4096, Math.floor(contextWindow * 0.25)),
|
|
125
|
-
pricing: { input: 0, output: 0 }, // Free - local inference
|
|
126
|
-
deprecated: false,
|
|
127
|
-
available: true,
|
|
128
|
-
local: true,
|
|
129
|
-
offline: true,
|
|
130
|
-
details: {
|
|
131
|
-
family: details.family || null,
|
|
132
|
-
parameterSize: paramSize,
|
|
133
|
-
quantization: details.quantization_level || null,
|
|
134
|
-
format: details.format || null,
|
|
135
|
-
sizeBytes: ollamaModel.size || 0
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Estimate context window based on model info
|
|
142
|
-
* @private
|
|
143
|
-
*/
|
|
144
|
-
_estimateContextWindow(paramSize, name) {
|
|
145
|
-
// Known models with specific context windows
|
|
146
|
-
const nameLower = name.toLowerCase();
|
|
147
|
-
if (nameLower.includes('llama3') || nameLower.includes('llama-3')) return 128000;
|
|
148
|
-
if (nameLower.includes('llama2') || nameLower.includes('llama-2')) return 4096;
|
|
149
|
-
if (nameLower.includes('mistral')) return 32768;
|
|
150
|
-
if (nameLower.includes('mixtral')) return 32768;
|
|
151
|
-
if (nameLower.includes('phi')) return 16384;
|
|
152
|
-
if (nameLower.includes('gemma2') || nameLower.includes('gemma-2')) return 8192;
|
|
153
|
-
if (nameLower.includes('qwen')) return 32768;
|
|
154
|
-
if (nameLower.includes('deepseek')) return 128000;
|
|
155
|
-
if (nameLower.includes('codellama')) return 16384;
|
|
156
|
-
if (nameLower.includes('command-r')) return 128000;
|
|
157
|
-
|
|
158
|
-
// Default based on parameter size
|
|
159
|
-
const sizeMatch = paramSize.match(/([\d.]+)/);
|
|
160
|
-
if (sizeMatch) {
|
|
161
|
-
const params = parseFloat(sizeMatch[1]);
|
|
162
|
-
if (params >= 70) return 8192;
|
|
163
|
-
if (params >= 13) return 8192;
|
|
164
|
-
return 4096;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return 4096; // Conservative default
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Send a chat completion request to Ollama
|
|
172
|
-
* @param {string} modelId - Normalized model ID (ollama-xxx)
|
|
173
|
-
* @param {Array} messages - Conversation messages [{role, content}]
|
|
174
|
-
* @param {Object} options - Options (temperature, maxTokens, systemPrompt)
|
|
175
|
-
* @returns {Object} Response matching AIService format
|
|
176
|
-
*/
|
|
177
|
-
async sendMessage(modelId, messages, options = {}) {
|
|
178
|
-
const ollamaName = this._resolveModelName(modelId);
|
|
179
|
-
|
|
180
|
-
// Build message array with system prompt
|
|
181
|
-
const ollamaMessages = [];
|
|
182
|
-
if (options.systemPrompt) {
|
|
183
|
-
ollamaMessages.push({ role: 'system', content: options.systemPrompt });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
for (const msg of messages) {
|
|
187
|
-
ollamaMessages.push({
|
|
188
|
-
role: msg.role === 'assistant' ? 'assistant' : (msg.role === 'system' ? 'system' : 'user'),
|
|
189
|
-
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const startTime = Date.now();
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
const response = await this.client.chat({
|
|
197
|
-
model: ollamaName,
|
|
198
|
-
messages: ollamaMessages,
|
|
199
|
-
stream: false,
|
|
200
|
-
options: {
|
|
201
|
-
temperature: options.temperature ?? 0.7,
|
|
202
|
-
num_predict: options.maxTokens || 4096,
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const duration = Date.now() - startTime;
|
|
207
|
-
|
|
208
|
-
// Estimate token counts from Ollama's eval metrics
|
|
209
|
-
const promptTokens = response.prompt_eval_count || this._estimateTokens(ollamaMessages);
|
|
210
|
-
const completionTokens = response.eval_count || this._estimateTokens([{ content: response.message?.content || '' }]);
|
|
211
|
-
|
|
212
|
-
this.logger?.info(`[Ollama] ${ollamaName} responded in ${duration}ms (${promptTokens}+${completionTokens} tokens)`);
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
choices: [{
|
|
216
|
-
message: { content: response.message?.content || '' },
|
|
217
|
-
finish_reason: response.done ? 'stop' : 'length'
|
|
218
|
-
}],
|
|
219
|
-
model: modelId,
|
|
220
|
-
usage: {
|
|
221
|
-
prompt_tokens: promptTokens,
|
|
222
|
-
completion_tokens: completionTokens,
|
|
223
|
-
total_tokens: promptTokens + completionTokens
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
} catch (err) {
|
|
227
|
-
this.logger?.error(`[Ollama] Chat error for ${ollamaName}: ${err.message}`);
|
|
228
|
-
throw new Error(`Ollama error (${ollamaName}): ${err.message}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Send a streaming chat request to Ollama
|
|
234
|
-
* @param {string} modelId - Normalized model ID
|
|
235
|
-
* @param {Array} messages - Conversation messages
|
|
236
|
-
* @param {Object} options - Options including onChunk, onDone, onError callbacks
|
|
237
|
-
* @returns {Object} Final response with content and metadata
|
|
238
|
-
*/
|
|
239
|
-
async sendMessageStream(modelId, messages, options = {}) {
|
|
240
|
-
const ollamaName = this._resolveModelName(modelId);
|
|
241
|
-
const { onChunk, onDone, onError } = options;
|
|
242
|
-
|
|
243
|
-
// Build message array
|
|
244
|
-
const ollamaMessages = [];
|
|
245
|
-
if (options.systemPrompt) {
|
|
246
|
-
ollamaMessages.push({ role: 'system', content: options.systemPrompt });
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
for (const msg of messages) {
|
|
250
|
-
ollamaMessages.push({
|
|
251
|
-
role: msg.role === 'assistant' ? 'assistant' : (msg.role === 'system' ? 'system' : 'user'),
|
|
252
|
-
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const startTime = Date.now();
|
|
257
|
-
let fullContent = '';
|
|
258
|
-
let promptTokens = 0;
|
|
259
|
-
let completionTokens = 0;
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const stream = await this.client.chat({
|
|
263
|
-
model: ollamaName,
|
|
264
|
-
messages: ollamaMessages,
|
|
265
|
-
stream: true,
|
|
266
|
-
options: {
|
|
267
|
-
temperature: options.temperature ?? 0.7,
|
|
268
|
-
num_predict: options.maxTokens || 4096,
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
if (!stream) {
|
|
273
|
-
throw new Error(`Ollama returned no stream for model "${ollamaName}". The model may not be loaded or available.`);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
for await (const chunk of stream) {
|
|
277
|
-
const content = chunk.message?.content || '';
|
|
278
|
-
if (content) {
|
|
279
|
-
fullContent += content;
|
|
280
|
-
if (onChunk) {
|
|
281
|
-
onChunk({ content, type: 'chunk' });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Final chunk has eval counts
|
|
286
|
-
if (chunk.done) {
|
|
287
|
-
promptTokens = chunk.prompt_eval_count || this._estimateTokens(ollamaMessages);
|
|
288
|
-
completionTokens = chunk.eval_count || this._estimateTokens([{ content: fullContent }]);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const duration = Date.now() - startTime;
|
|
293
|
-
const usage = {
|
|
294
|
-
prompt_tokens: promptTokens,
|
|
295
|
-
completion_tokens: completionTokens,
|
|
296
|
-
total_tokens: promptTokens + completionTokens
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
this.logger?.info(`[Ollama] ${ollamaName} streamed in ${duration}ms (${usage.total_tokens} tokens)`);
|
|
300
|
-
|
|
301
|
-
const result = {
|
|
302
|
-
content: fullContent,
|
|
303
|
-
model: modelId,
|
|
304
|
-
tokenUsage: usage,
|
|
305
|
-
finishReason: 'stop'
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
if (onDone) {
|
|
309
|
-
onDone(result);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return result;
|
|
313
|
-
} catch (err) {
|
|
314
|
-
this.logger?.error(`[Ollama] Stream error for ${ollamaName}: ${err.message}`);
|
|
315
|
-
if (onError) {
|
|
316
|
-
onError(err);
|
|
317
|
-
}
|
|
318
|
-
throw new Error(`Ollama streaming error (${ollamaName}): ${err.message}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
/**
|
|
323
|
-
* Resolve normalized model ID back to Ollama model name
|
|
324
|
-
* @private
|
|
325
|
-
*/
|
|
326
|
-
_resolveModelName(modelId) {
|
|
327
|
-
// If it has the prefix, strip it and restore colons
|
|
328
|
-
if (modelId.startsWith(OLLAMA_MODEL_PREFIX)) {
|
|
329
|
-
const stripped = modelId.slice(OLLAMA_MODEL_PREFIX.length);
|
|
330
|
-
// Restore the last dash to colon for tag separator (e.g., llama3.1-8b → llama3.1:8b)
|
|
331
|
-
// Use cached model list for accurate mapping
|
|
332
|
-
if (this._modelCache) {
|
|
333
|
-
const cached = this._modelCache.find(m => m.name === modelId);
|
|
334
|
-
if (cached) return cached.ollamaName;
|
|
335
|
-
}
|
|
336
|
-
// Fallback: restore last dash before a version-like segment to colon
|
|
337
|
-
return stripped.replace(/-(\d+[bgBG]?)$/, ':$1')
|
|
338
|
-
.replace(/-latest$/, ':latest');
|
|
339
|
-
}
|
|
340
|
-
return modelId;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Estimate token count from messages (fallback when Ollama doesn't report)
|
|
345
|
-
* @private
|
|
346
|
-
*/
|
|
347
|
-
_estimateTokens(messages) {
|
|
348
|
-
let chars = 0;
|
|
349
|
-
for (const msg of messages) {
|
|
350
|
-
const content = msg.content || '';
|
|
351
|
-
chars += typeof content === 'string' ? content.length : JSON.stringify(content).length;
|
|
352
|
-
}
|
|
353
|
-
return Math.ceil(chars / 3.5); // ~3.5 chars per token average
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Check if a model ID is an Ollama model
|
|
358
|
-
* @param {string} modelId - Model identifier
|
|
359
|
-
* @returns {boolean}
|
|
360
|
-
*/
|
|
361
|
-
static isOllamaModel(modelId) {
|
|
362
|
-
return typeof modelId === 'string' && modelId.startsWith(OLLAMA_MODEL_PREFIX);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* Pull a model from Ollama registry
|
|
367
|
-
* @param {string} modelName - Model to pull (e.g., "llama3.1:8b")
|
|
368
|
-
* @param {Function} onProgress - Progress callback
|
|
369
|
-
*/
|
|
370
|
-
async pullModel(modelName, onProgress = null) {
|
|
371
|
-
this.logger?.info(`[Ollama] Pulling model: ${modelName}`);
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
const stream = await this.client.pull({ model: modelName, stream: true });
|
|
375
|
-
|
|
376
|
-
if (!stream) {
|
|
377
|
-
throw new Error(`Ollama returned no stream when pulling "${modelName}". Check that Ollama is running.`);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
for await (const progress of stream) {
|
|
381
|
-
if (onProgress) {
|
|
382
|
-
onProgress({
|
|
383
|
-
status: progress.status,
|
|
384
|
-
total: progress.total,
|
|
385
|
-
completed: progress.completed,
|
|
386
|
-
percent: progress.total ? Math.round((progress.completed / progress.total) * 100) : null
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Invalidate model cache
|
|
392
|
-
this._modelCache = null;
|
|
393
|
-
this.logger?.info(`[Ollama] Successfully pulled ${modelName}`);
|
|
394
|
-
return { success: true, model: modelName };
|
|
395
|
-
} catch (err) {
|
|
396
|
-
this.logger?.error(`[Ollama] Pull failed for ${modelName}: ${err.message}`);
|
|
397
|
-
throw err;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Delete a model from Ollama
|
|
403
|
-
* @param {string} modelName - Model to delete
|
|
404
|
-
*/
|
|
405
|
-
async deleteModel(modelName) {
|
|
406
|
-
try {
|
|
407
|
-
await this.client.delete({ model: modelName });
|
|
408
|
-
this._modelCache = null;
|
|
409
|
-
this.logger?.info(`[Ollama] Deleted model: ${modelName}`);
|
|
410
|
-
return { success: true };
|
|
411
|
-
} catch (err) {
|
|
412
|
-
this.logger?.error(`[Ollama] Delete failed for ${modelName}: ${err.message}`);
|
|
413
|
-
throw err;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Get model info from Ollama
|
|
419
|
-
* @param {string} modelName - Model name
|
|
420
|
-
*/
|
|
421
|
-
async getModelInfo(modelName) {
|
|
422
|
-
try {
|
|
423
|
-
const info = await this.client.show({ model: modelName });
|
|
424
|
-
return {
|
|
425
|
-
success: true,
|
|
426
|
-
info: {
|
|
427
|
-
name: modelName,
|
|
428
|
-
license: info.license,
|
|
429
|
-
modelfile: info.modelfile,
|
|
430
|
-
parameters: info.parameters,
|
|
431
|
-
template: info.template,
|
|
432
|
-
details: info.details
|
|
433
|
-
}
|
|
434
|
-
};
|
|
435
|
-
} catch (err) {
|
|
436
|
-
return { success: false, error: err.message };
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Singleton
|
|
442
|
-
let instance = null;
|
|
443
|
-
|
|
444
|
-
export function getOllamaService(config = {}, logger = null) {
|
|
445
|
-
if (!instance) {
|
|
446
|
-
instance = new OllamaService(config, logger);
|
|
447
|
-
}
|
|
448
|
-
return instance;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export { OllamaService, OLLAMA_MODEL_PREFIX };
|
|
452
|
-
export default OllamaService;
|
|
1
|
+
/**
|
|
2
|
+
* @file ollamaService.js
|
|
3
|
+
* @description Ollama integration service for local/offline LLM inference.
|
|
4
|
+
* Wraps the ollama-js library to provide chat, streaming, and model discovery
|
|
5
|
+
* capabilities that integrate with the existing AI service architecture.
|
|
6
|
+
*
|
|
7
|
+
* Ollama models are free to use (no billing) and run entirely offline.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Ollama } from 'ollama';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_OLLAMA_HOST = 'http://127.0.0.1:11434';
|
|
13
|
+
const MODEL_CACHE_TTL = 60_000; // 1 minute cache for model list
|
|
14
|
+
const HEALTH_CHECK_TIMEOUT = 3000; // 3 second timeout for health check
|
|
15
|
+
const OLLAMA_MODEL_PREFIX = 'ollama-';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* OllamaService - Local LLM inference via Ollama
|
|
19
|
+
*/
|
|
20
|
+
class OllamaService {
|
|
21
|
+
constructor(config = {}, logger = null) {
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
this.host = config.ollamaHost || DEFAULT_OLLAMA_HOST;
|
|
24
|
+
this.client = new Ollama({ host: this.host });
|
|
25
|
+
this.enabled = config.ollamaEnabled !== false; // Enabled by default
|
|
26
|
+
|
|
27
|
+
// Model cache
|
|
28
|
+
this._modelCache = null;
|
|
29
|
+
this._modelCacheTime = 0;
|
|
30
|
+
|
|
31
|
+
// Connection state
|
|
32
|
+
this._isAvailable = null; // null = unknown
|
|
33
|
+
this._lastHealthCheck = 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Update Ollama host URL
|
|
38
|
+
*/
|
|
39
|
+
setHost(host) {
|
|
40
|
+
this.host = host || DEFAULT_OLLAMA_HOST;
|
|
41
|
+
this.client = new Ollama({ host: this.host });
|
|
42
|
+
this._isAvailable = null;
|
|
43
|
+
this._modelCache = null;
|
|
44
|
+
this.logger?.info(`[Ollama] Host updated to ${this.host}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if Ollama server is reachable
|
|
49
|
+
*/
|
|
50
|
+
async isAvailable() {
|
|
51
|
+
// Cache health check for 10 seconds
|
|
52
|
+
if (this._isAvailable !== null && Date.now() - this._lastHealthCheck < 10_000) {
|
|
53
|
+
return this._isAvailable;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
const timeoutId = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT);
|
|
59
|
+
const response = await fetch(`${this.host}/api/version`, {
|
|
60
|
+
signal: controller.signal
|
|
61
|
+
});
|
|
62
|
+
clearTimeout(timeoutId);
|
|
63
|
+
this._isAvailable = response.ok;
|
|
64
|
+
} catch {
|
|
65
|
+
this._isAvailable = false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this._lastHealthCheck = Date.now();
|
|
69
|
+
return this._isAvailable;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* List available local models from Ollama
|
|
74
|
+
* @returns {Array<Object>} Model list with normalized specs
|
|
75
|
+
*/
|
|
76
|
+
async listModels() {
|
|
77
|
+
// Return cached if fresh
|
|
78
|
+
if (this._modelCache && Date.now() - this._modelCacheTime < MODEL_CACHE_TTL) {
|
|
79
|
+
return this._modelCache;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!this.enabled) return [];
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const available = await this.isAvailable();
|
|
86
|
+
if (!available) return [];
|
|
87
|
+
|
|
88
|
+
const response = await this.client.list();
|
|
89
|
+
const models = (response.models || []).map(m => this._normalizeModel(m));
|
|
90
|
+
|
|
91
|
+
this._modelCache = models;
|
|
92
|
+
this._modelCacheTime = Date.now();
|
|
93
|
+
|
|
94
|
+
this.logger?.info(`[Ollama] Discovered ${models.length} local models`);
|
|
95
|
+
return models;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
this.logger?.warn(`[Ollama] Failed to list models: ${err.message}`);
|
|
98
|
+
return this._modelCache || [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Normalize Ollama model info to match platform model schema
|
|
104
|
+
* @private
|
|
105
|
+
*/
|
|
106
|
+
_normalizeModel(ollamaModel) {
|
|
107
|
+
const name = ollamaModel.name; // e.g. "llama3.1:8b", "codellama:13b"
|
|
108
|
+
const modelId = OLLAMA_MODEL_PREFIX + name.replace(/[:/]/g, '-'); // ollama-llama3.1-8b
|
|
109
|
+
const details = ollamaModel.details || {};
|
|
110
|
+
const sizeGB = ollamaModel.size ? (ollamaModel.size / 1e9).toFixed(1) : '?';
|
|
111
|
+
|
|
112
|
+
// Estimate context window from parameter size and quantization
|
|
113
|
+
const paramSize = details.parameter_size || '';
|
|
114
|
+
const contextWindow = this._estimateContextWindow(paramSize, name);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
name: modelId,
|
|
118
|
+
ollamaName: name, // Original name for API calls
|
|
119
|
+
displayName: `${name} (${sizeGB}GB)`,
|
|
120
|
+
provider: 'ollama',
|
|
121
|
+
type: 'chat',
|
|
122
|
+
api_type: ['chat'],
|
|
123
|
+
contextWindow,
|
|
124
|
+
maxTokens: Math.min(4096, Math.floor(contextWindow * 0.25)),
|
|
125
|
+
pricing: { input: 0, output: 0 }, // Free - local inference
|
|
126
|
+
deprecated: false,
|
|
127
|
+
available: true,
|
|
128
|
+
local: true,
|
|
129
|
+
offline: true,
|
|
130
|
+
details: {
|
|
131
|
+
family: details.family || null,
|
|
132
|
+
parameterSize: paramSize,
|
|
133
|
+
quantization: details.quantization_level || null,
|
|
134
|
+
format: details.format || null,
|
|
135
|
+
sizeBytes: ollamaModel.size || 0
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Estimate context window based on model info
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_estimateContextWindow(paramSize, name) {
|
|
145
|
+
// Known models with specific context windows
|
|
146
|
+
const nameLower = name.toLowerCase();
|
|
147
|
+
if (nameLower.includes('llama3') || nameLower.includes('llama-3')) return 128000;
|
|
148
|
+
if (nameLower.includes('llama2') || nameLower.includes('llama-2')) return 4096;
|
|
149
|
+
if (nameLower.includes('mistral')) return 32768;
|
|
150
|
+
if (nameLower.includes('mixtral')) return 32768;
|
|
151
|
+
if (nameLower.includes('phi')) return 16384;
|
|
152
|
+
if (nameLower.includes('gemma2') || nameLower.includes('gemma-2')) return 8192;
|
|
153
|
+
if (nameLower.includes('qwen')) return 32768;
|
|
154
|
+
if (nameLower.includes('deepseek')) return 128000;
|
|
155
|
+
if (nameLower.includes('codellama')) return 16384;
|
|
156
|
+
if (nameLower.includes('command-r')) return 128000;
|
|
157
|
+
|
|
158
|
+
// Default based on parameter size
|
|
159
|
+
const sizeMatch = paramSize.match(/([\d.]+)/);
|
|
160
|
+
if (sizeMatch) {
|
|
161
|
+
const params = parseFloat(sizeMatch[1]);
|
|
162
|
+
if (params >= 70) return 8192;
|
|
163
|
+
if (params >= 13) return 8192;
|
|
164
|
+
return 4096;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return 4096; // Conservative default
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Send a chat completion request to Ollama
|
|
172
|
+
* @param {string} modelId - Normalized model ID (ollama-xxx)
|
|
173
|
+
* @param {Array} messages - Conversation messages [{role, content}]
|
|
174
|
+
* @param {Object} options - Options (temperature, maxTokens, systemPrompt)
|
|
175
|
+
* @returns {Object} Response matching AIService format
|
|
176
|
+
*/
|
|
177
|
+
async sendMessage(modelId, messages, options = {}) {
|
|
178
|
+
const ollamaName = this._resolveModelName(modelId);
|
|
179
|
+
|
|
180
|
+
// Build message array with system prompt
|
|
181
|
+
const ollamaMessages = [];
|
|
182
|
+
if (options.systemPrompt) {
|
|
183
|
+
ollamaMessages.push({ role: 'system', content: options.systemPrompt });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const msg of messages) {
|
|
187
|
+
ollamaMessages.push({
|
|
188
|
+
role: msg.role === 'assistant' ? 'assistant' : (msg.role === 'system' ? 'system' : 'user'),
|
|
189
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const startTime = Date.now();
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const response = await this.client.chat({
|
|
197
|
+
model: ollamaName,
|
|
198
|
+
messages: ollamaMessages,
|
|
199
|
+
stream: false,
|
|
200
|
+
options: {
|
|
201
|
+
temperature: options.temperature ?? 0.7,
|
|
202
|
+
num_predict: options.maxTokens || 4096,
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const duration = Date.now() - startTime;
|
|
207
|
+
|
|
208
|
+
// Estimate token counts from Ollama's eval metrics
|
|
209
|
+
const promptTokens = response.prompt_eval_count || this._estimateTokens(ollamaMessages);
|
|
210
|
+
const completionTokens = response.eval_count || this._estimateTokens([{ content: response.message?.content || '' }]);
|
|
211
|
+
|
|
212
|
+
this.logger?.info(`[Ollama] ${ollamaName} responded in ${duration}ms (${promptTokens}+${completionTokens} tokens)`);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
choices: [{
|
|
216
|
+
message: { content: response.message?.content || '' },
|
|
217
|
+
finish_reason: response.done ? 'stop' : 'length'
|
|
218
|
+
}],
|
|
219
|
+
model: modelId,
|
|
220
|
+
usage: {
|
|
221
|
+
prompt_tokens: promptTokens,
|
|
222
|
+
completion_tokens: completionTokens,
|
|
223
|
+
total_tokens: promptTokens + completionTokens
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
} catch (err) {
|
|
227
|
+
this.logger?.error(`[Ollama] Chat error for ${ollamaName}: ${err.message}`);
|
|
228
|
+
throw new Error(`Ollama error (${ollamaName}): ${err.message}`, { cause: err });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Send a streaming chat request to Ollama
|
|
234
|
+
* @param {string} modelId - Normalized model ID
|
|
235
|
+
* @param {Array} messages - Conversation messages
|
|
236
|
+
* @param {Object} options - Options including onChunk, onDone, onError callbacks
|
|
237
|
+
* @returns {Object} Final response with content and metadata
|
|
238
|
+
*/
|
|
239
|
+
async sendMessageStream(modelId, messages, options = {}) {
|
|
240
|
+
const ollamaName = this._resolveModelName(modelId);
|
|
241
|
+
const { onChunk, onDone, onError } = options;
|
|
242
|
+
|
|
243
|
+
// Build message array
|
|
244
|
+
const ollamaMessages = [];
|
|
245
|
+
if (options.systemPrompt) {
|
|
246
|
+
ollamaMessages.push({ role: 'system', content: options.systemPrompt });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const msg of messages) {
|
|
250
|
+
ollamaMessages.push({
|
|
251
|
+
role: msg.role === 'assistant' ? 'assistant' : (msg.role === 'system' ? 'system' : 'user'),
|
|
252
|
+
content: typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const startTime = Date.now();
|
|
257
|
+
let fullContent = '';
|
|
258
|
+
let promptTokens = 0;
|
|
259
|
+
let completionTokens = 0;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const stream = await this.client.chat({
|
|
263
|
+
model: ollamaName,
|
|
264
|
+
messages: ollamaMessages,
|
|
265
|
+
stream: true,
|
|
266
|
+
options: {
|
|
267
|
+
temperature: options.temperature ?? 0.7,
|
|
268
|
+
num_predict: options.maxTokens || 4096,
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (!stream) {
|
|
273
|
+
throw new Error(`Ollama returned no stream for model "${ollamaName}". The model may not be loaded or available.`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
for await (const chunk of stream) {
|
|
277
|
+
const content = chunk.message?.content || '';
|
|
278
|
+
if (content) {
|
|
279
|
+
fullContent += content;
|
|
280
|
+
if (onChunk) {
|
|
281
|
+
onChunk({ content, type: 'chunk' });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Final chunk has eval counts
|
|
286
|
+
if (chunk.done) {
|
|
287
|
+
promptTokens = chunk.prompt_eval_count || this._estimateTokens(ollamaMessages);
|
|
288
|
+
completionTokens = chunk.eval_count || this._estimateTokens([{ content: fullContent }]);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const duration = Date.now() - startTime;
|
|
293
|
+
const usage = {
|
|
294
|
+
prompt_tokens: promptTokens,
|
|
295
|
+
completion_tokens: completionTokens,
|
|
296
|
+
total_tokens: promptTokens + completionTokens
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
this.logger?.info(`[Ollama] ${ollamaName} streamed in ${duration}ms (${usage.total_tokens} tokens)`);
|
|
300
|
+
|
|
301
|
+
const result = {
|
|
302
|
+
content: fullContent,
|
|
303
|
+
model: modelId,
|
|
304
|
+
tokenUsage: usage,
|
|
305
|
+
finishReason: 'stop'
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
if (onDone) {
|
|
309
|
+
onDone(result);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return result;
|
|
313
|
+
} catch (err) {
|
|
314
|
+
this.logger?.error(`[Ollama] Stream error for ${ollamaName}: ${err.message}`);
|
|
315
|
+
if (onError) {
|
|
316
|
+
onError(err);
|
|
317
|
+
}
|
|
318
|
+
throw new Error(`Ollama streaming error (${ollamaName}): ${err.message}`, { cause: err });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Resolve normalized model ID back to Ollama model name
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
_resolveModelName(modelId) {
|
|
327
|
+
// If it has the prefix, strip it and restore colons
|
|
328
|
+
if (modelId.startsWith(OLLAMA_MODEL_PREFIX)) {
|
|
329
|
+
const stripped = modelId.slice(OLLAMA_MODEL_PREFIX.length);
|
|
330
|
+
// Restore the last dash to colon for tag separator (e.g., llama3.1-8b → llama3.1:8b)
|
|
331
|
+
// Use cached model list for accurate mapping
|
|
332
|
+
if (this._modelCache) {
|
|
333
|
+
const cached = this._modelCache.find(m => m.name === modelId);
|
|
334
|
+
if (cached) return cached.ollamaName;
|
|
335
|
+
}
|
|
336
|
+
// Fallback: restore last dash before a version-like segment to colon
|
|
337
|
+
return stripped.replace(/-(\d+[bgBG]?)$/, ':$1')
|
|
338
|
+
.replace(/-latest$/, ':latest');
|
|
339
|
+
}
|
|
340
|
+
return modelId;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Estimate token count from messages (fallback when Ollama doesn't report)
|
|
345
|
+
* @private
|
|
346
|
+
*/
|
|
347
|
+
_estimateTokens(messages) {
|
|
348
|
+
let chars = 0;
|
|
349
|
+
for (const msg of messages) {
|
|
350
|
+
const content = msg.content || '';
|
|
351
|
+
chars += typeof content === 'string' ? content.length : JSON.stringify(content).length;
|
|
352
|
+
}
|
|
353
|
+
return Math.ceil(chars / 3.5); // ~3.5 chars per token average
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Check if a model ID is an Ollama model
|
|
358
|
+
* @param {string} modelId - Model identifier
|
|
359
|
+
* @returns {boolean}
|
|
360
|
+
*/
|
|
361
|
+
static isOllamaModel(modelId) {
|
|
362
|
+
return typeof modelId === 'string' && modelId.startsWith(OLLAMA_MODEL_PREFIX);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Pull a model from Ollama registry
|
|
367
|
+
* @param {string} modelName - Model to pull (e.g., "llama3.1:8b")
|
|
368
|
+
* @param {Function} onProgress - Progress callback
|
|
369
|
+
*/
|
|
370
|
+
async pullModel(modelName, onProgress = null) {
|
|
371
|
+
this.logger?.info(`[Ollama] Pulling model: ${modelName}`);
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
const stream = await this.client.pull({ model: modelName, stream: true });
|
|
375
|
+
|
|
376
|
+
if (!stream) {
|
|
377
|
+
throw new Error(`Ollama returned no stream when pulling "${modelName}". Check that Ollama is running.`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
for await (const progress of stream) {
|
|
381
|
+
if (onProgress) {
|
|
382
|
+
onProgress({
|
|
383
|
+
status: progress.status,
|
|
384
|
+
total: progress.total,
|
|
385
|
+
completed: progress.completed,
|
|
386
|
+
percent: progress.total ? Math.round((progress.completed / progress.total) * 100) : null
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Invalidate model cache
|
|
392
|
+
this._modelCache = null;
|
|
393
|
+
this.logger?.info(`[Ollama] Successfully pulled ${modelName}`);
|
|
394
|
+
return { success: true, model: modelName };
|
|
395
|
+
} catch (err) {
|
|
396
|
+
this.logger?.error(`[Ollama] Pull failed for ${modelName}: ${err.message}`);
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Delete a model from Ollama
|
|
403
|
+
* @param {string} modelName - Model to delete
|
|
404
|
+
*/
|
|
405
|
+
async deleteModel(modelName) {
|
|
406
|
+
try {
|
|
407
|
+
await this.client.delete({ model: modelName });
|
|
408
|
+
this._modelCache = null;
|
|
409
|
+
this.logger?.info(`[Ollama] Deleted model: ${modelName}`);
|
|
410
|
+
return { success: true };
|
|
411
|
+
} catch (err) {
|
|
412
|
+
this.logger?.error(`[Ollama] Delete failed for ${modelName}: ${err.message}`);
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get model info from Ollama
|
|
419
|
+
* @param {string} modelName - Model name
|
|
420
|
+
*/
|
|
421
|
+
async getModelInfo(modelName) {
|
|
422
|
+
try {
|
|
423
|
+
const info = await this.client.show({ model: modelName });
|
|
424
|
+
return {
|
|
425
|
+
success: true,
|
|
426
|
+
info: {
|
|
427
|
+
name: modelName,
|
|
428
|
+
license: info.license,
|
|
429
|
+
modelfile: info.modelfile,
|
|
430
|
+
parameters: info.parameters,
|
|
431
|
+
template: info.template,
|
|
432
|
+
details: info.details
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
} catch (err) {
|
|
436
|
+
return { success: false, error: err.message };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Singleton
|
|
442
|
+
let instance = null;
|
|
443
|
+
|
|
444
|
+
export function getOllamaService(config = {}, logger = null) {
|
|
445
|
+
if (!instance) {
|
|
446
|
+
instance = new OllamaService(config, logger);
|
|
447
|
+
}
|
|
448
|
+
return instance;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export { OllamaService, OLLAMA_MODEL_PREFIX };
|
|
452
|
+
export default OllamaService;
|