libre-webui 0.14.3 → 0.15.1

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 (156) hide show
  1. package/README.md +117 -33
  2. package/backend/dist/db.d.ts.map +1 -1
  3. package/backend/dist/db.js +84 -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 +99 -35
  7. package/backend/dist/index.js.map +1 -1
  8. package/backend/dist/middleware/auth.d.ts.map +1 -1
  9. package/backend/dist/middleware/auth.js +27 -2
  10. package/backend/dist/middleware/auth.js.map +1 -1
  11. package/backend/dist/routes/plugins.d.ts.map +1 -1
  12. package/backend/dist/routes/plugins.js +1 -1
  13. package/backend/dist/routes/plugins.js.map +1 -1
  14. package/backend/dist/routes/users.d.ts.map +1 -1
  15. package/backend/dist/routes/users.js +40 -6
  16. package/backend/dist/routes/users.js.map +1 -1
  17. package/backend/dist/routes/work.d.ts +3 -0
  18. package/backend/dist/routes/work.d.ts.map +1 -0
  19. package/backend/dist/routes/work.js +604 -0
  20. package/backend/dist/routes/work.js.map +1 -0
  21. package/backend/dist/services/ollamaService.d.ts +7 -2
  22. package/backend/dist/services/ollamaService.d.ts.map +1 -1
  23. package/backend/dist/services/ollamaService.js +8 -8
  24. package/backend/dist/services/ollamaService.js.map +1 -1
  25. package/backend/dist/services/pluginService.d.ts.map +1 -1
  26. package/backend/dist/services/pluginService.js +15 -15
  27. package/backend/dist/services/pluginService.js.map +1 -1
  28. package/backend/dist/services/preferencesService.d.ts.map +1 -1
  29. package/backend/dist/services/preferencesService.js +1 -0
  30. package/backend/dist/services/preferencesService.js.map +1 -1
  31. package/backend/dist/services/workAgentGuidance.d.ts +24 -0
  32. package/backend/dist/services/workAgentGuidance.d.ts.map +1 -0
  33. package/backend/dist/services/workAgentGuidance.js +131 -0
  34. package/backend/dist/services/workAgentGuidance.js.map +1 -0
  35. package/backend/dist/services/workAgentService.d.ts +33 -0
  36. package/backend/dist/services/workAgentService.d.ts.map +1 -0
  37. package/backend/dist/services/workAgentService.js +935 -0
  38. package/backend/dist/services/workAgentService.js.map +1 -0
  39. package/backend/dist/services/workEventService.d.ts +40 -0
  40. package/backend/dist/services/workEventService.d.ts.map +1 -0
  41. package/backend/dist/services/workEventService.js +324 -0
  42. package/backend/dist/services/workEventService.js.map +1 -0
  43. package/backend/dist/services/workModelProviderService.d.ts +51 -0
  44. package/backend/dist/services/workModelProviderService.d.ts.map +1 -0
  45. package/backend/dist/services/workModelProviderService.js +952 -0
  46. package/backend/dist/services/workModelProviderService.js.map +1 -0
  47. package/backend/dist/services/workRuntimeService.d.ts +143 -0
  48. package/backend/dist/services/workRuntimeService.d.ts.map +1 -0
  49. package/backend/dist/services/workRuntimeService.js +1860 -0
  50. package/backend/dist/services/workRuntimeService.js.map +1 -0
  51. package/backend/dist/services/workTaskService.d.ts +96 -0
  52. package/backend/dist/services/workTaskService.d.ts.map +1 -0
  53. package/backend/dist/services/workTaskService.js +718 -0
  54. package/backend/dist/services/workTaskService.js.map +1 -0
  55. package/backend/dist/types/index.d.ts +3 -0
  56. package/backend/dist/types/index.d.ts.map +1 -1
  57. package/backend/dist/types/index.js.map +1 -1
  58. package/backend/dist/types/work.d.ts +209 -0
  59. package/backend/dist/types/work.d.ts.map +1 -0
  60. package/backend/dist/types/work.js +18 -0
  61. package/backend/dist/types/work.js.map +1 -0
  62. package/backend/dist/utils/ollamaLibrary.d.ts.map +1 -1
  63. package/backend/dist/utils/ollamaLibrary.js +81 -8
  64. package/backend/dist/utils/ollamaLibrary.js.map +1 -1
  65. package/backend/dist/utils/pluginChatAdapter.d.ts +9 -0
  66. package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
  67. package/backend/dist/utils/pluginChatAdapter.js +32 -6
  68. package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
  69. package/backend/dist/utils/pluginStreamAdapter.d.ts +12 -0
  70. package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -1
  71. package/backend/dist/utils/pluginStreamAdapter.js +168 -19
  72. package/backend/dist/utils/pluginStreamAdapter.js.map +1 -1
  73. package/backend/dist/utils/pluginStreaming.d.ts +10 -0
  74. package/backend/dist/utils/pluginStreaming.d.ts.map +1 -1
  75. package/backend/dist/utils/pluginStreaming.js.map +1 -1
  76. package/backend/dist/utils/pluginValidation.d.ts +1 -0
  77. package/backend/dist/utils/pluginValidation.d.ts.map +1 -1
  78. package/backend/dist/utils/pluginValidation.js +3 -0
  79. package/backend/dist/utils/pluginValidation.js.map +1 -1
  80. package/backend/package.json +1 -1
  81. package/electron/main.js +39 -16
  82. package/frontend/dist/assets/index-DkShhNBf.js +5 -0
  83. package/frontend/dist/assets/index-bdSWBsAM.css +1 -0
  84. package/frontend/dist/index.html +5 -4
  85. package/frontend/dist/js/{ArtifactContainer-CuvIIoHC.js → ArtifactContainer-BHSBrlQB.js} +1 -1
  86. package/frontend/dist/js/{ArtifactDemoPage-DI0QzAee.js → ArtifactDemoPage-BHGJmJAT.js} +1 -1
  87. package/frontend/dist/js/ArtifactSlideOutPanel-s8vcE5Ea.js +1 -0
  88. package/frontend/dist/js/ChatPage-B6Hm_pdu.js +16 -0
  89. package/frontend/dist/js/FirstTimeSetup-BYWAVAdE.js +1 -0
  90. package/frontend/dist/js/GalleryPage-CH4f65q8.js +1 -0
  91. package/frontend/dist/js/LibreClawPage-CcH7M3mJ.js +1 -0
  92. package/frontend/dist/js/{ModelManagerModals-DfgizeSq.js → ModelManagerModals-DltRprHU.js} +1 -1
  93. package/frontend/dist/js/ModelSelector-T1KZtIde.js +1 -0
  94. package/frontend/dist/js/ModelsPage-C1bM_Yoc.js +2 -0
  95. package/frontend/dist/js/PersonasPage-T_PrhnPG.js +13 -0
  96. package/frontend/dist/js/RichMessageContent-BdU5vXz9.js +3 -0
  97. package/frontend/dist/js/SettingsModal-BVj-D1L4.js +1 -0
  98. package/frontend/dist/js/UserManagementPage-Pix71oRW.js +1 -0
  99. package/frontend/dist/js/WorkPage-DdOVGyj_.js +2 -0
  100. package/frontend/dist/js/api-Do6u0tMM.js +19 -0
  101. package/frontend/dist/js/{ar-UnnZB1Qy.js → ar-KceI0PfT.js} +2 -2
  102. package/frontend/dist/js/babel-BnLuavNv.js +16 -0
  103. package/frontend/dist/js/{bn-uZXaDyeV.js → bn-DsKo2dzS.js} +2 -2
  104. package/frontend/dist/js/{cs-CCXlnUEB.js → cs-6UKhKj0A.js} +2 -2
  105. package/frontend/dist/js/{da--BpVVGPp.js → da-CJgZ3YuL.js} +2 -2
  106. package/frontend/dist/js/{de-DZd9lsBc.js → de-BZAmOuCT.js} +2 -2
  107. package/frontend/dist/js/{es-Bm968oUX.js → es-CD4u7no9.js} +2 -2
  108. package/frontend/dist/js/estree-Bu_-Wi6O.js +44 -0
  109. package/frontend/dist/js/fr-Cou2jOS3.js +2 -0
  110. package/frontend/dist/js/{hi-C6kwkL7d.js → hi-ZxkbNCyB.js} +2 -2
  111. package/frontend/dist/js/html-Ca764Rus.js +25 -0
  112. package/frontend/dist/js/{id-CDfqnnXS.js → id-DT9CqaWQ.js} +2 -2
  113. package/frontend/dist/js/{is-Dnd_7Jqv.js → is-BWSbAIoq.js} +2 -2
  114. package/frontend/dist/js/{it-BM0j6_Sz.js → it-CdNoTXm3.js} +2 -2
  115. package/frontend/dist/js/{ja-xGblKDDf.js → ja-CIuQg0xv.js} +2 -2
  116. package/frontend/dist/js/{ko-CQ2d_Oj0.js → ko-BWRUjDOH.js} +2 -2
  117. package/frontend/dist/js/markdown-DaiQGPEs.js +70 -0
  118. package/frontend/dist/js/{ms-CFpRuv_a.js → ms-gJstIj2a.js} +2 -2
  119. package/frontend/dist/js/{nl-D6oTztmP.js → nl-CydjVNUw.js} +2 -2
  120. package/frontend/dist/js/{pl-ByuvABul.js → pl-DPN9mAa5.js} +2 -2
  121. package/frontend/dist/js/postcss-DSDkwgFE.js +62 -0
  122. package/frontend/dist/js/{pt-CNFV21T7.js → pt-MRXGqIjQ.js} +2 -2
  123. package/frontend/dist/js/router-vendor-BjDI9mRe.js +1 -0
  124. package/frontend/dist/js/{ru-CRsSOntz.js → ru-CvQ9GkJF.js} +2 -2
  125. package/frontend/dist/js/standalone-DWWnsbIr.js +29 -0
  126. package/frontend/dist/js/{sv-BRkwjyS5.js → sv-BxS2fvhU.js} +2 -2
  127. package/frontend/dist/js/{th-DigqsQEB.js → th-DMxyn6e4.js} +2 -2
  128. package/frontend/dist/js/{tr-B5aMUMJE.js → tr-BTPldUaA.js} +2 -2
  129. package/frontend/dist/js/typescript-CatjJeHm.js +22 -0
  130. package/frontend/dist/js/ui-vendor-CAFEDSaO.js +177 -0
  131. package/frontend/dist/js/{uk-BUYi3llT.js → uk-BGxZOKgp.js} +2 -2
  132. package/frontend/dist/js/useBaseQuery-CVNuhuID.js +1 -0
  133. package/frontend/dist/js/useQuery-CYhC_da4.js +1 -0
  134. package/frontend/dist/js/{vi-hQIcXi3m.js → vi-82JkwFsQ.js} +2 -2
  135. package/frontend/dist/js/yaml-mLy9chOK.js +154 -0
  136. package/frontend/dist/js/{zh-CUDZ3JDv.js → zh-Bxxfwci3.js} +2 -2
  137. package/package.json +4 -2
  138. package/plugins/kimi-code.json +6 -37
  139. package/plugins/mlx-lm.json +55 -0
  140. package/frontend/dist/assets/index-BKzwVaAM.js +0 -16
  141. package/frontend/dist/assets/index-BPvF-fpc.css +0 -1
  142. package/frontend/dist/js/ArtifactSlideOutPanel-BzBemzHy.js +0 -1
  143. package/frontend/dist/js/ChatPage-DqpXctC2.js +0 -16
  144. package/frontend/dist/js/FirstTimeSetup-DRWeCsVI.js +0 -1
  145. package/frontend/dist/js/GalleryPage-CgD8fVx0.js +0 -1
  146. package/frontend/dist/js/LibreClawPage-B_Yon2fQ.js +0 -1
  147. package/frontend/dist/js/ModelsPage-CAKQ7J-b.js +0 -2
  148. package/frontend/dist/js/PersonasPage-Dj6Bka4I.js +0 -13
  149. package/frontend/dist/js/RichMessageContent-DP7XoxIa.js +0 -3
  150. package/frontend/dist/js/SettingsModal-BFyYhZYE.js +0 -1
  151. package/frontend/dist/js/UserManagementPage-CGMnhZr1.js +0 -1
  152. package/frontend/dist/js/fr-RlC0rD8I.js +0 -2
  153. package/frontend/dist/js/router-vendor-Bbvzv1ZC.js +0 -1
  154. package/frontend/dist/js/ui-vendor-CurEAeCe.js +0 -177
  155. package/frontend/dist/js/useBaseQuery-B1on3xcz.js +0 -1
  156. package/frontend/dist/js/useQuery-CXvo2RBT.js +0 -1
@@ -0,0 +1,952 @@
1
+ /*
2
+ * Libre WebUI
3
+ * Copyright (C) 2025 Kroonen AI, Inc.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import axios from 'axios';
18
+ import { getOpenAICompatibleSamplingParameters, resolvePluginChatParameters, } from '../utils/pluginChatAdapter.js';
19
+ import { streamAnthropicResponse, streamOpenAICompatibleResponse, } from '../utils/pluginStreamAdapter.js';
20
+ import { applyModelEndpointTemplate, assertSafePluginEndpoint, buildPluginAuthHeaders, pluginRequiresApiKey, resolvePluginEndpoint, validatePluginModel, } from '../utils/pluginValidation.js';
21
+ import ollamaService from './ollamaService.js';
22
+ import pluginService from './pluginService.js';
23
+ export const WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY = 'libreToolArgumentsError';
24
+ 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.';
25
+ export class WorkModelProviderError extends Error {
26
+ constructor(message, status, code) {
27
+ super(message);
28
+ this.name = 'WorkModelProviderError';
29
+ this.status = status;
30
+ this.code = code;
31
+ }
32
+ }
33
+ export class WorkModelProviderService {
34
+ constructor(dependencies = {
35
+ ollama: ollamaService,
36
+ plugins: pluginService,
37
+ post: axios.post.bind(axios),
38
+ }) {
39
+ this.dependencies = dependencies;
40
+ }
41
+ async availability(userId) {
42
+ const [ollamaAvailable, pluginAvailable] = await Promise.all([
43
+ this.dependencies.ollama.isHealthy(),
44
+ Promise.resolve(this.hasConfiguredPlugin(userId)),
45
+ ]);
46
+ return { ollamaAvailable, pluginAvailable };
47
+ }
48
+ async assertModelSupportsTools(model, provider, userId) {
49
+ const cleaned = model.trim();
50
+ if (!cleaned) {
51
+ throw new WorkModelProviderError('A Work model is required.', 422, 'WORK_MODEL_TOOLS_UNSUPPORTED');
52
+ }
53
+ if (provider.providerType === 'plugin') {
54
+ this.requireExactPlugin(provider.providerId, cleaned, userId);
55
+ return;
56
+ }
57
+ assertOllamaProvider(provider);
58
+ let details;
59
+ try {
60
+ details = await this.dependencies.ollama.showModel(cleaned, false);
61
+ }
62
+ catch (error) {
63
+ throw new WorkModelProviderError(error instanceof Error ? error.message : 'Could not inspect model.', 503, 'WORK_MODEL_UNAVAILABLE');
64
+ }
65
+ const capabilities = Array.isArray(details.capabilities)
66
+ ? details.capabilities.map(value => String(value).toLowerCase())
67
+ : [];
68
+ if (!capabilities.includes('tools')) {
69
+ throw new WorkModelProviderError(`Model "${cleaned}" does not advertise tool support.`, 422, 'WORK_MODEL_TOOLS_UNSUPPORTED');
70
+ }
71
+ }
72
+ async generateChatResponse(request, provider, userId, signal) {
73
+ if (provider.providerType === 'ollama') {
74
+ assertOllamaProvider(provider);
75
+ return this.dependencies.ollama.generateChatResponse(request, signal);
76
+ }
77
+ const plugin = this.requireExactPlugin(provider.providerId, request.model, userId);
78
+ return this.generatePluginResponse(plugin, request, userId, signal);
79
+ }
80
+ async generateChatStreamResponse(request, provider, userId, observer, signal) {
81
+ const streamRequest = { ...request, stream: true };
82
+ if (provider.providerType === 'ollama') {
83
+ assertOllamaProvider(provider);
84
+ return this.generateOllamaStream(streamRequest, observer, signal);
85
+ }
86
+ const plugin = this.requireExactPlugin(provider.providerId, request.model, userId);
87
+ return this.generatePluginStream(plugin, streamRequest, userId, observer, signal);
88
+ }
89
+ hasConfiguredPlugin(userId) {
90
+ return this.dependencies.plugins
91
+ .getActivePlugins()
92
+ .filter(isWorkPlugin)
93
+ .some(plugin => plugin.model_map.length > 0 &&
94
+ (!pluginRequiresApiKey(plugin) ||
95
+ Boolean(this.dependencies.plugins.getApiKey(plugin, userId))));
96
+ }
97
+ requireExactPlugin(providerId, model, userId) {
98
+ const cleanedProviderId = providerId?.trim();
99
+ if (!cleanedProviderId) {
100
+ throw new WorkModelProviderError('A plugin provider ID is required.', 400, 'WORK_PLUGIN_ID_REQUIRED');
101
+ }
102
+ const plugin = this.dependencies.plugins.getPlugin(cleanedProviderId);
103
+ if (!plugin || !plugin.active) {
104
+ throw new WorkModelProviderError(`Plugin "${cleanedProviderId}" is not active.`, 422, 'WORK_PLUGIN_UNAVAILABLE');
105
+ }
106
+ assertWorkPluginType(plugin);
107
+ if (!plugin.model_map.includes(model)) {
108
+ throw new WorkModelProviderError(`Model "${model}" is not configured for plugin "${plugin.id}".`, 422, 'WORK_PLUGIN_MODEL_UNAVAILABLE');
109
+ }
110
+ if (pluginRequiresApiKey(plugin) &&
111
+ !this.dependencies.plugins.getApiKey(plugin, userId)) {
112
+ throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
113
+ }
114
+ return plugin;
115
+ }
116
+ async generatePluginResponse(plugin, request, userId, signal) {
117
+ validatePluginModel(request.model);
118
+ const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
119
+ if (pluginRequiresApiKey(plugin) && !apiKey) {
120
+ throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
121
+ }
122
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
123
+ const endpoint = applyModelEndpointTemplate(resolvePluginEndpoint(plugin.endpoint, variables.endpoint), request.model);
124
+ assertSafePluginEndpoint(endpoint, 'Work model endpoint');
125
+ const headers = buildPluginAuthHeaders(plugin, apiKey);
126
+ const { payload, extraHeaders } = buildPluginWorkPayload(plugin, request, variables);
127
+ Object.assign(headers, extraHeaders);
128
+ try {
129
+ const response = await this.dependencies.post(endpoint, payload, {
130
+ headers,
131
+ signal,
132
+ timeout: 300000,
133
+ });
134
+ return normalizePluginWorkResponse(plugin, response.data, request.model);
135
+ }
136
+ catch (error) {
137
+ if (signal?.aborted)
138
+ throw error;
139
+ const message = axios.isAxiosError(error) && error.response
140
+ ? `Plugin API error: ${error.response.status} - ${providerErrorMessage(error.response.data)}`
141
+ : error instanceof Error
142
+ ? error.message
143
+ : 'Plugin request failed.';
144
+ throw new WorkModelProviderError(message, 502, 'WORK_PLUGIN_REQUEST_FAILED');
145
+ }
146
+ }
147
+ async generateOllamaStream(request, observer, signal) {
148
+ const stream = this.dependencies.ollama.generateChatStreamResponse;
149
+ if (!stream) {
150
+ const response = await this.dependencies.ollama.generateChatResponse({ ...request, stream: false }, signal);
151
+ if (response.message?.thinking) {
152
+ observer.onReasoning?.(response.message.thinking);
153
+ }
154
+ if (response.message?.content) {
155
+ observer.onContent?.(response.message.content);
156
+ }
157
+ observer.onUsage?.(ollamaUsage(response));
158
+ return response;
159
+ }
160
+ return new Promise((resolve, reject) => {
161
+ let settled = false;
162
+ let content = '';
163
+ let reasoning = '';
164
+ let latest;
165
+ const toolCalls = [];
166
+ const finish = (error) => {
167
+ if (settled)
168
+ return;
169
+ settled = true;
170
+ if (error) {
171
+ reject(error);
172
+ return;
173
+ }
174
+ const response = {
175
+ ...(latest || {
176
+ model: request.model,
177
+ created_at: new Date().toISOString(),
178
+ done: true,
179
+ }),
180
+ message: {
181
+ role: 'assistant',
182
+ content,
183
+ ...(reasoning ? { thinking: reasoning } : {}),
184
+ ...(toolCalls.length ? { tool_calls: toolCalls } : {}),
185
+ },
186
+ done: true,
187
+ };
188
+ observer.onUsage?.(ollamaUsage(response));
189
+ resolve(response);
190
+ };
191
+ void stream
192
+ .call(this.dependencies.ollama, request, chunk => {
193
+ latest = chunk;
194
+ const contentDelta = chunk.message?.content || '';
195
+ const reasoningDelta = chunk.message?.thinking || '';
196
+ if (contentDelta) {
197
+ content += contentDelta;
198
+ observer.onContent?.(contentDelta);
199
+ }
200
+ if (reasoningDelta) {
201
+ reasoning += reasoningDelta;
202
+ observer.onReasoning?.(reasoningDelta);
203
+ }
204
+ if (Array.isArray(chunk.message?.tool_calls)) {
205
+ for (const call of chunk.message.tool_calls) {
206
+ toolCalls.push(call);
207
+ }
208
+ }
209
+ if (chunk.done)
210
+ finish();
211
+ }, error => finish(error), () => finish(), signal)
212
+ .catch(error => finish(error instanceof Error ? error : new Error(String(error))));
213
+ });
214
+ }
215
+ async generatePluginStream(plugin, request, userId, observer, signal) {
216
+ validatePluginModel(request.model);
217
+ const apiKey = this.dependencies.plugins.getApiKey(plugin, userId);
218
+ if (pluginRequiresApiKey(plugin) && !apiKey) {
219
+ throw new WorkModelProviderError(`API key not found for plugin ${plugin.id}.`, 422, 'WORK_PLUGIN_CREDENTIALS_MISSING');
220
+ }
221
+ const variables = this.dependencies.plugins.getPluginVariables(plugin, userId);
222
+ let endpoint = applyModelEndpointTemplate(resolvePluginEndpoint(plugin.endpoint, variables.endpoint), request.model);
223
+ if (plugin.id === 'gemini') {
224
+ endpoint = geminiStreamingEndpoint(endpoint);
225
+ }
226
+ assertSafePluginEndpoint(endpoint, 'Work model endpoint');
227
+ const headers = buildPluginAuthHeaders(plugin, apiKey);
228
+ const { payload, extraHeaders } = buildPluginWorkPayload(plugin, { ...request, stream: true }, variables);
229
+ Object.assign(headers, extraHeaders);
230
+ const timeoutSignal = AbortSignal.timeout(300000);
231
+ const requestSignal = signal
232
+ ? AbortSignal.any([signal, timeoutSignal])
233
+ : timeoutSignal;
234
+ try {
235
+ const response = await fetch(endpoint, {
236
+ method: 'POST',
237
+ headers,
238
+ body: JSON.stringify(payload),
239
+ signal: requestSignal,
240
+ });
241
+ const contentType = response.headers.get('content-type') || '';
242
+ if (response.ok &&
243
+ !contentType.includes('text/event-stream') &&
244
+ !contentType.includes('application/x-ndjson')) {
245
+ const data = (await response.json());
246
+ const normalized = normalizePluginWorkResponse(plugin, data, request.model);
247
+ if (normalized.message.thinking) {
248
+ observer.onReasoning?.(normalized.message.thinking);
249
+ }
250
+ if (normalized.message.content) {
251
+ observer.onContent?.(normalized.message.content);
252
+ }
253
+ return normalized;
254
+ }
255
+ const chunks = plugin.id === 'anthropic'
256
+ ? streamAnthropicResponse(response)
257
+ : plugin.id === 'gemini'
258
+ ? streamGeminiWorkResponse(response)
259
+ : streamOpenAICompatibleResponse(response);
260
+ return await collectPluginWorkStream(chunks, request.model, observer);
261
+ }
262
+ catch (error) {
263
+ if (signal?.aborted)
264
+ throw error;
265
+ if (timeoutSignal.aborted) {
266
+ throw new WorkModelProviderError('Plugin request timed out after 300 seconds.', 504, 'WORK_PLUGIN_REQUEST_TIMEOUT');
267
+ }
268
+ if (error instanceof WorkModelProviderError)
269
+ throw error;
270
+ throw new WorkModelProviderError(error instanceof Error ? error.message : 'Plugin request failed.', 502, 'WORK_PLUGIN_REQUEST_FAILED');
271
+ }
272
+ }
273
+ }
274
+ export function buildPluginWorkPayload(plugin, request, variables = {}) {
275
+ const options = (request.options || {});
276
+ const params = resolvePluginChatParameters(options, variables);
277
+ if (plugin.id === 'anthropic') {
278
+ return {
279
+ payload: buildAnthropicWorkPayload(request.model, request.messages, request.tools || [], params.maxTokens, Boolean(request.stream)),
280
+ extraHeaders: { 'anthropic-version': '2023-06-01' },
281
+ };
282
+ }
283
+ if (plugin.id === 'gemini') {
284
+ return {
285
+ payload: buildGeminiWorkPayload(request.messages, request.tools || [], params),
286
+ extraHeaders: {},
287
+ };
288
+ }
289
+ return {
290
+ payload: {
291
+ model: request.model,
292
+ messages: toOpenAIWorkMessages(request.messages),
293
+ tools: request.tools,
294
+ tool_choice: request.tools?.length ? 'auto' : undefined,
295
+ ...getOpenAICompatibleSamplingParameters(plugin, params),
296
+ max_tokens: params.maxTokens,
297
+ stream: Boolean(request.stream),
298
+ },
299
+ extraHeaders: {},
300
+ };
301
+ }
302
+ export function normalizePluginWorkResponse(plugin, response, model) {
303
+ if (plugin.id === 'anthropic') {
304
+ return normalizeAnthropicWorkResponse(response, model);
305
+ }
306
+ if (plugin.id === 'gemini') {
307
+ return normalizeGeminiWorkResponse(response, model);
308
+ }
309
+ return normalizeOpenAIWorkResponse(response, model);
310
+ }
311
+ export function toOpenAIWorkMessages(messages) {
312
+ let pendingCalls = [];
313
+ return messages.map((message, messageIndex) => {
314
+ if (message.role === 'assistant') {
315
+ const toolCalls = normalizeOutboundToolCalls(message.tool_calls);
316
+ const reasoningContent = toolCalls
317
+ .map(call => asObject(call.providerMetadata)?.openAIReasoningContent)
318
+ .find(value => typeof value === 'string');
319
+ pendingCalls = toolCalls.map(call => ({
320
+ id: String(call.id),
321
+ name: String(call.function.name),
322
+ }));
323
+ return {
324
+ role: 'assistant',
325
+ content: message.content || null,
326
+ ...(typeof reasoningContent === 'string'
327
+ ? { reasoning_content: reasoningContent }
328
+ : {}),
329
+ ...(toolCalls.length
330
+ ? {
331
+ tool_calls: toolCalls.map(call => ({
332
+ id: call.id,
333
+ type: 'function',
334
+ function: call.function,
335
+ })),
336
+ }
337
+ : {}),
338
+ };
339
+ }
340
+ if (message.role === 'tool') {
341
+ const matchingIndex = pendingCalls.findIndex(call => !message.tool_name || call.name === message.tool_name);
342
+ const matching = matchingIndex >= 0
343
+ ? pendingCalls.splice(matchingIndex, 1)[0]
344
+ : undefined;
345
+ return {
346
+ role: 'tool',
347
+ content: message.content,
348
+ name: message.tool_name,
349
+ tool_call_id: matching?.id || `work-tool-${messageIndex}`,
350
+ };
351
+ }
352
+ return { role: message.role, content: message.content };
353
+ });
354
+ }
355
+ function buildAnthropicWorkPayload(model, messages, tools, maxTokens, stream = false) {
356
+ const system = messages
357
+ .filter(message => message.role === 'system')
358
+ .map(message => message.content)
359
+ .join('\n');
360
+ const providerMessages = [];
361
+ let pendingCalls = [];
362
+ let appendToolResult = false;
363
+ for (const [messageIndex, message] of messages.entries()) {
364
+ if (message.role === 'system')
365
+ continue;
366
+ if (message.role === 'assistant') {
367
+ const blocks = [];
368
+ const toolCalls = normalizeOutboundToolCalls(message.tool_calls);
369
+ const anthropicThinkingBlocks = toolCalls.flatMap(call => {
370
+ const metadata = asObject(call.providerMetadata);
371
+ return Array.isArray(metadata?.anthropicThinkingBlocks)
372
+ ? metadata.anthropicThinkingBlocks.flatMap(block => asObject(block) ? [block] : [])
373
+ : [];
374
+ });
375
+ blocks.push(...anthropicThinkingBlocks);
376
+ if (message.content)
377
+ blocks.push({ type: 'text', text: message.content });
378
+ pendingCalls = toolCalls.map(call => ({
379
+ id: String(call.id),
380
+ name: String(call.function.name),
381
+ }));
382
+ for (const call of toolCalls) {
383
+ const fn = call.function;
384
+ blocks.push({
385
+ type: 'tool_use',
386
+ id: call.id,
387
+ name: fn.name,
388
+ input: parseToolArguments(fn.arguments),
389
+ });
390
+ }
391
+ providerMessages.push({ role: 'assistant', content: blocks });
392
+ appendToolResult = false;
393
+ continue;
394
+ }
395
+ if (message.role === 'tool') {
396
+ const matchIndex = pendingCalls.findIndex(call => !message.tool_name || call.name === message.tool_name);
397
+ const matching = matchIndex >= 0 ? pendingCalls.splice(matchIndex, 1)[0] : undefined;
398
+ const block = {
399
+ type: 'tool_result',
400
+ tool_use_id: matching?.id || `work-tool-${messageIndex}`,
401
+ content: message.content,
402
+ };
403
+ const previous = providerMessages[providerMessages.length - 1];
404
+ if (appendToolResult && previous?.role === 'user') {
405
+ previous.content.push(block);
406
+ }
407
+ else {
408
+ providerMessages.push({ role: 'user', content: [block] });
409
+ }
410
+ appendToolResult = true;
411
+ continue;
412
+ }
413
+ providerMessages.push({
414
+ role: 'user',
415
+ content: [{ type: 'text', text: message.content }],
416
+ });
417
+ appendToolResult = false;
418
+ }
419
+ return {
420
+ model,
421
+ system: system || undefined,
422
+ messages: providerMessages,
423
+ tools: toAnthropicTools(tools),
424
+ max_tokens: maxTokens ?? 4096,
425
+ stream,
426
+ };
427
+ }
428
+ function buildGeminiWorkPayload(messages, tools, params) {
429
+ const system = messages
430
+ .filter(message => message.role === 'system')
431
+ .map(message => message.content)
432
+ .join('\n');
433
+ const contents = [];
434
+ let pendingCalls = [];
435
+ const append = (role, part) => {
436
+ const previous = contents[contents.length - 1];
437
+ if (previous?.role === role)
438
+ previous.parts.push(part);
439
+ else
440
+ contents.push({ role, parts: [part] });
441
+ };
442
+ for (const [messageIndex, message] of messages.entries()) {
443
+ if (message.role === 'system')
444
+ continue;
445
+ if (message.role === 'assistant') {
446
+ if (message.content)
447
+ append('model', { text: message.content });
448
+ const calls = normalizeOutboundToolCalls(message.tool_calls);
449
+ pendingCalls = calls.map(call => ({
450
+ id: String(call.id),
451
+ name: String(call.function.name),
452
+ }));
453
+ for (const call of calls) {
454
+ const fn = call.function;
455
+ append('model', {
456
+ functionCall: {
457
+ id: call.id,
458
+ name: fn.name,
459
+ args: parseToolArguments(fn.arguments),
460
+ },
461
+ ...(typeof call.thoughtSignature === 'string'
462
+ ? { thoughtSignature: call.thoughtSignature }
463
+ : {}),
464
+ });
465
+ }
466
+ continue;
467
+ }
468
+ if (message.role === 'tool') {
469
+ const matchIndex = pendingCalls.findIndex(call => !message.tool_name || call.name === message.tool_name);
470
+ const matching = matchIndex >= 0 ? pendingCalls.splice(matchIndex, 1)[0] : undefined;
471
+ append('user', {
472
+ functionResponse: {
473
+ id: matching?.id || `work-tool-${messageIndex}`,
474
+ name: message.tool_name || matching?.name || 'work_tool',
475
+ response: { result: message.content },
476
+ },
477
+ });
478
+ continue;
479
+ }
480
+ append('user', { text: message.content });
481
+ }
482
+ return {
483
+ systemInstruction: system ? { parts: [{ text: system }] } : undefined,
484
+ contents,
485
+ ...(tools.length
486
+ ? {
487
+ tools: [
488
+ {
489
+ functionDeclarations: tools.flatMap(tool => {
490
+ const fn = asObject(tool.function);
491
+ return fn
492
+ ? [
493
+ {
494
+ name: fn.name,
495
+ description: fn.description,
496
+ parameters: fn.parameters,
497
+ },
498
+ ]
499
+ : [];
500
+ }),
501
+ },
502
+ ],
503
+ }
504
+ : {}),
505
+ generationConfig: {
506
+ temperature: params.temperature,
507
+ maxOutputTokens: params.maxTokens ?? 4096,
508
+ topP: params.topP,
509
+ },
510
+ };
511
+ }
512
+ function normalizeOpenAIWorkResponse(response, model) {
513
+ const choices = Array.isArray(response.choices) ? response.choices : [];
514
+ const choice = asObject(choices[0]);
515
+ const message = asObject(choice?.message);
516
+ if (!message) {
517
+ throw new WorkModelProviderError('Plugin returned an invalid chat response.', 502, 'WORK_PLUGIN_INVALID_RESPONSE');
518
+ }
519
+ const toolCalls = normalizeInboundToolCalls(message.tool_calls);
520
+ if (typeof message.reasoning_content === 'string' && toolCalls[0]) {
521
+ toolCalls[0].providerMetadata = {
522
+ ...asObject(toolCalls[0].providerMetadata),
523
+ openAIReasoningContent: message.reasoning_content,
524
+ };
525
+ }
526
+ return workResponse(model, contentText(message.content), toolCalls, typeof message.reasoning_content === 'string'
527
+ ? message.reasoning_content
528
+ : '');
529
+ }
530
+ function normalizeAnthropicWorkResponse(response, model) {
531
+ const blocks = Array.isArray(response.content) ? response.content : [];
532
+ const text = [];
533
+ const calls = [];
534
+ const thinkingBlocks = [];
535
+ const reasoning = [];
536
+ for (const [index, value] of blocks.entries()) {
537
+ const block = asObject(value);
538
+ if (block?.type === 'thinking' || block?.type === 'redacted_thinking') {
539
+ thinkingBlocks.push(block);
540
+ if (typeof block.thinking === 'string')
541
+ reasoning.push(block.thinking);
542
+ }
543
+ if (block?.type === 'text' && typeof block.text === 'string') {
544
+ text.push(block.text);
545
+ }
546
+ if (block?.type === 'tool_use' && typeof block.name === 'string') {
547
+ calls.push({
548
+ id: typeof block.id === 'string' ? block.id : `work-anthropic-${index}`,
549
+ function: {
550
+ name: block.name,
551
+ arguments: asObject(block.input) || {},
552
+ },
553
+ });
554
+ }
555
+ }
556
+ if (thinkingBlocks.length > 0 && calls[0]) {
557
+ calls[0].providerMetadata = {
558
+ anthropicThinkingBlocks: thinkingBlocks,
559
+ };
560
+ }
561
+ return workResponse(model, text.join(''), calls, reasoning.join(''));
562
+ }
563
+ function normalizeGeminiWorkResponse(response, model) {
564
+ const candidates = Array.isArray(response.candidates)
565
+ ? response.candidates
566
+ : [];
567
+ const candidate = asObject(candidates[0]);
568
+ const content = asObject(candidate?.content);
569
+ const parts = Array.isArray(content?.parts) ? content.parts : [];
570
+ const text = [];
571
+ const reasoning = [];
572
+ const calls = [];
573
+ for (const [index, value] of parts.entries()) {
574
+ const part = asObject(value);
575
+ if (typeof part?.text === 'string') {
576
+ if (part.thought === true)
577
+ reasoning.push(part.text);
578
+ else
579
+ text.push(part.text);
580
+ }
581
+ const call = asObject(part?.functionCall);
582
+ if (call && typeof call.name === 'string') {
583
+ calls.push({
584
+ id: typeof call.id === 'string' ? call.id : `work-gemini-${index}`,
585
+ ...(typeof part?.thoughtSignature === 'string'
586
+ ? { thoughtSignature: part.thoughtSignature }
587
+ : {}),
588
+ function: {
589
+ name: call.name,
590
+ arguments: asObject(call.args) || {},
591
+ },
592
+ });
593
+ }
594
+ }
595
+ return workResponse(model, text.join(''), calls, reasoning.join(''));
596
+ }
597
+ function workResponse(model, content, toolCalls, reasoning = '') {
598
+ return {
599
+ model,
600
+ created_at: new Date().toISOString(),
601
+ message: {
602
+ role: 'assistant',
603
+ content,
604
+ ...(reasoning ? { thinking: reasoning } : {}),
605
+ tool_calls: toolCalls,
606
+ },
607
+ done: true,
608
+ };
609
+ }
610
+ async function collectPluginWorkStream(chunks, model, observer) {
611
+ let content = '';
612
+ let reasoning = '';
613
+ let usage = {};
614
+ const toolCalls = [];
615
+ for await (const chunk of chunks) {
616
+ if (chunk.type === 'content') {
617
+ content += chunk.content;
618
+ observer.onContent?.(chunk.content);
619
+ continue;
620
+ }
621
+ if (chunk.type === 'reasoning') {
622
+ reasoning += chunk.content;
623
+ observer.onReasoning?.(chunk.content);
624
+ continue;
625
+ }
626
+ if (chunk.type === 'usage') {
627
+ usage = { ...usage, ...chunk.usage };
628
+ observer.onUsage?.(usage);
629
+ continue;
630
+ }
631
+ if (chunk.type === 'tool_call') {
632
+ const parsedArguments = parseToolArgumentsWithStatus(chunk.toolCall.arguments);
633
+ const metadata = {
634
+ ...chunk.toolCall.providerMetadata,
635
+ ...(parsedArguments.error
636
+ ? {
637
+ [WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY]: parsedArguments.error,
638
+ }
639
+ : {}),
640
+ };
641
+ toolCalls.push({
642
+ id: chunk.toolCall.id || `work-plugin-${toolCalls.length}`,
643
+ ...(typeof metadata?.geminiThoughtSignature === 'string'
644
+ ? { thoughtSignature: metadata.geminiThoughtSignature }
645
+ : {}),
646
+ ...(metadata &&
647
+ Object.keys(metadata).some(key => key !== 'geminiThoughtSignature')
648
+ ? {
649
+ providerMetadata: Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== 'geminiThoughtSignature')),
650
+ }
651
+ : {}),
652
+ function: {
653
+ name: chunk.toolCall.name,
654
+ arguments: parsedArguments.arguments,
655
+ },
656
+ });
657
+ }
658
+ }
659
+ if (reasoning && toolCalls[0]) {
660
+ const firstMetadata = asObject(toolCalls[0].providerMetadata) || {};
661
+ if (!firstMetadata.openAIReasoningContent &&
662
+ !firstMetadata.anthropicThinkingBlocks) {
663
+ toolCalls[0].providerMetadata = {
664
+ ...firstMetadata,
665
+ openAIReasoningContent: reasoning,
666
+ };
667
+ }
668
+ }
669
+ return {
670
+ ...workResponse(model, content, toolCalls, reasoning),
671
+ prompt_eval_count: usage.promptTokens,
672
+ eval_count: usage.completionTokens,
673
+ };
674
+ }
675
+ function ollamaUsage(response) {
676
+ return {
677
+ promptTokens: response.prompt_eval_count,
678
+ completionTokens: response.eval_count,
679
+ totalTokens: response.prompt_eval_count !== undefined ||
680
+ response.eval_count !== undefined
681
+ ? (response.prompt_eval_count || 0) + (response.eval_count || 0)
682
+ : undefined,
683
+ };
684
+ }
685
+ function geminiStreamingEndpoint(endpoint) {
686
+ const url = new URL(endpoint);
687
+ url.pathname = url.pathname.replace(/:generateContent$/, ':streamGenerateContent');
688
+ url.searchParams.set('alt', 'sse');
689
+ return url.toString();
690
+ }
691
+ async function* streamGeminiWorkResponse(response) {
692
+ if (!response.ok) {
693
+ const errorText = await response.text();
694
+ throw new Error(`Plugin API error: ${response.status} - ${errorText.slice(0, 200)}`);
695
+ }
696
+ if (!response.body) {
697
+ throw new Error('No response body for streaming');
698
+ }
699
+ const reader = response.body.getReader();
700
+ const decoder = new TextDecoder();
701
+ let buffer = '';
702
+ let callIndex = 0;
703
+ try {
704
+ while (true) {
705
+ const { done, value } = await reader.read();
706
+ if (done)
707
+ break;
708
+ buffer += decoder.decode(value, { stream: true });
709
+ const lines = buffer.split('\n');
710
+ buffer = lines.pop() || '';
711
+ for (const line of lines) {
712
+ const trimmed = line.trim();
713
+ if (!trimmed.startsWith('data:'))
714
+ continue;
715
+ let payload;
716
+ try {
717
+ payload = JSON.parse(trimmed.slice(5).trim());
718
+ }
719
+ catch {
720
+ continue;
721
+ }
722
+ const streamError = providerStreamErrorMessage(payload);
723
+ if (streamError) {
724
+ throw new Error(`Plugin API error: ${streamError}`);
725
+ }
726
+ const candidates = Array.isArray(payload.candidates)
727
+ ? payload.candidates
728
+ : [];
729
+ const candidate = asObject(candidates[0]);
730
+ const candidateContent = asObject(candidate?.content);
731
+ const parts = Array.isArray(candidateContent?.parts)
732
+ ? candidateContent.parts
733
+ : [];
734
+ for (const rawPart of parts) {
735
+ const part = asObject(rawPart);
736
+ if (typeof part?.text === 'string' && part.text) {
737
+ yield part.thought === true
738
+ ? { type: 'reasoning', content: part.text }
739
+ : { type: 'content', content: part.text };
740
+ }
741
+ const call = asObject(part?.functionCall);
742
+ if (call && typeof call.name === 'string') {
743
+ yield {
744
+ type: 'tool_call',
745
+ toolCall: {
746
+ id: typeof call.id === 'string'
747
+ ? call.id
748
+ : `work-gemini-${callIndex++}`,
749
+ name: call.name,
750
+ arguments: JSON.stringify(asObject(call.args) || {}),
751
+ ...(typeof part?.thoughtSignature === 'string'
752
+ ? {
753
+ providerMetadata: {
754
+ geminiThoughtSignature: part.thoughtSignature,
755
+ },
756
+ }
757
+ : {}),
758
+ },
759
+ };
760
+ }
761
+ }
762
+ const usageMetadata = asObject(payload.usageMetadata);
763
+ if (usageMetadata) {
764
+ const promptTokens = typeof usageMetadata.promptTokenCount === 'number'
765
+ ? usageMetadata.promptTokenCount
766
+ : undefined;
767
+ const completionTokens = typeof usageMetadata.candidatesTokenCount === 'number'
768
+ ? usageMetadata.candidatesTokenCount
769
+ : undefined;
770
+ const totalTokens = typeof usageMetadata.totalTokenCount === 'number'
771
+ ? usageMetadata.totalTokenCount
772
+ : promptTokens !== undefined || completionTokens !== undefined
773
+ ? (promptTokens || 0) + (completionTokens || 0)
774
+ : undefined;
775
+ yield {
776
+ type: 'usage',
777
+ usage: { promptTokens, completionTokens, totalTokens },
778
+ };
779
+ }
780
+ }
781
+ }
782
+ yield { type: 'done' };
783
+ }
784
+ finally {
785
+ reader.releaseLock();
786
+ }
787
+ }
788
+ function normalizeOutboundToolCalls(value) {
789
+ if (!Array.isArray(value))
790
+ return [];
791
+ return value.flatMap((raw, index) => {
792
+ const call = asObject(raw);
793
+ const fn = asObject(call?.function);
794
+ if (!fn || typeof fn.name !== 'string')
795
+ return [];
796
+ const providerMetadata = asObject(call?.providerMetadata);
797
+ return [
798
+ {
799
+ id: typeof call?.id === 'string' ? call.id : `work-tool-call-${index}`,
800
+ ...(typeof call?.thoughtSignature === 'string'
801
+ ? { thoughtSignature: call.thoughtSignature }
802
+ : {}),
803
+ ...(providerMetadata ? { providerMetadata } : {}),
804
+ type: 'function',
805
+ function: {
806
+ name: fn.name,
807
+ arguments: typeof fn.arguments === 'string'
808
+ ? fn.arguments
809
+ : JSON.stringify(asObject(fn.arguments) || {}),
810
+ },
811
+ },
812
+ ];
813
+ });
814
+ }
815
+ function normalizeInboundToolCalls(value) {
816
+ if (!Array.isArray(value))
817
+ return [];
818
+ return value.flatMap((raw, index) => {
819
+ const call = asObject(raw);
820
+ const fn = asObject(call?.function);
821
+ if (!fn || typeof fn.name !== 'string')
822
+ return [];
823
+ const parsedArguments = parseToolArgumentsWithStatus(fn.arguments);
824
+ const providerMetadata = {
825
+ ...asObject(call?.providerMetadata),
826
+ ...(parsedArguments.error
827
+ ? {
828
+ [WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY]: parsedArguments.error,
829
+ }
830
+ : {}),
831
+ };
832
+ return [
833
+ {
834
+ id: typeof call?.id === 'string' ? call.id : `work-plugin-${index}`,
835
+ ...(typeof call?.thoughtSignature === 'string'
836
+ ? { thoughtSignature: call.thoughtSignature }
837
+ : {}),
838
+ ...(Object.keys(providerMetadata).length > 0
839
+ ? { providerMetadata }
840
+ : {}),
841
+ function: {
842
+ name: fn.name,
843
+ arguments: typeof fn.arguments === 'string' && !parsedArguments.error
844
+ ? fn.arguments
845
+ : parsedArguments.arguments,
846
+ },
847
+ },
848
+ ];
849
+ });
850
+ }
851
+ function toAnthropicTools(tools) {
852
+ return tools.flatMap(tool => {
853
+ const fn = asObject(tool.function);
854
+ if (!fn || typeof fn.name !== 'string')
855
+ return [];
856
+ return [
857
+ {
858
+ name: fn.name,
859
+ description: fn.description,
860
+ input_schema: fn.parameters || {
861
+ type: 'object',
862
+ properties: {},
863
+ },
864
+ },
865
+ ];
866
+ });
867
+ }
868
+ function parseToolArguments(value) {
869
+ return parseToolArgumentsWithStatus(value).arguments;
870
+ }
871
+ function parseToolArgumentsWithStatus(value) {
872
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
873
+ return { arguments: value };
874
+ }
875
+ if (typeof value === 'string') {
876
+ if (!value.trim())
877
+ return { arguments: {} };
878
+ try {
879
+ const parsed = asObject(JSON.parse(value));
880
+ return parsed
881
+ ? { arguments: parsed }
882
+ : {
883
+ arguments: {},
884
+ error: WORK_TOOL_ARGUMENTS_ERROR_MESSAGE,
885
+ };
886
+ }
887
+ catch {
888
+ return {
889
+ arguments: {},
890
+ error: WORK_TOOL_ARGUMENTS_ERROR_MESSAGE,
891
+ };
892
+ }
893
+ }
894
+ return { arguments: {} };
895
+ }
896
+ function contentText(value) {
897
+ if (typeof value === 'string')
898
+ return value;
899
+ if (!Array.isArray(value))
900
+ return '';
901
+ return value
902
+ .flatMap(item => {
903
+ const block = asObject(item);
904
+ return typeof block?.text === 'string' ? [block.text] : [];
905
+ })
906
+ .join('');
907
+ }
908
+ function providerErrorMessage(value) {
909
+ const payload = asObject(value);
910
+ const error = asObject(payload?.error);
911
+ if (typeof error?.message === 'string')
912
+ return error.message;
913
+ if (typeof payload?.message === 'string')
914
+ return payload.message;
915
+ return 'Request failed.';
916
+ }
917
+ function providerStreamErrorMessage(value) {
918
+ const payload = asObject(value);
919
+ if (!payload)
920
+ return undefined;
921
+ const error = asObject(payload.error);
922
+ const message = typeof error?.message === 'string'
923
+ ? error.message
924
+ : typeof payload.error === 'string'
925
+ ? payload.error
926
+ : typeof payload.message === 'string' &&
927
+ (payload.type === 'error' || payload.status === 'error')
928
+ ? payload.message
929
+ : undefined;
930
+ return message?.slice(0, 500);
931
+ }
932
+ function asObject(value) {
933
+ return value && typeof value === 'object' && !Array.isArray(value)
934
+ ? value
935
+ : undefined;
936
+ }
937
+ function isWorkPlugin(plugin) {
938
+ return plugin.type === 'completion' || plugin.type === 'chat';
939
+ }
940
+ function assertWorkPluginType(plugin) {
941
+ if (!isWorkPlugin(plugin)) {
942
+ throw new WorkModelProviderError(`Plugin "${plugin.name}" does not provide chat completions.`, 422, 'WORK_PLUGIN_TOOLS_UNSUPPORTED');
943
+ }
944
+ }
945
+ function assertOllamaProvider(provider) {
946
+ if (provider.providerType !== 'ollama' || provider.providerId) {
947
+ throw new WorkModelProviderError('Invalid Ollama provider selection.', 400, 'WORK_PROVIDER_INVALID');
948
+ }
949
+ }
950
+ export const workModelProviderService = new WorkModelProviderService();
951
+ export default workModelProviderService;
952
+ //# sourceMappingURL=workModelProviderService.js.map