libre-webui 0.17.0 → 0.18.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.
Files changed (279) hide show
  1. package/backend/dist/db.d.ts.map +1 -1
  2. package/backend/dist/db.js +150 -6
  3. package/backend/dist/db.js.map +1 -1
  4. package/backend/dist/index.d.ts.map +1 -1
  5. package/backend/dist/index.js +15 -10
  6. package/backend/dist/index.js.map +1 -1
  7. package/backend/dist/middleware/auth.d.ts.map +1 -1
  8. package/backend/dist/middleware/auth.js +35 -3
  9. package/backend/dist/middleware/auth.js.map +1 -1
  10. package/backend/dist/models/userModel.d.ts +23 -0
  11. package/backend/dist/models/userModel.d.ts.map +1 -1
  12. package/backend/dist/models/userModel.js +89 -24
  13. package/backend/dist/models/userModel.js.map +1 -1
  14. package/backend/dist/routes/agentCli.d.ts.map +1 -1
  15. package/backend/dist/routes/agentCli.js +2 -0
  16. package/backend/dist/routes/agentCli.js.map +1 -1
  17. package/backend/dist/routes/auth.d.ts.map +1 -1
  18. package/backend/dist/routes/auth.js +46 -33
  19. package/backend/dist/routes/auth.js.map +1 -1
  20. package/backend/dist/routes/chat.d.ts.map +1 -1
  21. package/backend/dist/routes/chat.js +3 -1
  22. package/backend/dist/routes/chat.js.map +1 -1
  23. package/backend/dist/routes/documents.d.ts.map +1 -1
  24. package/backend/dist/routes/documents.js +49 -41
  25. package/backend/dist/routes/documents.js.map +1 -1
  26. package/backend/dist/routes/embeddings.js +2 -2
  27. package/backend/dist/routes/embeddings.js.map +1 -1
  28. package/backend/dist/routes/huggingfaceHub.d.ts.map +1 -1
  29. package/backend/dist/routes/huggingfaceHub.js +3 -1
  30. package/backend/dist/routes/huggingfaceHub.js.map +1 -1
  31. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  32. package/backend/dist/routes/imageGen.js +2 -0
  33. package/backend/dist/routes/imageGen.js.map +1 -1
  34. package/backend/dist/routes/media.d.ts +3 -0
  35. package/backend/dist/routes/media.d.ts.map +1 -0
  36. package/backend/dist/routes/media.js +429 -0
  37. package/backend/dist/routes/media.js.map +1 -0
  38. package/backend/dist/routes/ollama.d.ts.map +1 -1
  39. package/backend/dist/routes/ollama.js +15 -83
  40. package/backend/dist/routes/ollama.js.map +1 -1
  41. package/backend/dist/routes/personas.d.ts.map +1 -1
  42. package/backend/dist/routes/personas.js +2 -0
  43. package/backend/dist/routes/personas.js.map +1 -1
  44. package/backend/dist/routes/plugins.d.ts.map +1 -1
  45. package/backend/dist/routes/plugins.js +28 -0
  46. package/backend/dist/routes/plugins.js.map +1 -1
  47. package/backend/dist/routes/systemDiagnostics.d.ts +3 -0
  48. package/backend/dist/routes/systemDiagnostics.d.ts.map +1 -0
  49. package/backend/dist/routes/systemDiagnostics.js +49 -0
  50. package/backend/dist/routes/systemDiagnostics.js.map +1 -0
  51. package/backend/dist/routes/tts.d.ts.map +1 -1
  52. package/backend/dist/routes/tts.js +4 -0
  53. package/backend/dist/routes/tts.js.map +1 -1
  54. package/backend/dist/routes/users.d.ts.map +1 -1
  55. package/backend/dist/routes/users.js +58 -0
  56. package/backend/dist/routes/users.js.map +1 -1
  57. package/backend/dist/routes/work.d.ts.map +1 -1
  58. package/backend/dist/routes/work.js +84 -0
  59. package/backend/dist/routes/work.js.map +1 -1
  60. package/backend/dist/services/authService.d.ts +15 -9
  61. package/backend/dist/services/authService.d.ts.map +1 -1
  62. package/backend/dist/services/authService.js +34 -29
  63. package/backend/dist/services/authService.js.map +1 -1
  64. package/backend/dist/services/chatRequestService.d.ts +8 -2
  65. package/backend/dist/services/chatRequestService.d.ts.map +1 -1
  66. package/backend/dist/services/chatRequestService.js +30 -3
  67. package/backend/dist/services/chatRequestService.js.map +1 -1
  68. package/backend/dist/services/documentService.d.ts +11 -10
  69. package/backend/dist/services/documentService.d.ts.map +1 -1
  70. package/backend/dist/services/documentService.js +62 -54
  71. package/backend/dist/services/documentService.js.map +1 -1
  72. package/backend/dist/services/encryptionService.d.ts.map +1 -1
  73. package/backend/dist/services/encryptionService.js +18 -8
  74. package/backend/dist/services/encryptionService.js.map +1 -1
  75. package/backend/dist/services/galleryService.d.ts +23 -1
  76. package/backend/dist/services/galleryService.d.ts.map +1 -1
  77. package/backend/dist/services/galleryService.js +125 -8
  78. package/backend/dist/services/galleryService.js.map +1 -1
  79. package/backend/dist/services/mediaGenerationJobService.d.ts +25 -0
  80. package/backend/dist/services/mediaGenerationJobService.d.ts.map +1 -0
  81. package/backend/dist/services/mediaGenerationJobService.js +82 -0
  82. package/backend/dist/services/mediaGenerationJobService.js.map +1 -0
  83. package/backend/dist/services/pluginAudioGenerationService.d.ts +34 -0
  84. package/backend/dist/services/pluginAudioGenerationService.d.ts.map +1 -0
  85. package/backend/dist/services/pluginAudioGenerationService.js +211 -0
  86. package/backend/dist/services/pluginAudioGenerationService.js.map +1 -0
  87. package/backend/dist/services/pluginCapabilityRegistryService.d.ts.map +1 -1
  88. package/backend/dist/services/pluginCapabilityRegistryService.js +6 -0
  89. package/backend/dist/services/pluginCapabilityRegistryService.js.map +1 -1
  90. package/backend/dist/services/pluginEmbeddingService.d.ts +2 -0
  91. package/backend/dist/services/pluginEmbeddingService.d.ts.map +1 -1
  92. package/backend/dist/services/pluginEmbeddingService.js +66 -35
  93. package/backend/dist/services/pluginEmbeddingService.js.map +1 -1
  94. package/backend/dist/services/pluginImageGenerationService.d.ts +2 -0
  95. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
  96. package/backend/dist/services/pluginImageGenerationService.js +63 -38
  97. package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
  98. package/backend/dist/services/pluginService.d.ts +38 -1
  99. package/backend/dist/services/pluginService.d.ts.map +1 -1
  100. package/backend/dist/services/pluginService.js +390 -69
  101. package/backend/dist/services/pluginService.js.map +1 -1
  102. package/backend/dist/services/pluginTTSService.d.ts +2 -0
  103. package/backend/dist/services/pluginTTSService.d.ts.map +1 -1
  104. package/backend/dist/services/pluginTTSService.js +27 -2
  105. package/backend/dist/services/pluginTTSService.js.map +1 -1
  106. package/backend/dist/services/pluginUsageService.d.ts +82 -0
  107. package/backend/dist/services/pluginUsageService.d.ts.map +1 -0
  108. package/backend/dist/services/pluginUsageService.js +238 -0
  109. package/backend/dist/services/pluginUsageService.js.map +1 -0
  110. package/backend/dist/services/pluginVideoGenerationService.d.ts +45 -0
  111. package/backend/dist/services/pluginVideoGenerationService.d.ts.map +1 -0
  112. package/backend/dist/services/pluginVideoGenerationService.js +198 -0
  113. package/backend/dist/services/pluginVideoGenerationService.js.map +1 -0
  114. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  115. package/backend/dist/services/preferencesService.js +15 -1
  116. package/backend/dist/services/preferencesService.js.map +1 -1
  117. package/backend/dist/services/registrationPolicy.d.ts +5 -0
  118. package/backend/dist/services/registrationPolicy.d.ts.map +1 -0
  119. package/backend/dist/services/registrationPolicy.js +21 -0
  120. package/backend/dist/services/registrationPolicy.js.map +1 -0
  121. package/backend/dist/services/simpleGitHubOAuth.d.ts.map +1 -1
  122. package/backend/dist/services/simpleGitHubOAuth.js +8 -12
  123. package/backend/dist/services/simpleGitHubOAuth.js.map +1 -1
  124. package/backend/dist/services/simpleHuggingFaceOAuth.d.ts.map +1 -1
  125. package/backend/dist/services/simpleHuggingFaceOAuth.js +7 -12
  126. package/backend/dist/services/simpleHuggingFaceOAuth.js.map +1 -1
  127. package/backend/dist/services/systemDiagnosticsService.d.ts +117 -0
  128. package/backend/dist/services/systemDiagnosticsService.d.ts.map +1 -0
  129. package/backend/dist/services/systemDiagnosticsService.js +326 -0
  130. package/backend/dist/services/systemDiagnosticsService.js.map +1 -0
  131. package/backend/dist/services/turnstileService.d.ts +2 -1
  132. package/backend/dist/services/turnstileService.d.ts.map +1 -1
  133. package/backend/dist/services/turnstileService.js +28 -2
  134. package/backend/dist/services/turnstileService.js.map +1 -1
  135. package/backend/dist/services/workModelProviderService.d.ts +3 -0
  136. package/backend/dist/services/workModelProviderService.d.ts.map +1 -1
  137. package/backend/dist/services/workModelProviderService.js +37 -4
  138. package/backend/dist/services/workModelProviderService.js.map +1 -1
  139. package/backend/dist/services/workPreviewProxyService.d.ts +27 -0
  140. package/backend/dist/services/workPreviewProxyService.d.ts.map +1 -0
  141. package/backend/dist/services/workPreviewProxyService.js +514 -0
  142. package/backend/dist/services/workPreviewProxyService.js.map +1 -0
  143. package/backend/dist/services/workRuntimeService.d.ts +22 -1
  144. package/backend/dist/services/workRuntimeService.d.ts.map +1 -1
  145. package/backend/dist/services/workRuntimeService.js +287 -8
  146. package/backend/dist/services/workRuntimeService.js.map +1 -1
  147. package/backend/dist/storage.d.ts.map +1 -1
  148. package/backend/dist/storage.js +3 -3
  149. package/backend/dist/storage.js.map +1 -1
  150. package/backend/dist/types/index.d.ts +77 -1
  151. package/backend/dist/types/index.d.ts.map +1 -1
  152. package/backend/dist/types/index.js.map +1 -1
  153. package/backend/dist/types/work.d.ts +31 -0
  154. package/backend/dist/types/work.d.ts.map +1 -1
  155. package/backend/dist/utils/chatDocumentContext.d.ts +1 -1
  156. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
  157. package/backend/dist/utils/chatDocumentContext.js +4 -4
  158. package/backend/dist/utils/chatDocumentContext.js.map +1 -1
  159. package/backend/dist/utils/pluginDefinitionTrust.js +1 -1
  160. package/backend/dist/utils/pluginValidation.d.ts +3 -2
  161. package/backend/dist/utils/pluginValidation.d.ts.map +1 -1
  162. package/backend/dist/utils/pluginValidation.js +30 -2
  163. package/backend/dist/utils/pluginValidation.js.map +1 -1
  164. package/backend/dist/utils/workGit.d.ts +14 -0
  165. package/backend/dist/utils/workGit.d.ts.map +1 -0
  166. package/backend/dist/utils/workGit.js +207 -0
  167. package/backend/dist/utils/workGit.js.map +1 -0
  168. package/backend/dist/websocketServer.d.ts +1 -0
  169. package/backend/dist/websocketServer.d.ts.map +1 -1
  170. package/backend/dist/websocketServer.js +108 -24
  171. package/backend/dist/websocketServer.js.map +1 -1
  172. package/backend/dist/workTerminalServer.d.ts.map +1 -1
  173. package/backend/dist/workTerminalServer.js +3 -1
  174. package/backend/dist/workTerminalServer.js.map +1 -1
  175. package/backend/package.json +1 -1
  176. package/frontend/dist/assets/index-ChhWhug2.css +1 -0
  177. package/frontend/dist/assets/index-CmZntyR1.js +3 -0
  178. package/frontend/dist/index.html +26 -6
  179. package/frontend/dist/js/ArtifactContainer-CBAbdoIJ.js +1 -0
  180. package/frontend/dist/js/{ArtifactDemoPage-B4qsvUnF.js → ArtifactDemoPage-BWN0MWCx.js} +1 -1
  181. package/frontend/dist/js/ArtifactSlideOutPanel-D-AvYkfk.js +1 -0
  182. package/frontend/dist/js/ChatPage-w2FkF_fE.js +16 -0
  183. package/frontend/dist/js/FirstTimeSetup-CzBxl7UH.js +1 -0
  184. package/frontend/dist/js/GalleryPage-Cv8pniRW.js +1 -0
  185. package/frontend/dist/js/HomePage-Vyj4ndU4.js +1 -0
  186. package/frontend/dist/js/LibreClawPage-CYhLgQB-.js +1 -0
  187. package/frontend/dist/js/{ModelManagerModals-DMoEav1l.js → ModelManagerModals-C2rwHd01.js} +1 -1
  188. package/frontend/dist/js/ModelSelector-D7kDjZV9.js +1 -0
  189. package/frontend/dist/js/ModelsPage-BnUddkrs.js +2 -0
  190. package/frontend/dist/js/PersonasPage-BL5XlrEm.js +13 -0
  191. package/frontend/dist/js/PluginUsagePage-BXqweCCs.js +1 -0
  192. package/frontend/dist/js/{RichMessageContent-BJ49VN9j.js → RichMessageContent-nAEaPTpH.js} +2 -2
  193. package/frontend/dist/js/SettingsModal-DZYCI5-y.js +1 -0
  194. package/frontend/dist/js/SystemPage-jdHYfn9A.js +1 -0
  195. package/frontend/dist/js/UserManagementPage-BpOnt6os.js +1 -0
  196. package/frontend/dist/js/WorkPage-DAp1T1dl.js +5 -0
  197. package/frontend/dist/js/api-BCqWXjZI.js +19 -0
  198. package/frontend/dist/js/appStore-CDM6V5ny.js +2 -0
  199. package/frontend/dist/js/ar-DS8gs8WK.js +2 -0
  200. package/frontend/dist/js/{authStore-mOYH_AXI.js → authStore-Bl5ofD_h.js} +3 -3
  201. package/frontend/dist/js/bn-CrMPJwTR.js +2 -0
  202. package/frontend/dist/js/cs-BL116Ezu.js +2 -0
  203. package/frontend/dist/js/da-TDNaKzqy.js +2 -0
  204. package/frontend/dist/js/de-DkIqdAB5.js +2 -0
  205. package/frontend/dist/js/es-CHRXt6Fd.js +2 -0
  206. package/frontend/dist/js/{estree-0vwU39qX.js → estree-BFya-bSu.js} +1 -1
  207. package/frontend/dist/js/fr-8715yUKq.js +2 -0
  208. package/frontend/dist/js/hi-C2O08SBU.js +2 -0
  209. package/frontend/dist/js/{html-CApr2Bvi.js → html-DJot0Wwf.js} +1 -1
  210. package/frontend/dist/js/id-Dq7UXUEV.js +2 -0
  211. package/frontend/dist/js/is-B3dbbhSl.js +2 -0
  212. package/frontend/dist/js/it-CLSuu2K7.js +2 -0
  213. package/frontend/dist/js/ja-CthrqyCR.js +2 -0
  214. package/frontend/dist/js/ko-BJvFDJ82.js +2 -0
  215. package/frontend/dist/js/{markdown-C78Qc6xu.js → markdown-CXwWsPYG.js} +1 -1
  216. package/frontend/dist/js/ms-QV0202Mj.js +2 -0
  217. package/frontend/dist/js/nl-Dob1m1pV.js +2 -0
  218. package/frontend/dist/js/pl-urfOKnYI.js +2 -0
  219. package/frontend/dist/js/pt-D52Xkpi0.js +2 -0
  220. package/frontend/dist/js/ru-CwnW9Xe2.js +2 -0
  221. package/frontend/dist/js/{standalone-DmLfI4Qj.js → standalone-C0NjJpWr.js} +1 -1
  222. package/frontend/dist/js/sv-BNclj-Fo.js +2 -0
  223. package/frontend/dist/js/th-BlQ4PL57.js +2 -0
  224. package/frontend/dist/js/tr-B80aZ1px.js +2 -0
  225. package/frontend/dist/js/{typescript-Bcq62agC.js → typescript-d0TUM2-0.js} +1 -1
  226. package/frontend/dist/js/ui-vendor-ChfdHf5M.js +177 -0
  227. package/frontend/dist/js/uk-DZuuaI56.js +2 -0
  228. package/frontend/dist/js/vi-Bg42fPfF.js +2 -0
  229. package/frontend/dist/js/zh-BSt87zs6.js +2 -0
  230. package/package.json +3 -3
  231. package/plugins/openrouter.json +89 -0
  232. package/backend/dist/services/systemSettingsService.d.ts +0 -12
  233. package/backend/dist/services/systemSettingsService.d.ts.map +0 -1
  234. package/backend/dist/services/systemSettingsService.js +0 -70
  235. package/backend/dist/services/systemSettingsService.js.map +0 -1
  236. package/frontend/dist/assets/index-BqI0kezz.css +0 -1
  237. package/frontend/dist/assets/index-CRC6g6bL.js +0 -3
  238. package/frontend/dist/js/ArtifactContainer-Cdy-973y.js +0 -1
  239. package/frontend/dist/js/ArtifactSlideOutPanel-CmKgQ4eD.js +0 -1
  240. package/frontend/dist/js/ChatPage-CYT69oBR.js +0 -16
  241. package/frontend/dist/js/FirstTimeSetup-C8LdLP5J.js +0 -1
  242. package/frontend/dist/js/GalleryPage-Bm89pnS8.js +0 -1
  243. package/frontend/dist/js/HomePage-CeCYnMUG.js +0 -1
  244. package/frontend/dist/js/LibreClawPage-DGMEV6dm.js +0 -1
  245. package/frontend/dist/js/ModelSelector-DXPdGdQ_.js +0 -1
  246. package/frontend/dist/js/ModelsPage-D-FIy2x-.js +0 -2
  247. package/frontend/dist/js/PersonasPage-CIrEVFo5.js +0 -13
  248. package/frontend/dist/js/SettingsModal-CxtOXPY7.js +0 -1
  249. package/frontend/dist/js/UserManagementPage-BfyYGGFQ.js +0 -1
  250. package/frontend/dist/js/WorkPage-C7bW6Hxx.js +0 -4
  251. package/frontend/dist/js/api-DCD41-l9.js +0 -19
  252. package/frontend/dist/js/appStore-Bw-8Exsx.js +0 -2
  253. package/frontend/dist/js/ar-BAHlIvy0.js +0 -2
  254. package/frontend/dist/js/bn-BxquDVbd.js +0 -2
  255. package/frontend/dist/js/cs-C4DoKzRm.js +0 -2
  256. package/frontend/dist/js/da-CFslpGTw.js +0 -2
  257. package/frontend/dist/js/de-ChudoRh2.js +0 -2
  258. package/frontend/dist/js/es-B36fefE-.js +0 -2
  259. package/frontend/dist/js/fr-BSVeTM5W.js +0 -2
  260. package/frontend/dist/js/hi-BGoFbl5V.js +0 -2
  261. package/frontend/dist/js/id-BfYs2p_j.js +0 -2
  262. package/frontend/dist/js/is-DZ1MS-5A.js +0 -2
  263. package/frontend/dist/js/it-CXtrPCG1.js +0 -2
  264. package/frontend/dist/js/ja-FPJ_NK_y.js +0 -2
  265. package/frontend/dist/js/ko-DVwD4ZZF.js +0 -2
  266. package/frontend/dist/js/ms-PKPP0RX3.js +0 -2
  267. package/frontend/dist/js/nl-DWM5HJZ8.js +0 -2
  268. package/frontend/dist/js/pl-B9va8Tuf.js +0 -2
  269. package/frontend/dist/js/pt-P0IQi-Zr.js +0 -2
  270. package/frontend/dist/js/ru-Ca2bxYnc.js +0 -2
  271. package/frontend/dist/js/sv-DI4RDwyZ.js +0 -2
  272. package/frontend/dist/js/th-DN0bDS9d.js +0 -2
  273. package/frontend/dist/js/tr-ChqMv5Q4.js +0 -2
  274. package/frontend/dist/js/ui-vendor-DohPFHDW.js +0 -177
  275. package/frontend/dist/js/uk-Cr51h_XQ.js +0 -2
  276. package/frontend/dist/js/useBaseQuery-BAOlhjar.js +0 -1
  277. package/frontend/dist/js/useQuery-CcRqxveh.js +0 -1
  278. package/frontend/dist/js/vi-D4OhoVMH.js +0 -2
  279. package/frontend/dist/js/zh-Bvau7hlR.js +0 -2
@@ -22,10 +22,12 @@ import axios from 'axios';
22
22
  import pluginCredentialsService from './pluginCredentialsService.js';
23
23
  import pluginVariablesService from './pluginVariablesService.js';
24
24
  import pluginActivationService from './pluginActivationService.js';
25
+ import { PluginAudioGenerationService } from './pluginAudioGenerationService.js';
25
26
  import { PluginCapabilityRegistryService } from './pluginCapabilityRegistryService.js';
26
27
  import { PluginEmbeddingService } from './pluginEmbeddingService.js';
27
28
  import { PluginImageGenerationService } from './pluginImageGenerationService.js';
28
29
  import { PluginTTSService } from './pluginTTSService.js';
30
+ import { PluginVideoGenerationService } from './pluginVideoGenerationService.js';
29
31
  import { applyPluginDefinitionPolicy, buildPluginChatPayload, convertProviderResponse, getOpenAICompatibleSamplingParameters, resolvePluginChatParameters, toOpenAICompatibleMessages, } from '../utils/pluginChatAdapter.js';
30
32
  import { streamAnthropicResponse, streamOpenAICompatibleResponse, streamOpenAIResponsesResponse, } from '../utils/pluginStreamAdapter.js';
31
33
  import { applyModelEndpointTemplate, assertSafePluginEndpoint, buildPluginAuthHeaders, buildPluginModelDiscoveryHeaders, pluginRequiresApiKey, resolvePluginApiConfig, resolvePluginEndpoint, resolvePluginModelsEndpoint, validatePluginModel, } from '../utils/pluginValidation.js';
@@ -34,6 +36,7 @@ import { resolveBundledPluginsDir } from '../utils/packagePaths.js';
34
36
  import { getDatabaseSafe } from '../db.js';
35
37
  import { createOpenAIResponsesStateScope, createPluginCredentialFingerprint, OPENAI_RESPONSES_INCOMPLETE_REASON_METADATA_KEY, } from '../utils/openAIResponsesAdapter.js';
36
38
  import { getPluginConnectionVariableNames } from '../utils/pluginConnectionVariables.js';
39
+ import pluginUsageService from './pluginUsageService.js';
37
40
  import { getPluginDefinitionFingerprint, matchesBundledPluginTrustAnchor, } from '../utils/pluginDefinitionTrust.js';
38
41
  const logger = createLogger('plugins');
39
42
  const readPositiveIntEnv = (name, fallback) => {
@@ -77,6 +80,7 @@ function getPluginRoutingAuthProjection(plugin) {
77
80
  return {
78
81
  name,
79
82
  endpoint: capability.endpoint ?? null,
83
+ models_endpoint: capability.models_endpoint ?? null,
80
84
  endpoint_variable: config.endpoint_variable ?? capability.endpoint_variable ?? null,
81
85
  };
82
86
  });
@@ -118,6 +122,10 @@ export class PluginService {
118
122
  this.discoveredModelsUpdatedAt = new Map();
119
123
  this.discoveryAttemptedAt = new Map();
120
124
  this.inflightDiscovery = new Map();
125
+ this.discoveredCapabilityModelsCache = new Map();
126
+ this.discoveredCapabilityModelsUpdatedAt = new Map();
127
+ this.capabilityDiscoveryAttemptedAt = new Map();
128
+ this.inflightCapabilityDiscovery = new Map();
121
129
  this.bundledRoutingProjectionCache = new Map();
122
130
  this.bundledPluginsDir = resolveBundledPluginsDir(import.meta.url);
123
131
  this.legacyPluginsDir = path.join(process.cwd(), 'plugins');
@@ -134,6 +142,7 @@ export class PluginService {
134
142
  getApiKey: (plugin, userId) => this.getApiKey(plugin, userId),
135
143
  getPluginVariables: (plugin, userId) => this.getPluginVariables(plugin, userId),
136
144
  validateEndpointUrl: endpoint => this.validateEndpointUrl(endpoint),
145
+ recordUsage: usage => pluginUsageService.record(usage),
137
146
  });
138
147
  this.ttsService = new PluginTTSService({
139
148
  getAllPlugins: userId => this.getActivePlugins(userId),
@@ -141,6 +150,7 @@ export class PluginService {
141
150
  getApiKey: (plugin, userId) => this.getApiKey(plugin, userId),
142
151
  getPluginVariables: (plugin, userId) => this.getPluginVariables(plugin, userId),
143
152
  validateEndpointUrl: endpoint => this.validateEndpointUrl(endpoint),
153
+ recordUsage: usage => pluginUsageService.record(usage),
144
154
  });
145
155
  this.imageGenerationService = new PluginImageGenerationService({
146
156
  getAllPlugins: userId => this.getActivePlugins(userId),
@@ -148,6 +158,23 @@ export class PluginService {
148
158
  getApiKey: (plugin, userId) => this.getApiKey(plugin, userId),
149
159
  getPluginVariables: (plugin, userId) => this.getPluginVariables(plugin, userId),
150
160
  validateEndpointUrl: endpoint => this.validateEndpointUrl(endpoint),
161
+ recordUsage: usage => pluginUsageService.record(usage),
162
+ });
163
+ this.audioGenerationService = new PluginAudioGenerationService({
164
+ getAllPlugins: userId => this.getActivePlugins(userId),
165
+ getPlugin: (id, userId) => this.getPlugin(id, userId),
166
+ getApiKey: (plugin, userId) => this.getApiKey(plugin, userId),
167
+ getPluginVariables: (plugin, userId) => this.getPluginVariables(plugin, userId),
168
+ validateEndpointUrl: endpoint => this.validateEndpointUrl(endpoint),
169
+ recordUsage: usage => pluginUsageService.record(usage),
170
+ });
171
+ this.videoGenerationService = new PluginVideoGenerationService({
172
+ getAllPlugins: userId => this.getActivePlugins(userId),
173
+ getPlugin: (id, userId) => this.getPlugin(id, userId),
174
+ getApiKey: (plugin, userId) => this.getApiKey(plugin, userId),
175
+ getPluginVariables: (plugin, userId) => this.getPluginVariables(plugin, userId),
176
+ validateEndpointUrl: endpoint => this.validateEndpointUrl(endpoint),
177
+ recordUsage: usage => pluginUsageService.record(usage),
151
178
  });
152
179
  this.capabilityRegistryService = new PluginCapabilityRegistryService({
153
180
  getAllPlugins: userId => this.getActivePlugins(userId),
@@ -157,6 +184,65 @@ export class PluginService {
157
184
  discoveredModelsCacheKey(pluginId, userId) {
158
185
  return `${userId}:${pluginId}`;
159
186
  }
187
+ discoveredCapabilityModelsCacheKey(pluginId, capability, userId) {
188
+ return `${userId}:${pluginId}:${capability}`;
189
+ }
190
+ getDiscoveredCapabilityModels(pluginId, capability, userId) {
191
+ const effectiveUserId = userId || 'default';
192
+ const cacheKey = this.discoveredCapabilityModelsCacheKey(pluginId, capability, effectiveUserId);
193
+ if (this.discoveredCapabilityModelsCache.has(cacheKey)) {
194
+ return this.discoveredCapabilityModelsCache.get(cacheKey) || undefined;
195
+ }
196
+ const db = getDatabaseSafe();
197
+ if (!db) {
198
+ this.discoveredCapabilityModelsCache.set(cacheKey, null);
199
+ return undefined;
200
+ }
201
+ try {
202
+ const row = db
203
+ .prepare(`SELECT models_json, updated_at
204
+ FROM plugin_discovered_capability_models
205
+ WHERE user_id = ? AND plugin_id = ? AND capability = ?`)
206
+ .get(effectiveUserId, pluginId, capability);
207
+ if (!row) {
208
+ this.discoveredCapabilityModelsCache.set(cacheKey, null);
209
+ return undefined;
210
+ }
211
+ const parsed = JSON.parse(row.models_json);
212
+ const models = Array.isArray(parsed)
213
+ ? Array.from(new Set(parsed.filter((model) => typeof model === 'string' && model.length > 0)))
214
+ : [];
215
+ if (models.length === 0) {
216
+ this.discoveredCapabilityModelsCache.set(cacheKey, null);
217
+ return undefined;
218
+ }
219
+ this.discoveredCapabilityModelsCache.set(cacheKey, models);
220
+ this.discoveredCapabilityModelsUpdatedAt.set(cacheKey, row.updated_at);
221
+ return models;
222
+ }
223
+ catch (error) {
224
+ logger.warn('Failed to read discovered %s models for plugin %s:', capability, pluginId, error);
225
+ this.discoveredCapabilityModelsCache.set(cacheKey, null);
226
+ return undefined;
227
+ }
228
+ }
229
+ storeDiscoveredCapabilityModels(pluginId, capability, models, userId) {
230
+ const effectiveUserId = userId || 'default';
231
+ const uniqueModels = Array.from(new Set(models));
232
+ const cacheKey = this.discoveredCapabilityModelsCacheKey(pluginId, capability, effectiveUserId);
233
+ const discoveredAt = Date.now();
234
+ this.discoveredCapabilityModelsCache.set(cacheKey, uniqueModels);
235
+ this.discoveredCapabilityModelsUpdatedAt.set(cacheKey, discoveredAt);
236
+ const db = getDatabaseSafe();
237
+ if (!db)
238
+ return;
239
+ db.prepare(`INSERT INTO plugin_discovered_capability_models
240
+ (user_id, plugin_id, capability, models_json, updated_at)
241
+ VALUES (?, ?, ?, ?, ?)
242
+ ON CONFLICT(user_id, plugin_id, capability) DO UPDATE SET
243
+ models_json = excluded.models_json,
244
+ updated_at = excluded.updated_at`).run(effectiveUserId, pluginId, capability, JSON.stringify(uniqueModels), discoveredAt);
245
+ }
160
246
  getDiscoveredModels(pluginId, userId) {
161
247
  const effectiveUserId = userId || 'default';
162
248
  const cacheKey = this.discoveredModelsCacheKey(pluginId, effectiveUserId);
@@ -219,6 +305,7 @@ export class PluginService {
219
305
  }
220
306
  }
221
307
  clearDiscoveredModels(pluginId, userId) {
308
+ this.clearDiscoveredCapabilityModels(pluginId, userId);
222
309
  const db = getDatabaseSafe();
223
310
  if (userId) {
224
311
  const cacheKey = this.discoveredModelsCacheKey(pluginId, userId);
@@ -251,13 +338,58 @@ export class PluginService {
251
338
  logger.warn('Failed to clear discovered models for plugin %s:', pluginId, error);
252
339
  }
253
340
  }
341
+ clearDiscoveredCapabilityModels(pluginId, userId) {
342
+ const db = getDatabaseSafe();
343
+ if (userId) {
344
+ for (const capability of ['image', 'tts', 'audio', 'video']) {
345
+ const key = this.discoveredCapabilityModelsCacheKey(pluginId, capability, userId);
346
+ this.discoveredCapabilityModelsCache.delete(key);
347
+ this.discoveredCapabilityModelsUpdatedAt.delete(key);
348
+ this.capabilityDiscoveryAttemptedAt.delete(key);
349
+ }
350
+ if (db) {
351
+ db.prepare(`DELETE FROM plugin_discovered_capability_models
352
+ WHERE user_id = ? AND plugin_id = ?`).run(userId, pluginId);
353
+ }
354
+ return;
355
+ }
356
+ for (const key of this.discoveredCapabilityModelsCache.keys()) {
357
+ if (['image', 'tts', 'audio', 'video'].some(capability => key.endsWith(`:${pluginId}:${capability}`))) {
358
+ this.discoveredCapabilityModelsCache.delete(key);
359
+ this.discoveredCapabilityModelsUpdatedAt.delete(key);
360
+ this.capabilityDiscoveryAttemptedAt.delete(key);
361
+ }
362
+ }
363
+ if (db) {
364
+ db.prepare('DELETE FROM plugin_discovered_capability_models WHERE plugin_id = ?').run(pluginId);
365
+ }
366
+ }
254
367
  applyDiscoveredModels(plugin, userId) {
255
368
  if (!this.canUseStoredConnectionOverrides(userId) &&
256
369
  pluginVariablesService.hasStoredConnectionOverride(plugin.id, userId, plugin.variables, getPluginConnectionVariableNames(plugin))) {
257
370
  return plugin;
258
371
  }
259
372
  const models = this.getDiscoveredModels(plugin.id, userId);
260
- return models ? { ...plugin, model_map: [...models] } : plugin;
373
+ const capabilities = plugin.capabilities
374
+ ? { ...plugin.capabilities }
375
+ : undefined;
376
+ if (capabilities) {
377
+ for (const capability of ['image', 'tts', 'audio', 'video']) {
378
+ const definition = capabilities[capability];
379
+ const discovered = this.getDiscoveredCapabilityModels(plugin.id, capability, userId);
380
+ if (definition && discovered) {
381
+ capabilities[capability] = {
382
+ ...definition,
383
+ model_map: [...discovered],
384
+ };
385
+ }
386
+ }
387
+ }
388
+ return {
389
+ ...plugin,
390
+ ...(models ? { model_map: [...models] } : {}),
391
+ ...(capabilities ? { capabilities } : {}),
392
+ };
261
393
  }
262
394
  ensurePluginsDirectory() {
263
395
  if (!fs.existsSync(this.pluginsDir)) {
@@ -566,6 +698,98 @@ export class PluginService {
566
698
  new Promise(resolve => setTimeout(resolve, modelDiscoveryRefreshDeadlineMs()).unref?.()),
567
699
  ]);
568
700
  }
701
+ async discoverCapabilityModels(pluginId, capability, userId) {
702
+ const cacheKey = this.discoveredCapabilityModelsCacheKey(pluginId, capability, userId || 'default');
703
+ const inflight = this.inflightCapabilityDiscovery.get(cacheKey);
704
+ if (inflight)
705
+ return inflight;
706
+ const attempt = this.runCapabilityModelDiscovery(pluginId, capability, userId).finally(() => this.inflightCapabilityDiscovery.delete(cacheKey));
707
+ this.inflightCapabilityDiscovery.set(cacheKey, attempt);
708
+ return attempt;
709
+ }
710
+ isCapabilityModelDiscoveryDue(pluginId, capability, userId) {
711
+ const cacheKey = this.discoveredCapabilityModelsCacheKey(pluginId, capability, userId || 'default');
712
+ const attemptedAt = this.capabilityDiscoveryAttemptedAt.get(cacheKey);
713
+ if (attemptedAt && Date.now() - attemptedAt < modelDiscoveryRetryMs()) {
714
+ return false;
715
+ }
716
+ const models = this.getDiscoveredCapabilityModels(pluginId, capability, userId);
717
+ const updatedAt = this.discoveredCapabilityModelsUpdatedAt.get(cacheKey);
718
+ return (!models || !updatedAt || Date.now() - updatedAt >= modelDiscoveryTtlMs());
719
+ }
720
+ async refreshStaleCapabilityModels(capability, userId) {
721
+ const due = this.getActivePlugins(userId).filter(plugin => {
722
+ const definition = plugin.capabilities?.[capability];
723
+ return (Boolean(definition?.models_endpoint) &&
724
+ this.isCapabilityModelDiscoveryDue(plugin.id, capability, userId));
725
+ });
726
+ if (due.length === 0)
727
+ return;
728
+ const refreshes = Promise.all(due.map(plugin => this.discoverCapabilityModels(plugin.id, capability, userId).catch(() => undefined)));
729
+ await Promise.race([
730
+ refreshes,
731
+ new Promise(resolve => setTimeout(resolve, modelDiscoveryRefreshDeadlineMs()).unref?.()),
732
+ ]);
733
+ }
734
+ async runCapabilityModelDiscovery(pluginId, capability, userId) {
735
+ const plugin = this.getPlugin(pluginId, userId);
736
+ const definition = plugin?.capabilities?.[capability];
737
+ if (!plugin || !definition) {
738
+ return {
739
+ models: [],
740
+ outcome: 'unavailable',
741
+ reason: `Plugin has no ${capability} capability`,
742
+ };
743
+ }
744
+ if (!definition.models_endpoint) {
745
+ return { models: definition.model_map, outcome: 'unchanged' };
746
+ }
747
+ const cacheKey = this.discoveredCapabilityModelsCacheKey(pluginId, capability, userId || 'default');
748
+ this.capabilityDiscoveryAttemptedAt.set(cacheKey, Date.now());
749
+ const modelsEndpoint = definition.models_endpoint;
750
+ assertSafePluginEndpoint(modelsEndpoint, `${capability} model discovery endpoint`);
751
+ const apiKey = this.getApiKey(plugin, userId);
752
+ if (pluginRequiresApiKey(plugin) && !apiKey) {
753
+ return {
754
+ models: definition.model_map,
755
+ outcome: 'missing_credentials',
756
+ reason: this.describeMissingCredential(plugin),
757
+ };
758
+ }
759
+ try {
760
+ const response = await axios.get(modelsEndpoint, {
761
+ headers: buildPluginModelDiscoveryHeaders(plugin, apiKey, modelsEndpoint),
762
+ timeout: MODEL_DISCOVERY_REQUEST_TIMEOUT_MS,
763
+ maxRedirects: 0,
764
+ });
765
+ const data = response.data?.data;
766
+ const models = Array.isArray(data)
767
+ ? Array.from(new Set(data
768
+ .map((entry) => entry?.id)
769
+ .filter((id) => typeof id === 'string' && id.length > 0)))
770
+ : [];
771
+ if (models.length === 0) {
772
+ return {
773
+ models: definition.model_map,
774
+ outcome: 'unavailable',
775
+ reason: 'The provider returned no models',
776
+ };
777
+ }
778
+ const previous = definition.model_map;
779
+ this.storeDiscoveredCapabilityModels(pluginId, capability, models, userId);
780
+ return {
781
+ models,
782
+ outcome: JSON.stringify(previous) === JSON.stringify(models)
783
+ ? 'unchanged'
784
+ : 'updated',
785
+ };
786
+ }
787
+ catch (error) {
788
+ const reason = describeModelDiscoveryFailure(error);
789
+ logger.debug('[Plugin] %s model discovery unavailable for %s (%s)', capability, pluginId, reason);
790
+ return { models: definition.model_map, outcome: 'unavailable', reason };
791
+ }
792
+ }
569
793
  /**
570
794
  * Say why no key was usable. An environment key is deliberately ignored for a
571
795
  * provider whose definition was installed into the writable plugins directory,
@@ -602,7 +826,7 @@ export class PluginService {
602
826
  reason: this.describeMissingCredential(plugin),
603
827
  };
604
828
  }
605
- const headers = buildPluginModelDiscoveryHeaders(plugin, apiKey);
829
+ const headers = buildPluginModelDiscoveryHeaders(plugin, apiKey, modelsEndpoint);
606
830
  try {
607
831
  const response = await axios.get(modelsEndpoint, {
608
832
  headers,
@@ -810,7 +1034,12 @@ export class PluginService {
810
1034
  }
811
1035
  // Wait for discovery so the activation response and the UI's first reload
812
1036
  // observe the same user-scoped model catalog.
813
- await this.discoverModels(id, userId).catch(() => { });
1037
+ await Promise.all([
1038
+ this.discoverModels(id, userId).catch(() => []),
1039
+ ...['image', 'tts', 'audio', 'video']
1040
+ .filter(capability => plugin.capabilities?.[capability]?.models_endpoint)
1041
+ .map(capability => this.discoverCapabilityModels(id, capability, userId).catch(() => undefined)),
1042
+ ]);
814
1043
  return true;
815
1044
  }
816
1045
  // Deactivate a specific plugin
@@ -897,18 +1126,45 @@ export class PluginService {
897
1126
  const providerStateScope = apiMode === 'responses'
898
1127
  ? createOpenAIResponsesStateScope(activePlugin.id, model, processedEndpoint, createPluginCredentialFingerprint(apiKey))
899
1128
  : undefined;
900
- const headers = buildPluginAuthHeaders(activePlugin, apiKey);
1129
+ const headers = buildPluginAuthHeaders(activePlugin, apiKey, processedEndpoint);
901
1130
  const { payload, headers: payloadHeaders } = buildPluginChatPayload(activePlugin, model, messages, options, pluginVars, false, apiMode, providerStateScope);
902
1131
  Object.assign(headers, payloadHeaders);
1132
+ const startedAt = Date.now();
903
1133
  try {
904
1134
  const response = await axios.post(processedEndpoint, payload, {
905
1135
  headers,
906
1136
  timeout: 60000, // 60 second timeout
907
1137
  maxRedirects: 0,
908
1138
  });
909
- return convertProviderResponse(activePlugin, response.data, model, apiMode, providerStateScope);
1139
+ const normalized = convertProviderResponse(activePlugin, response.data, model, apiMode, providerStateScope);
1140
+ pluginUsageService.record({
1141
+ userId,
1142
+ pluginId: activePlugin.id,
1143
+ pluginName: activePlugin.name,
1144
+ capability: 'chat',
1145
+ model,
1146
+ status: 'success',
1147
+ durationMs: Date.now() - startedAt,
1148
+ tokens: normalized.usage
1149
+ ? {
1150
+ promptTokens: normalized.usage.prompt_tokens,
1151
+ completionTokens: normalized.usage.completion_tokens,
1152
+ totalTokens: normalized.usage.total_tokens,
1153
+ }
1154
+ : undefined,
1155
+ });
1156
+ return normalized;
910
1157
  }
911
1158
  catch (error) {
1159
+ pluginUsageService.record({
1160
+ userId,
1161
+ pluginId: activePlugin.id,
1162
+ pluginName: activePlugin.name,
1163
+ capability: 'chat',
1164
+ model,
1165
+ status: 'error',
1166
+ durationMs: Date.now() - startedAt,
1167
+ });
912
1168
  logger.error(`Plugin request failed for ${activePlugin.id}:`, error);
913
1169
  if (error && typeof error === 'object' && 'response' in error) {
914
1170
  const axiosError = error;
@@ -948,7 +1204,7 @@ export class PluginService {
948
1204
  const providerStateScope = apiMode === 'responses'
949
1205
  ? createOpenAIResponsesStateScope(activePlugin.id, model, processedEndpoint, createPluginCredentialFingerprint(apiKey))
950
1206
  : undefined;
951
- const headers = buildPluginAuthHeaders(activePlugin, apiKey);
1207
+ const headers = buildPluginAuthHeaders(activePlugin, apiKey, processedEndpoint);
952
1208
  let payload;
953
1209
  if (activePlugin.id === 'anthropic' || apiMode === 'responses') {
954
1210
  const pluginRequest = buildPluginChatPayload(activePlugin, model, messages, options, pluginVars, true, apiMode, providerStateScope);
@@ -966,77 +1222,124 @@ export class PluginService {
966
1222
  stream: true,
967
1223
  };
968
1224
  }
969
- const response = await fetch(processedEndpoint, {
970
- method: 'POST',
971
- headers,
972
- body: JSON.stringify(payload),
973
- redirect: 'error',
974
- });
975
- if (activePlugin.id === 'anthropic') {
976
- yield* streamAnthropicResponse(response);
977
- }
978
- else if (apiMode === 'responses') {
979
- const contentType = response.headers.get('content-type') || '';
980
- if (!contentType.includes('text/event-stream')) {
981
- if (!response.ok) {
982
- const errorText = await response.text();
983
- throw new Error(`Plugin API error: ${response.status} - ${errorText.slice(0, 200)}`);
984
- }
985
- const normalized = convertProviderResponse(activePlugin, (await response.json()), model, apiMode, providerStateScope);
986
- const choice = normalized.choices[0];
987
- const message = choice?.message;
988
- if (typeof message?.content === 'string' && message.content) {
989
- yield { type: 'content', content: message.content };
990
- }
991
- if (typeof message?.reasoning_content === 'string' &&
992
- message.reasoning_content) {
993
- yield {
994
- type: 'reasoning',
995
- content: message.reasoning_content,
996
- };
997
- }
998
- for (const call of message?.tool_calls || []) {
1225
+ const startedAt = Date.now();
1226
+ let status = 'cancelled';
1227
+ let tokenUsage;
1228
+ const captureUsage = (chunk) => {
1229
+ if (chunk.type !== 'usage' || !chunk.usage)
1230
+ return;
1231
+ const { promptTokens, completionTokens, totalTokens } = chunk.usage;
1232
+ if (promptTokens === undefined &&
1233
+ completionTokens === undefined &&
1234
+ totalTokens === undefined) {
1235
+ return;
1236
+ }
1237
+ const prompt = promptTokens ?? 0;
1238
+ const completion = completionTokens ?? 0;
1239
+ tokenUsage = {
1240
+ promptTokens: prompt,
1241
+ completionTokens: completion,
1242
+ totalTokens: totalTokens ?? prompt + completion,
1243
+ };
1244
+ };
1245
+ const forward = async function* (chunks) {
1246
+ for await (const chunk of chunks) {
1247
+ captureUsage(chunk);
1248
+ yield chunk;
1249
+ }
1250
+ };
1251
+ try {
1252
+ const response = await fetch(processedEndpoint, {
1253
+ method: 'POST',
1254
+ headers,
1255
+ body: JSON.stringify(payload),
1256
+ redirect: 'error',
1257
+ });
1258
+ if (activePlugin.id === 'anthropic') {
1259
+ yield* forward(streamAnthropicResponse(response));
1260
+ }
1261
+ else if (apiMode === 'responses') {
1262
+ const contentType = response.headers.get('content-type') || '';
1263
+ if (!contentType.includes('text/event-stream')) {
1264
+ if (!response.ok) {
1265
+ const errorText = await response.text();
1266
+ throw new Error(`Plugin API error: ${response.status} - ${errorText.slice(0, 200)}`);
1267
+ }
1268
+ const normalized = convertProviderResponse(activePlugin, (await response.json()), model, apiMode, providerStateScope);
1269
+ const choice = normalized.choices[0];
1270
+ const message = choice?.message;
1271
+ if (typeof message?.content === 'string' && message.content) {
1272
+ yield { type: 'content', content: message.content };
1273
+ }
1274
+ if (typeof message?.reasoning_content === 'string' &&
1275
+ message.reasoning_content) {
1276
+ yield {
1277
+ type: 'reasoning',
1278
+ content: message.reasoning_content,
1279
+ };
1280
+ }
1281
+ for (const call of message?.tool_calls || []) {
1282
+ yield {
1283
+ type: 'tool_call',
1284
+ toolCall: {
1285
+ id: call.id,
1286
+ name: call.function.name,
1287
+ arguments: call.function.arguments,
1288
+ ...(call.providerMetadata
1289
+ ? { providerMetadata: call.providerMetadata }
1290
+ : {}),
1291
+ },
1292
+ };
1293
+ }
1294
+ if (normalized.usage) {
1295
+ const usageChunk = {
1296
+ type: 'usage',
1297
+ usage: {
1298
+ promptTokens: normalized.usage.prompt_tokens,
1299
+ completionTokens: normalized.usage.completion_tokens,
1300
+ totalTokens: normalized.usage.total_tokens,
1301
+ },
1302
+ };
1303
+ captureUsage(usageChunk);
1304
+ yield usageChunk;
1305
+ }
1306
+ const incompleteReason = typeof normalized.providerMetadata?.[OPENAI_RESPONSES_INCOMPLETE_REASON_METADATA_KEY] === 'string'
1307
+ ? normalized.providerMetadata[OPENAI_RESPONSES_INCOMPLETE_REASON_METADATA_KEY]
1308
+ : undefined;
999
1309
  yield {
1000
- type: 'tool_call',
1001
- toolCall: {
1002
- id: call.id,
1003
- name: call.function.name,
1004
- arguments: call.function.arguments,
1005
- ...(call.providerMetadata
1006
- ? { providerMetadata: call.providerMetadata }
1007
- : {}),
1008
- },
1310
+ type: 'done',
1311
+ ...(incompleteReason
1312
+ ? { doneReason: `incomplete:${incompleteReason}` }
1313
+ : {}),
1314
+ ...(normalized.providerMetadata
1315
+ ? { providerMetadata: normalized.providerMetadata }
1316
+ : {}),
1009
1317
  };
1010
1318
  }
1011
- if (normalized.usage) {
1012
- yield {
1013
- type: 'usage',
1014
- usage: {
1015
- promptTokens: normalized.usage.prompt_tokens,
1016
- completionTokens: normalized.usage.completion_tokens,
1017
- totalTokens: normalized.usage.total_tokens,
1018
- },
1019
- };
1319
+ else {
1320
+ yield* forward(streamOpenAIResponsesResponse(response, providerStateScope));
1020
1321
  }
1021
- const incompleteReason = typeof normalized.providerMetadata?.[OPENAI_RESPONSES_INCOMPLETE_REASON_METADATA_KEY] === 'string'
1022
- ? normalized.providerMetadata[OPENAI_RESPONSES_INCOMPLETE_REASON_METADATA_KEY]
1023
- : undefined;
1024
- yield {
1025
- type: 'done',
1026
- ...(incompleteReason
1027
- ? { doneReason: `incomplete:${incompleteReason}` }
1028
- : {}),
1029
- ...(normalized.providerMetadata
1030
- ? { providerMetadata: normalized.providerMetadata }
1031
- : {}),
1032
- };
1033
1322
  }
1034
1323
  else {
1035
- yield* streamOpenAIResponsesResponse(response, providerStateScope);
1324
+ yield* forward(streamOpenAICompatibleResponse(response));
1036
1325
  }
1326
+ status = 'success';
1037
1327
  }
1038
- else {
1039
- yield* streamOpenAICompatibleResponse(response);
1328
+ catch (error) {
1329
+ status = 'error';
1330
+ throw error;
1331
+ }
1332
+ finally {
1333
+ pluginUsageService.record({
1334
+ userId,
1335
+ pluginId: activePlugin.id,
1336
+ pluginName: activePlugin.name,
1337
+ capability: 'chat',
1338
+ model,
1339
+ status,
1340
+ durationMs: Date.now() - startedAt,
1341
+ tokens: tokenUsage,
1342
+ });
1040
1343
  }
1041
1344
  }
1042
1345
  // Validate plugin structure
@@ -1152,6 +1455,24 @@ export class PluginService {
1152
1455
  getPluginsByCapability(capabilityType, userId) {
1153
1456
  return this.capabilityRegistryService.getPluginsByCapability(capabilityType, userId);
1154
1457
  }
1458
+ getAvailableAudioGenModels(userId) {
1459
+ return this.audioGenerationService.getAvailableModels(userId);
1460
+ }
1461
+ executeAudioGenRequest(model, prompt, options) {
1462
+ return this.audioGenerationService.generate(model, prompt, options);
1463
+ }
1464
+ getAvailableVideoGenModels(userId) {
1465
+ return this.videoGenerationService.getAvailableModels(userId);
1466
+ }
1467
+ submitVideoGenRequest(model, prompt, options) {
1468
+ return this.videoGenerationService.submit(model, prompt, options);
1469
+ }
1470
+ pollVideoGenRequest(model, providerJobId, pluginId, userId) {
1471
+ return this.videoGenerationService.poll(model, providerJobId, pluginId, userId);
1472
+ }
1473
+ downloadVideoGenResult(model, providerJobId, pluginId, userId) {
1474
+ return this.videoGenerationService.download(model, providerJobId, pluginId, userId);
1475
+ }
1155
1476
  }
1156
1477
  export default new PluginService();
1157
1478
  //# sourceMappingURL=pluginService.js.map