libre-webui 0.15.0 → 0.16.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 (231) hide show
  1. package/README.md +26 -10
  2. package/backend/dist/db.d.ts.map +1 -1
  3. package/backend/dist/db.js +63 -0
  4. package/backend/dist/db.js.map +1 -1
  5. package/backend/dist/index.d.ts.map +1 -1
  6. package/backend/dist/index.js +4 -4
  7. package/backend/dist/index.js.map +1 -1
  8. package/backend/dist/routes/chat.d.ts.map +1 -1
  9. package/backend/dist/routes/chat.js +93 -10
  10. package/backend/dist/routes/chat.js.map +1 -1
  11. package/backend/dist/routes/imageGen.d.ts.map +1 -1
  12. package/backend/dist/routes/imageGen.js +52 -29
  13. package/backend/dist/routes/imageGen.js.map +1 -1
  14. package/backend/dist/routes/plugins.d.ts.map +1 -1
  15. package/backend/dist/routes/plugins.js +218 -41
  16. package/backend/dist/routes/plugins.js.map +1 -1
  17. package/backend/dist/routes/preferences.d.ts.map +1 -1
  18. package/backend/dist/routes/preferences.js +5 -4
  19. package/backend/dist/routes/preferences.js.map +1 -1
  20. package/backend/dist/routes/tts.js +2 -2
  21. package/backend/dist/routes/tts.js.map +1 -1
  22. package/backend/dist/services/assistantCompletionService.d.ts +2 -1
  23. package/backend/dist/services/assistantCompletionService.d.ts.map +1 -1
  24. package/backend/dist/services/assistantCompletionService.js +3 -1
  25. package/backend/dist/services/assistantCompletionService.js.map +1 -1
  26. package/backend/dist/services/chatGenerationService.d.ts +5 -3
  27. package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
  28. package/backend/dist/services/chatGenerationService.js +18 -6
  29. package/backend/dist/services/chatGenerationService.js.map +1 -1
  30. package/backend/dist/services/chatRequestService.d.ts +6 -4
  31. package/backend/dist/services/chatRequestService.d.ts.map +1 -1
  32. package/backend/dist/services/chatRequestService.js +10 -2
  33. package/backend/dist/services/chatRequestService.js.map +1 -1
  34. package/backend/dist/services/chatService.d.ts +2 -2
  35. package/backend/dist/services/chatService.d.ts.map +1 -1
  36. package/backend/dist/services/chatService.js +29 -17
  37. package/backend/dist/services/chatService.js.map +1 -1
  38. package/backend/dist/services/embeddingService.js +1 -1
  39. package/backend/dist/services/embeddingService.js.map +1 -1
  40. package/backend/dist/services/pluginActivationService.d.ts +11 -0
  41. package/backend/dist/services/pluginActivationService.d.ts.map +1 -0
  42. package/backend/dist/services/pluginActivationService.js +163 -0
  43. package/backend/dist/services/pluginActivationService.js.map +1 -0
  44. package/backend/dist/services/pluginCapabilityRegistryService.d.ts +1 -1
  45. package/backend/dist/services/pluginCapabilityRegistryService.d.ts.map +1 -1
  46. package/backend/dist/services/pluginCapabilityRegistryService.js +1 -1
  47. package/backend/dist/services/pluginCapabilityRegistryService.js.map +1 -1
  48. package/backend/dist/services/pluginCredentialsService.d.ts +16 -8
  49. package/backend/dist/services/pluginCredentialsService.d.ts.map +1 -1
  50. package/backend/dist/services/pluginCredentialsService.js +47 -14
  51. package/backend/dist/services/pluginCredentialsService.js.map +1 -1
  52. package/backend/dist/services/pluginEmbeddingService.d.ts +2 -2
  53. package/backend/dist/services/pluginEmbeddingService.d.ts.map +1 -1
  54. package/backend/dist/services/pluginEmbeddingService.js +33 -13
  55. package/backend/dist/services/pluginEmbeddingService.js.map +1 -1
  56. package/backend/dist/services/pluginImageGenerationService.d.ts +11 -7
  57. package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
  58. package/backend/dist/services/pluginImageGenerationService.js +265 -81
  59. package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
  60. package/backend/dist/services/pluginService.d.ts +55 -25
  61. package/backend/dist/services/pluginService.d.ts.map +1 -1
  62. package/backend/dist/services/pluginService.js +663 -174
  63. package/backend/dist/services/pluginService.js.map +1 -1
  64. package/backend/dist/services/pluginTTSService.d.ts +4 -4
  65. package/backend/dist/services/pluginTTSService.d.ts.map +1 -1
  66. package/backend/dist/services/pluginTTSService.js +24 -28
  67. package/backend/dist/services/pluginTTSService.js.map +1 -1
  68. package/backend/dist/services/pluginVariablesService.d.ts +6 -1
  69. package/backend/dist/services/pluginVariablesService.d.ts.map +1 -1
  70. package/backend/dist/services/pluginVariablesService.js +59 -1
  71. package/backend/dist/services/pluginVariablesService.js.map +1 -1
  72. package/backend/dist/services/preferencesService.d.ts +2 -2
  73. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  74. package/backend/dist/services/preferencesService.js +78 -10
  75. package/backend/dist/services/preferencesService.js.map +1 -1
  76. package/backend/dist/services/titleGenerationService.d.ts +6 -4
  77. package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
  78. package/backend/dist/services/titleGenerationService.js +9 -5
  79. package/backend/dist/services/titleGenerationService.js.map +1 -1
  80. package/backend/dist/services/workAgentGuidance.d.ts +1 -0
  81. package/backend/dist/services/workAgentGuidance.d.ts.map +1 -1
  82. package/backend/dist/services/workAgentGuidance.js +2 -0
  83. package/backend/dist/services/workAgentGuidance.js.map +1 -1
  84. package/backend/dist/services/workAgentService.d.ts +5 -2
  85. package/backend/dist/services/workAgentService.d.ts.map +1 -1
  86. package/backend/dist/services/workAgentService.js +379 -53
  87. package/backend/dist/services/workAgentService.js.map +1 -1
  88. package/backend/dist/services/workModelProviderService.d.ts +8 -3
  89. package/backend/dist/services/workModelProviderService.d.ts.map +1 -1
  90. package/backend/dist/services/workModelProviderService.js +293 -33
  91. package/backend/dist/services/workModelProviderService.js.map +1 -1
  92. package/backend/dist/services/workTaskService.d.ts +2 -0
  93. package/backend/dist/services/workTaskService.d.ts.map +1 -1
  94. package/backend/dist/services/workTaskService.js +89 -14
  95. package/backend/dist/services/workTaskService.js.map +1 -1
  96. package/backend/dist/storage.d.ts.map +1 -1
  97. package/backend/dist/storage.js +9 -6
  98. package/backend/dist/storage.js.map +1 -1
  99. package/backend/dist/storageMappers.d.ts +3 -0
  100. package/backend/dist/storageMappers.d.ts.map +1 -1
  101. package/backend/dist/storageMappers.js +3 -0
  102. package/backend/dist/storageMappers.js.map +1 -1
  103. package/backend/dist/types/index.d.ts +54 -1
  104. package/backend/dist/types/index.d.ts.map +1 -1
  105. package/backend/dist/types/index.js.map +1 -1
  106. package/backend/dist/types/work.d.ts +1 -1
  107. package/backend/dist/types/work.d.ts.map +1 -1
  108. package/backend/dist/utils/chatContext.d.ts +13 -1
  109. package/backend/dist/utils/chatContext.d.ts.map +1 -1
  110. package/backend/dist/utils/chatContext.js +52 -0
  111. package/backend/dist/utils/chatContext.js.map +1 -1
  112. package/backend/dist/utils/chatProviderSelection.d.ts +15 -0
  113. package/backend/dist/utils/chatProviderSelection.d.ts.map +1 -0
  114. package/backend/dist/utils/chatProviderSelection.js +59 -0
  115. package/backend/dist/utils/chatProviderSelection.js.map +1 -0
  116. package/backend/dist/utils/imageGenerationValidation.d.ts +3 -0
  117. package/backend/dist/utils/imageGenerationValidation.d.ts.map +1 -0
  118. package/backend/dist/utils/imageGenerationValidation.js +55 -0
  119. package/backend/dist/utils/imageGenerationValidation.js.map +1 -0
  120. package/backend/dist/utils/ollamaLibrary.d.ts.map +1 -1
  121. package/backend/dist/utils/ollamaLibrary.js +81 -8
  122. package/backend/dist/utils/ollamaLibrary.js.map +1 -1
  123. package/backend/dist/utils/openAIResponsesAdapter.d.ts +88 -0
  124. package/backend/dist/utils/openAIResponsesAdapter.d.ts.map +1 -0
  125. package/backend/dist/utils/openAIResponsesAdapter.js +521 -0
  126. package/backend/dist/utils/openAIResponsesAdapter.js.map +1 -0
  127. package/backend/dist/utils/pluginChatAdapter.d.ts +7 -4
  128. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  129. package/backend/dist/utils/pluginChatAdapter.js +33 -5
  130. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  131. package/backend/dist/utils/pluginChatContext.d.ts.map +1 -1
  132. package/backend/dist/utils/pluginChatContext.js +5 -1
  133. package/backend/dist/utils/pluginChatContext.js.map +1 -1
  134. package/backend/dist/utils/pluginConnectionVariables.d.ts +6 -0
  135. package/backend/dist/utils/pluginConnectionVariables.d.ts.map +1 -0
  136. package/backend/dist/utils/pluginConnectionVariables.js +52 -0
  137. package/backend/dist/utils/pluginConnectionVariables.js.map +1 -0
  138. package/backend/dist/utils/pluginDefinitionTrust.d.ts +9 -0
  139. package/backend/dist/utils/pluginDefinitionTrust.d.ts.map +1 -0
  140. package/backend/dist/utils/pluginDefinitionTrust.js +71 -0
  141. package/backend/dist/utils/pluginDefinitionTrust.js.map +1 -0
  142. package/backend/dist/utils/pluginStreamAdapter.d.ts +3 -0
  143. package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -1
  144. package/backend/dist/utils/pluginStreamAdapter.js +410 -0
  145. package/backend/dist/utils/pluginStreamAdapter.js.map +1 -1
  146. package/backend/dist/utils/pluginStreaming.d.ts +7 -1
  147. package/backend/dist/utils/pluginStreaming.d.ts.map +1 -1
  148. package/backend/dist/utils/pluginStreaming.js +12 -1
  149. package/backend/dist/utils/pluginStreaming.js.map +1 -1
  150. package/backend/dist/utils/pluginValidation.d.ts +21 -2
  151. package/backend/dist/utils/pluginValidation.d.ts.map +1 -1
  152. package/backend/dist/utils/pluginValidation.js +190 -17
  153. package/backend/dist/utils/pluginValidation.js.map +1 -1
  154. package/backend/dist/utils/pluginVariableValidation.d.ts +7 -0
  155. package/backend/dist/utils/pluginVariableValidation.d.ts.map +1 -1
  156. package/backend/dist/utils/pluginVariableValidation.js +79 -4
  157. package/backend/dist/utils/pluginVariableValidation.js.map +1 -1
  158. package/backend/dist/websocketServer.d.ts.map +1 -1
  159. package/backend/dist/websocketServer.js +16 -3
  160. package/backend/dist/websocketServer.js.map +1 -1
  161. package/backend/package.json +1 -1
  162. package/frontend/dist/assets/index-CKV9Lnj8.js +5 -0
  163. package/frontend/dist/assets/index-Dme2Ozol.css +1 -0
  164. package/frontend/dist/index.html +4 -4
  165. package/frontend/dist/js/{ArtifactContainer-CXfmVx8T.js → ArtifactContainer-B0uVR8Z9.js} +1 -1
  166. package/frontend/dist/js/{ArtifactDemoPage-B1_AuMd2.js → ArtifactDemoPage-DBVPHFxk.js} +1 -1
  167. package/frontend/dist/js/{ArtifactSlideOutPanel-C0eTguNL.js → ArtifactSlideOutPanel-Cg5kAwv9.js} +1 -1
  168. package/frontend/dist/js/ChatPage-CPHlIZqU.js +16 -0
  169. package/frontend/dist/js/{FirstTimeSetup-DOoZUaRs.js → FirstTimeSetup-DksL5arK.js} +1 -1
  170. package/frontend/dist/js/GalleryPage-1mdUGo_N.js +1 -0
  171. package/frontend/dist/js/LibreClawPage-qGr1Jy8p.js +1 -0
  172. package/frontend/dist/js/{ModelManagerModals-DneqFQJC.js → ModelManagerModals-DltRprHU.js} +1 -1
  173. package/frontend/dist/js/ModelSelector-CUnwRmNU.js +1 -0
  174. package/frontend/dist/js/ModelsPage-DMYVg3LT.js +2 -0
  175. package/frontend/dist/js/PersonasPage-Ct-KKW-j.js +13 -0
  176. package/frontend/dist/js/{RichMessageContent-WQLdJ5jq.js → RichMessageContent-CGBAcUES.js} +1 -1
  177. package/frontend/dist/js/SettingsModal-BVj3gFCS.js +1 -0
  178. package/frontend/dist/js/UserManagementPage-D3tLGtTu.js +1 -0
  179. package/frontend/dist/js/WorkPage-DhTjy-kf.js +2 -0
  180. package/frontend/dist/js/api-YWoSobKv.js +19 -0
  181. package/frontend/dist/js/{ar-CCVY0-7X.js → ar-KceI0PfT.js} +1 -1
  182. package/frontend/dist/js/{bn-CoU5CSph.js → bn-DsKo2dzS.js} +1 -1
  183. package/frontend/dist/js/{cs-CR6pjIbG.js → cs-6UKhKj0A.js} +1 -1
  184. package/frontend/dist/js/{da-CkyYITRz.js → da-CJgZ3YuL.js} +1 -1
  185. package/frontend/dist/js/{de-DwNANzB-.js → de-BZAmOuCT.js} +1 -1
  186. package/frontend/dist/js/{es-TxygiaVQ.js → es-CD4u7no9.js} +1 -1
  187. package/frontend/dist/js/{estree-DPzMpjFm.js → estree-PDLT672k.js} +1 -1
  188. package/frontend/dist/js/{fr-DvEqVpZr.js → fr-Cou2jOS3.js} +1 -1
  189. package/frontend/dist/js/{hi-CyrWkyh7.js → hi-ZxkbNCyB.js} +1 -1
  190. package/frontend/dist/js/{html-R50jmBYq.js → html-CkhF9wDQ.js} +1 -1
  191. package/frontend/dist/js/{id-PJjIGHGM.js → id-DT9CqaWQ.js} +1 -1
  192. package/frontend/dist/js/{is-ChXcN1tI.js → is-BWSbAIoq.js} +1 -1
  193. package/frontend/dist/js/{it-DsgM8-MJ.js → it-CdNoTXm3.js} +1 -1
  194. package/frontend/dist/js/{ja-ivtt80N2.js → ja-CIuQg0xv.js} +1 -1
  195. package/frontend/dist/js/{ko-B8l_Ice1.js → ko-BWRUjDOH.js} +1 -1
  196. package/frontend/dist/js/{markdown-C08x2H-K.js → markdown-B1iNUbse.js} +1 -1
  197. package/frontend/dist/js/{ms-DoUBI6Qi.js → ms-gJstIj2a.js} +1 -1
  198. package/frontend/dist/js/{nl-DsX5Hj8m.js → nl-CydjVNUw.js} +1 -1
  199. package/frontend/dist/js/{pl-Db_NKL4x.js → pl-DPN9mAa5.js} +1 -1
  200. package/frontend/dist/js/{pt-CpzBhb5J.js → pt-MRXGqIjQ.js} +1 -1
  201. package/frontend/dist/js/{ru-DCXvJyXJ.js → ru-CvQ9GkJF.js} +1 -1
  202. package/frontend/dist/js/{standalone-DpMZUvjd.js → standalone-CCd6rkf-.js} +1 -1
  203. package/frontend/dist/js/{sv-DcVoybc7.js → sv-BxS2fvhU.js} +1 -1
  204. package/frontend/dist/js/{th-D0G7HyQn.js → th-DMxyn6e4.js} +1 -1
  205. package/frontend/dist/js/{tr-CMDOh4Aq.js → tr-BTPldUaA.js} +1 -1
  206. package/frontend/dist/js/{typescript-B6y_hxBm.js → typescript-DKJ1uM5L.js} +1 -1
  207. package/frontend/dist/js/ui-vendor-CAFEDSaO.js +177 -0
  208. package/frontend/dist/js/{uk-D2MMa6LP.js → uk-BGxZOKgp.js} +1 -1
  209. package/frontend/dist/js/useBaseQuery-MuQj7vA5.js +1 -0
  210. package/frontend/dist/js/useQuery-CYGPkxWR.js +1 -0
  211. package/frontend/dist/js/{vi-B8uHT8yh.js → vi-82JkwFsQ.js} +1 -1
  212. package/frontend/dist/js/{zh-CNqa_15K.js → zh-Bxxfwci3.js} +1 -1
  213. package/package.json +3 -2
  214. package/plugins/github.json +1 -2
  215. package/plugins/huggingface.json +30 -30
  216. package/plugins/mlx-lm.json +55 -0
  217. package/plugins/openai.json +53 -22
  218. package/frontend/dist/assets/index-Bv-twCuY.css +0 -1
  219. package/frontend/dist/assets/index-uCHBH_PM.js +0 -5
  220. package/frontend/dist/js/ChatPage-1bK0gDBy.js +0 -16
  221. package/frontend/dist/js/GalleryPage-6WwyH3kt.js +0 -1
  222. package/frontend/dist/js/LibreClawPage-ChHVfPLb.js +0 -1
  223. package/frontend/dist/js/ModelsPage-Dw6wdiMt.js +0 -2
  224. package/frontend/dist/js/PersonasPage-BxdI2e1-.js +0 -13
  225. package/frontend/dist/js/SettingsModal-C-cK6TNb.js +0 -1
  226. package/frontend/dist/js/UserManagementPage-DqjNXPkf.js +0 -1
  227. package/frontend/dist/js/WorkPage-MGR3odo6.js +0 -2
  228. package/frontend/dist/js/api-DD5qhhLf.js +0 -19
  229. package/frontend/dist/js/ui-vendor-DUqvZsfs.js +0 -177
  230. package/frontend/dist/js/useBaseQuery-Duml3DYp.js +0 -1
  231. package/frontend/dist/js/useQuery-C0lZwNoc.js +0 -1
@@ -15,11 +15,15 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import axios from 'axios';
18
+ import { createHash } from 'crypto';
18
19
  import { getOpenAICompatibleSamplingParameters, resolvePluginChatParameters, } from '../utils/pluginChatAdapter.js';
19
- import { streamAnthropicResponse, streamOpenAICompatibleResponse, } from '../utils/pluginStreamAdapter.js';
20
- import { applyModelEndpointTemplate, assertSafePluginEndpoint, buildPluginAuthHeaders, resolvePluginEndpoint, validatePluginModel, } from '../utils/pluginValidation.js';
20
+ import { OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY, OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY, createOpenAIResponsesStateScope, createPluginCredentialFingerprint, normalizeOpenAIResponsesResponse, toOpenAIResponsesInput, toOpenAIResponsesTools, } from '../utils/openAIResponsesAdapter.js';
21
+ import { streamAnthropicResponse, streamOpenAICompatibleResponse, streamOpenAIResponsesResponse, } from '../utils/pluginStreamAdapter.js';
22
+ import { applyModelEndpointTemplate, assertSafePluginEndpoint, buildPluginAuthHeaders, inferPluginApiMode, pluginRequiresApiKey, resolvePluginApiConfig, validatePluginModel, } from '../utils/pluginValidation.js';
21
23
  import ollamaService from './ollamaService.js';
22
24
  import pluginService from './pluginService.js';
25
+ export const WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY = 'libreToolArgumentsError';
26
+ export const WORK_TOOL_ARGUMENTS_ERROR_MESSAGE = 'The provider returned incomplete or invalid JSON for this tool call, likely because its output-token limit was reached. Retry with a smaller payload; split large write_file content into focused files.';
23
27
  export class WorkModelProviderError extends Error {
24
28
  constructor(message, status, code) {
25
29
  super(message);
@@ -67,6 +71,55 @@ export class WorkModelProviderService {
67
71
  throw new WorkModelProviderError(`Model "${cleaned}" does not advertise tool support.`, 422, 'WORK_MODEL_TOOLS_UNSUPPORTED');
68
72
  }
69
73
  }
74
+ getResponsesStateScope(model, provider, userId) {
75
+ if (provider.providerType !== 'plugin')
76
+ return undefined;
77
+ const providerId = provider.providerId?.trim();
78
+ if (!providerId)
79
+ return undefined;
80
+ const plugin = this.dependencies.plugins.getPlugin(providerId, userId);
81
+ if (!plugin || !plugin.active || !plugin.model_map.includes(model)) {
82
+ return undefined;
83
+ }
84
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
85
+ const apiConfig = resolvePluginApiConfig(plugin, variables);
86
+ if (apiConfig.apiMode !== 'responses')
87
+ return undefined;
88
+ const endpoint = applyModelEndpointTemplate(apiConfig.endpoint, model);
89
+ const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
90
+ if (pluginRequiresApiKey(plugin) && !apiKey)
91
+ return undefined;
92
+ return createOpenAIResponsesStateScope(plugin.id, model, endpoint, createPluginCredentialFingerprint(apiKey));
93
+ }
94
+ getRoutingFingerprint(model, provider, userId) {
95
+ if (provider.providerType === 'ollama') {
96
+ assertOllamaProvider(provider);
97
+ return createHash('sha256')
98
+ .update(JSON.stringify({
99
+ version: 1,
100
+ providerType: 'ollama',
101
+ model,
102
+ }))
103
+ .digest('hex');
104
+ }
105
+ const plugin = this.requireExactPlugin(provider.providerId, model, userId);
106
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
107
+ const apiConfig = resolvePluginApiConfig(plugin, variables);
108
+ const endpoint = applyModelEndpointTemplate(apiConfig.endpoint, model);
109
+ assertSafePluginEndpoint(endpoint, 'Work model endpoint');
110
+ const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
111
+ return createHash('sha256')
112
+ .update(JSON.stringify({
113
+ version: 2,
114
+ providerType: 'plugin',
115
+ providerId: plugin.id,
116
+ model,
117
+ apiMode: apiConfig.apiMode,
118
+ endpoint,
119
+ credentialFingerprint: createPluginCredentialFingerprint(apiKey),
120
+ }))
121
+ .digest('hex');
122
+ }
70
123
  async generateChatResponse(request, provider, userId, signal) {
71
124
  if (provider.providerType === 'ollama') {
72
125
  assertOllamaProvider(provider);
@@ -86,17 +139,18 @@ export class WorkModelProviderService {
86
139
  }
87
140
  hasConfiguredPlugin(userId) {
88
141
  return this.dependencies.plugins
89
- .getActivePlugins()
142
+ .getActivePlugins(userId)
90
143
  .filter(isWorkPlugin)
91
144
  .some(plugin => plugin.model_map.length > 0 &&
92
- Boolean(this.dependencies.plugins.getApiKey(plugin, userId)));
145
+ (!pluginRequiresApiKey(plugin) ||
146
+ Boolean(this.dependencies.plugins.getApiKey(plugin, userId))));
93
147
  }
94
148
  requireExactPlugin(providerId, model, userId) {
95
149
  const cleanedProviderId = providerId?.trim();
96
150
  if (!cleanedProviderId) {
97
151
  throw new WorkModelProviderError('A plugin provider ID is required.', 400, 'WORK_PLUGIN_ID_REQUIRED');
98
152
  }
99
- const plugin = this.dependencies.plugins.getPlugin(cleanedProviderId);
153
+ const plugin = this.dependencies.plugins.getPlugin(cleanedProviderId, userId);
100
154
  if (!plugin || !plugin.active) {
101
155
  throw new WorkModelProviderError(`Plugin "${cleanedProviderId}" is not active.`, 422, 'WORK_PLUGIN_UNAVAILABLE');
102
156
  }
@@ -104,30 +158,37 @@ export class WorkModelProviderService {
104
158
  if (!plugin.model_map.includes(model)) {
105
159
  throw new WorkModelProviderError(`Model "${model}" is not configured for plugin "${plugin.id}".`, 422, 'WORK_PLUGIN_MODEL_UNAVAILABLE');
106
160
  }
107
- if (!this.dependencies.plugins.getApiKey(plugin, userId)) {
161
+ resolvePluginApiConfig(plugin, this.dependencies.plugins.getPluginVariables(plugin, userId));
162
+ if (pluginRequiresApiKey(plugin) &&
163
+ !this.dependencies.plugins.getApiKey(plugin, userId)) {
108
164
  throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
109
165
  }
110
166
  return plugin;
111
167
  }
112
168
  async generatePluginResponse(plugin, request, userId, signal) {
113
169
  validatePluginModel(request.model);
170
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
171
+ const apiConfig = resolvePluginApiConfig(plugin, variables);
172
+ const endpoint = applyModelEndpointTemplate(apiConfig.endpoint, request.model);
173
+ assertSafePluginEndpoint(endpoint, 'Work model endpoint');
114
174
  const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
115
- if (!apiKey) {
175
+ if (pluginRequiresApiKey(plugin) && !apiKey) {
116
176
  throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
117
177
  }
118
- const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
119
- const endpoint = applyModelEndpointTemplate(resolvePluginEndpoint(plugin.endpoint, variables.endpoint), request.model);
120
- assertSafePluginEndpoint(endpoint, 'Work model endpoint');
178
+ const providerStateScope = apiConfig.apiMode === 'responses'
179
+ ? createOpenAIResponsesStateScope(plugin.id, request.model, endpoint, createPluginCredentialFingerprint(apiKey))
180
+ : undefined;
121
181
  const headers = buildPluginAuthHeaders(plugin, apiKey);
122
- const { payload, extraHeaders } = buildPluginWorkPayload(plugin, request, variables);
182
+ const { payload, extraHeaders } = buildPluginWorkPayload(plugin, request, variables, apiConfig.apiMode, providerStateScope);
123
183
  Object.assign(headers, extraHeaders);
124
184
  try {
125
185
  const response = await this.dependencies.post(endpoint, payload, {
126
186
  headers,
127
187
  signal,
128
188
  timeout: 300000,
189
+ maxRedirects: 0,
129
190
  });
130
- return normalizePluginWorkResponse(plugin, response.data, request.model);
191
+ return normalizePluginWorkResponse(plugin, response.data, request.model, apiConfig.apiMode, providerStateScope);
131
192
  }
132
193
  catch (error) {
133
194
  if (signal?.aborted)
@@ -210,18 +271,23 @@ export class WorkModelProviderService {
210
271
  }
211
272
  async generatePluginStream(plugin, request, userId, observer, signal) {
212
273
  validatePluginModel(request.model);
274
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
275
+ const apiConfig = resolvePluginApiConfig(plugin, variables);
276
+ let endpoint = applyModelEndpointTemplate(apiConfig.endpoint, request.model);
277
+ assertSafePluginEndpoint(endpoint, 'Work model endpoint');
213
278
  const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
214
- if (!apiKey) {
279
+ if (pluginRequiresApiKey(plugin) && !apiKey) {
215
280
  throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
216
281
  }
217
- const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
218
- let endpoint = applyModelEndpointTemplate(resolvePluginEndpoint(plugin.endpoint, variables.endpoint), request.model);
282
+ const providerStateScope = apiConfig.apiMode === 'responses'
283
+ ? createOpenAIResponsesStateScope(plugin.id, request.model, endpoint, createPluginCredentialFingerprint(apiKey))
284
+ : undefined;
219
285
  if (plugin.id === 'gemini') {
220
286
  endpoint = geminiStreamingEndpoint(endpoint);
221
287
  }
222
288
  assertSafePluginEndpoint(endpoint, 'Work model endpoint');
223
289
  const headers = buildPluginAuthHeaders(plugin, apiKey);
224
- const { payload, extraHeaders } = buildPluginWorkPayload(plugin, { ...request, stream: true }, variables);
290
+ const { payload, extraHeaders } = buildPluginWorkPayload(plugin, { ...request, stream: true }, variables, apiConfig.apiMode, providerStateScope);
225
291
  Object.assign(headers, extraHeaders);
226
292
  const timeoutSignal = AbortSignal.timeout(300000);
227
293
  const requestSignal = signal
@@ -233,13 +299,14 @@ export class WorkModelProviderService {
233
299
  headers,
234
300
  body: JSON.stringify(payload),
235
301
  signal: requestSignal,
302
+ redirect: 'error',
236
303
  });
237
304
  const contentType = response.headers.get('content-type') || '';
238
305
  if (response.ok &&
239
306
  !contentType.includes('text/event-stream') &&
240
307
  !contentType.includes('application/x-ndjson')) {
241
308
  const data = (await response.json());
242
- const normalized = normalizePluginWorkResponse(plugin, data, request.model);
309
+ const normalized = normalizePluginWorkResponse(plugin, data, request.model, apiConfig.apiMode, providerStateScope);
243
310
  if (normalized.message.thinking) {
244
311
  observer.onReasoning?.(normalized.message.thinking);
245
312
  }
@@ -252,7 +319,9 @@ export class WorkModelProviderService {
252
319
  ? streamAnthropicResponse(response)
253
320
  : plugin.id === 'gemini'
254
321
  ? streamGeminiWorkResponse(response)
255
- : streamOpenAICompatibleResponse(response);
322
+ : apiConfig.apiMode === 'responses'
323
+ ? streamOpenAIResponsesResponse(response, providerStateScope)
324
+ : streamOpenAICompatibleResponse(response);
256
325
  return await collectPluginWorkStream(chunks, request.model, observer);
257
326
  }
258
327
  catch (error) {
@@ -267,7 +336,7 @@ export class WorkModelProviderService {
267
336
  }
268
337
  }
269
338
  }
270
- export function buildPluginWorkPayload(plugin, request, variables = {}) {
339
+ export function buildPluginWorkPayload(plugin, request, variables = {}, apiMode = resolvePluginApiConfig(plugin, variables).apiMode, providerStateScope) {
271
340
  const options = (request.options || {});
272
341
  const params = resolvePluginChatParameters(options, variables);
273
342
  if (plugin.id === 'anthropic') {
@@ -282,6 +351,24 @@ export function buildPluginWorkPayload(plugin, request, variables = {}) {
282
351
  extraHeaders: {},
283
352
  };
284
353
  }
354
+ if (apiMode === 'responses') {
355
+ const sampling = getOpenAICompatibleSamplingParameters(plugin, params);
356
+ const tools = toOpenAIResponsesTools(request.tools || []);
357
+ return {
358
+ payload: {
359
+ model: request.model,
360
+ input: toOpenAIResponsesWorkInput(request.messages, providerStateScope),
361
+ ...(tools.length ? { tools, tool_choice: 'auto' } : {}),
362
+ temperature: sampling.temperature,
363
+ top_p: sampling.top_p,
364
+ max_output_tokens: params.maxTokens,
365
+ stream: Boolean(request.stream),
366
+ store: false,
367
+ include: ['reasoning.encrypted_content'],
368
+ },
369
+ extraHeaders: {},
370
+ };
371
+ }
285
372
  return {
286
373
  payload: {
287
374
  model: request.model,
@@ -295,13 +382,17 @@ export function buildPluginWorkPayload(plugin, request, variables = {}) {
295
382
  extraHeaders: {},
296
383
  };
297
384
  }
298
- export function normalizePluginWorkResponse(plugin, response, model) {
385
+ export function normalizePluginWorkResponse(plugin, response, model, apiMode = plugin.api_mode ||
386
+ inferPluginApiMode(plugin.endpoint), providerStateScope) {
299
387
  if (plugin.id === 'anthropic') {
300
388
  return normalizeAnthropicWorkResponse(response, model);
301
389
  }
302
390
  if (plugin.id === 'gemini') {
303
391
  return normalizeGeminiWorkResponse(response, model);
304
392
  }
393
+ if (apiMode === 'responses') {
394
+ return normalizeOpenAIResponsesWorkResponse(response, model, providerStateScope);
395
+ }
305
396
  return normalizeOpenAIWorkResponse(response, model);
306
397
  }
307
398
  export function toOpenAIWorkMessages(messages) {
@@ -342,12 +433,94 @@ export function toOpenAIWorkMessages(messages) {
342
433
  role: 'tool',
343
434
  content: message.content,
344
435
  name: message.tool_name,
345
- tool_call_id: matching?.id || `work-tool-${messageIndex}`,
436
+ tool_call_id: message.tool_call_id || matching?.id || `work-tool-${messageIndex}`,
346
437
  };
347
438
  }
348
439
  return { role: message.role, content: message.content };
349
440
  });
350
441
  }
442
+ export function toOpenAIResponsesWorkInput(messages, expectedStateScope) {
443
+ const input = [];
444
+ let pendingCalls = [];
445
+ for (const [messageIndex, message] of messages.entries()) {
446
+ if (message.role === 'assistant') {
447
+ const toolCalls = normalizeOutboundToolCalls(message.tool_calls);
448
+ const responseOutputItems = Array.isArray(message.providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY])
449
+ ? message.providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]
450
+ : [];
451
+ const replayableOutputItems = expectedStateScope === undefined ||
452
+ message.providerMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] === expectedStateScope
453
+ ? responseOutputItems.flatMap(rawItem => {
454
+ const item = asObject(rawItem);
455
+ return item ? [{ ...item }] : [];
456
+ })
457
+ : [];
458
+ if (replayableOutputItems.length > 0) {
459
+ pendingCalls = replayableOutputItems.flatMap(item => {
460
+ if (item.type !== 'function_call')
461
+ return [];
462
+ const id = typeof item.call_id === 'string'
463
+ ? item.call_id
464
+ : typeof item.id === 'string'
465
+ ? item.id
466
+ : undefined;
467
+ return id && typeof item.name === 'string'
468
+ ? [{ id, name: item.name }]
469
+ : [];
470
+ });
471
+ input.push(...replayableOutputItems);
472
+ continue;
473
+ }
474
+ pendingCalls = toolCalls.map(call => ({
475
+ id: String(call.id),
476
+ name: String(call.function.name),
477
+ }));
478
+ // Compatibility for Work messages created before full Responses output
479
+ // items were retained.
480
+ for (const call of toolCalls) {
481
+ const metadata = asObject(call.providerMetadata);
482
+ const reasoningItems = Array.isArray(metadata?.openAIResponsesReasoningItems)
483
+ ? metadata.openAIResponsesReasoningItems
484
+ : [];
485
+ for (const rawItem of reasoningItems) {
486
+ const item = asObject(rawItem);
487
+ if (item?.type === 'reasoning') {
488
+ input.push({ ...item });
489
+ }
490
+ }
491
+ }
492
+ input.push(...toOpenAIResponsesInput([
493
+ {
494
+ role: 'assistant',
495
+ content: message.content,
496
+ tool_calls: toolCalls,
497
+ },
498
+ ]));
499
+ continue;
500
+ }
501
+ if (message.role === 'tool') {
502
+ const matchingIndex = pendingCalls.findIndex(call => !message.tool_name || call.name === message.tool_name);
503
+ const matching = matchingIndex >= 0
504
+ ? pendingCalls.splice(matchingIndex, 1)[0]
505
+ : undefined;
506
+ input.push(...toOpenAIResponsesInput([
507
+ {
508
+ role: 'tool',
509
+ content: message.content,
510
+ name: message.tool_name,
511
+ tool_call_id: message.tool_call_id ||
512
+ matching?.id ||
513
+ `work-tool-${messageIndex}`,
514
+ },
515
+ ]));
516
+ continue;
517
+ }
518
+ input.push(...toOpenAIResponsesInput([
519
+ { role: message.role, content: message.content },
520
+ ]));
521
+ }
522
+ return input;
523
+ }
351
524
  function buildAnthropicWorkPayload(model, messages, tools, maxTokens, stream = false) {
352
525
  const system = messages
353
526
  .filter(message => message.role === 'system')
@@ -523,6 +696,45 @@ function normalizeOpenAIWorkResponse(response, model) {
523
696
  ? message.reasoning_content
524
697
  : '');
525
698
  }
699
+ function normalizeOpenAIResponsesWorkResponse(response, model, providerStateScope) {
700
+ const normalized = normalizeOpenAIResponsesResponse(response, model, providerStateScope);
701
+ const choice = normalized.choices[0];
702
+ const message = asObject(choice?.message);
703
+ if (!message) {
704
+ throw new WorkModelProviderError('Plugin returned an invalid Responses API response.', 502, 'WORK_PLUGIN_INVALID_RESPONSE');
705
+ }
706
+ const reasoning = typeof message.reasoning_content === 'string'
707
+ ? message.reasoning_content
708
+ : '';
709
+ const toolCalls = normalizeInboundToolCalls(message.tool_calls);
710
+ if (reasoning && toolCalls[0]) {
711
+ toolCalls[0].providerMetadata = {
712
+ ...asObject(toolCalls[0].providerMetadata),
713
+ openAIReasoningContent: reasoning,
714
+ };
715
+ }
716
+ const incompleteReason = response.status === 'incomplete'
717
+ ? typeof asObject(response.incomplete_details)?.reason === 'string'
718
+ ? String(asObject(response.incomplete_details)?.reason)
719
+ : 'unknown'
720
+ : undefined;
721
+ return {
722
+ ...workResponse(model, contentText(message.content), toolCalls, reasoning, {
723
+ ...(normalized.providerMetadata
724
+ ? { providerMetadata: normalized.providerMetadata }
725
+ : {}),
726
+ ...(incompleteReason
727
+ ? { doneReason: `incomplete:${incompleteReason}` }
728
+ : {}),
729
+ }),
730
+ ...(normalized.usage
731
+ ? {
732
+ prompt_eval_count: normalized.usage.prompt_tokens,
733
+ eval_count: normalized.usage.completion_tokens,
734
+ }
735
+ : {}),
736
+ };
737
+ }
526
738
  function normalizeAnthropicWorkResponse(response, model) {
527
739
  const blocks = Array.isArray(response.content) ? response.content : [];
528
740
  const text = [];
@@ -590,7 +802,7 @@ function normalizeGeminiWorkResponse(response, model) {
590
802
  }
591
803
  return workResponse(model, text.join(''), calls, reasoning.join(''));
592
804
  }
593
- function workResponse(model, content, toolCalls, reasoning = '') {
805
+ function workResponse(model, content, toolCalls, reasoning = '', options = {}) {
594
806
  return {
595
807
  model,
596
808
  created_at: new Date().toISOString(),
@@ -599,14 +811,20 @@ function workResponse(model, content, toolCalls, reasoning = '') {
599
811
  content,
600
812
  ...(reasoning ? { thinking: reasoning } : {}),
601
813
  tool_calls: toolCalls,
814
+ ...(options.providerMetadata
815
+ ? { providerMetadata: options.providerMetadata }
816
+ : {}),
602
817
  },
603
818
  done: true,
819
+ ...(options.doneReason ? { done_reason: options.doneReason } : {}),
604
820
  };
605
821
  }
606
822
  async function collectPluginWorkStream(chunks, model, observer) {
607
823
  let content = '';
608
824
  let reasoning = '';
609
825
  let usage = {};
826
+ let doneReason;
827
+ let providerMetadata;
610
828
  const toolCalls = [];
611
829
  for await (const chunk of chunks) {
612
830
  if (chunk.type === 'content') {
@@ -625,7 +843,15 @@ async function collectPluginWorkStream(chunks, model, observer) {
625
843
  continue;
626
844
  }
627
845
  if (chunk.type === 'tool_call') {
628
- const metadata = chunk.toolCall.providerMetadata;
846
+ const parsedArguments = parseToolArgumentsWithStatus(chunk.toolCall.arguments);
847
+ const metadata = {
848
+ ...chunk.toolCall.providerMetadata,
849
+ ...(parsedArguments.error
850
+ ? {
851
+ [WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY]: parsedArguments.error,
852
+ }
853
+ : {}),
854
+ };
629
855
  toolCalls.push({
630
856
  id: chunk.toolCall.id || `work-plugin-${toolCalls.length}`,
631
857
  ...(typeof metadata?.geminiThoughtSignature === 'string'
@@ -639,9 +865,16 @@ async function collectPluginWorkStream(chunks, model, observer) {
639
865
  : {}),
640
866
  function: {
641
867
  name: chunk.toolCall.name,
642
- arguments: parseToolArguments(chunk.toolCall.arguments),
868
+ arguments: parsedArguments.arguments,
643
869
  },
644
870
  });
871
+ continue;
872
+ }
873
+ if (chunk.type === 'done') {
874
+ doneReason = chunk.doneReason;
875
+ providerMetadata = chunk.providerMetadata
876
+ ? { ...providerMetadata, ...chunk.providerMetadata }
877
+ : providerMetadata;
645
878
  }
646
879
  }
647
880
  if (reasoning && toolCalls[0]) {
@@ -655,7 +888,10 @@ async function collectPluginWorkStream(chunks, model, observer) {
655
888
  }
656
889
  }
657
890
  return {
658
- ...workResponse(model, content, toolCalls, reasoning),
891
+ ...workResponse(model, content, toolCalls, reasoning, {
892
+ ...(providerMetadata ? { providerMetadata } : {}),
893
+ ...(doneReason ? { doneReason } : {}),
894
+ }),
659
895
  prompt_eval_count: usage.promptTokens,
660
896
  eval_count: usage.completionTokens,
661
897
  };
@@ -808,19 +1044,29 @@ function normalizeInboundToolCalls(value) {
808
1044
  const fn = asObject(call?.function);
809
1045
  if (!fn || typeof fn.name !== 'string')
810
1046
  return [];
811
- const providerMetadata = asObject(call?.providerMetadata);
1047
+ const parsedArguments = parseToolArgumentsWithStatus(fn.arguments);
1048
+ const providerMetadata = {
1049
+ ...asObject(call?.providerMetadata),
1050
+ ...(parsedArguments.error
1051
+ ? {
1052
+ [WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY]: parsedArguments.error,
1053
+ }
1054
+ : {}),
1055
+ };
812
1056
  return [
813
1057
  {
814
1058
  id: typeof call?.id === 'string' ? call.id : `work-plugin-${index}`,
815
1059
  ...(typeof call?.thoughtSignature === 'string'
816
1060
  ? { thoughtSignature: call.thoughtSignature }
817
1061
  : {}),
818
- ...(providerMetadata ? { providerMetadata } : {}),
1062
+ ...(Object.keys(providerMetadata).length > 0
1063
+ ? { providerMetadata }
1064
+ : {}),
819
1065
  function: {
820
1066
  name: fn.name,
821
- arguments: typeof fn.arguments === 'string'
1067
+ arguments: typeof fn.arguments === 'string' && !parsedArguments.error
822
1068
  ? fn.arguments
823
- : asObject(fn.arguments) || {},
1069
+ : parsedArguments.arguments,
824
1070
  },
825
1071
  },
826
1072
  ];
@@ -844,18 +1090,32 @@ function toAnthropicTools(tools) {
844
1090
  });
845
1091
  }
846
1092
  function parseToolArguments(value) {
1093
+ return parseToolArgumentsWithStatus(value).arguments;
1094
+ }
1095
+ function parseToolArgumentsWithStatus(value) {
847
1096
  if (value && typeof value === 'object' && !Array.isArray(value)) {
848
- return value;
1097
+ return { arguments: value };
849
1098
  }
850
1099
  if (typeof value === 'string') {
1100
+ if (!value.trim())
1101
+ return { arguments: {} };
851
1102
  try {
852
- return asObject(JSON.parse(value)) || {};
1103
+ const parsed = asObject(JSON.parse(value));
1104
+ return parsed
1105
+ ? { arguments: parsed }
1106
+ : {
1107
+ arguments: {},
1108
+ error: WORK_TOOL_ARGUMENTS_ERROR_MESSAGE,
1109
+ };
853
1110
  }
854
1111
  catch {
855
- return {};
1112
+ return {
1113
+ arguments: {},
1114
+ error: WORK_TOOL_ARGUMENTS_ERROR_MESSAGE,
1115
+ };
856
1116
  }
857
1117
  }
858
- return {};
1118
+ return { arguments: {} };
859
1119
  }
860
1120
  function contentText(value) {
861
1121
  if (typeof value === 'string')