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