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.
- package/README.md +26 -10
- package/backend/dist/db.d.ts.map +1 -1
- package/backend/dist/db.js +63 -0
- package/backend/dist/db.js.map +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +4 -4
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/chat.d.ts.map +1 -1
- package/backend/dist/routes/chat.js +93 -10
- package/backend/dist/routes/chat.js.map +1 -1
- package/backend/dist/routes/imageGen.d.ts.map +1 -1
- package/backend/dist/routes/imageGen.js +52 -29
- package/backend/dist/routes/imageGen.js.map +1 -1
- package/backend/dist/routes/plugins.d.ts.map +1 -1
- package/backend/dist/routes/plugins.js +218 -41
- package/backend/dist/routes/plugins.js.map +1 -1
- package/backend/dist/routes/preferences.d.ts.map +1 -1
- package/backend/dist/routes/preferences.js +5 -4
- package/backend/dist/routes/preferences.js.map +1 -1
- package/backend/dist/routes/tts.js +2 -2
- package/backend/dist/routes/tts.js.map +1 -1
- package/backend/dist/services/assistantCompletionService.d.ts +2 -1
- package/backend/dist/services/assistantCompletionService.d.ts.map +1 -1
- package/backend/dist/services/assistantCompletionService.js +3 -1
- package/backend/dist/services/assistantCompletionService.js.map +1 -1
- package/backend/dist/services/chatGenerationService.d.ts +5 -3
- package/backend/dist/services/chatGenerationService.d.ts.map +1 -1
- package/backend/dist/services/chatGenerationService.js +18 -6
- package/backend/dist/services/chatGenerationService.js.map +1 -1
- package/backend/dist/services/chatRequestService.d.ts +6 -4
- package/backend/dist/services/chatRequestService.d.ts.map +1 -1
- package/backend/dist/services/chatRequestService.js +10 -2
- package/backend/dist/services/chatRequestService.js.map +1 -1
- package/backend/dist/services/chatService.d.ts +2 -2
- package/backend/dist/services/chatService.d.ts.map +1 -1
- package/backend/dist/services/chatService.js +29 -17
- package/backend/dist/services/chatService.js.map +1 -1
- package/backend/dist/services/embeddingService.js +1 -1
- package/backend/dist/services/embeddingService.js.map +1 -1
- package/backend/dist/services/pluginActivationService.d.ts +11 -0
- package/backend/dist/services/pluginActivationService.d.ts.map +1 -0
- package/backend/dist/services/pluginActivationService.js +163 -0
- package/backend/dist/services/pluginActivationService.js.map +1 -0
- package/backend/dist/services/pluginCapabilityRegistryService.d.ts +1 -1
- package/backend/dist/services/pluginCapabilityRegistryService.d.ts.map +1 -1
- package/backend/dist/services/pluginCapabilityRegistryService.js +1 -1
- package/backend/dist/services/pluginCapabilityRegistryService.js.map +1 -1
- package/backend/dist/services/pluginCredentialsService.d.ts +16 -8
- package/backend/dist/services/pluginCredentialsService.d.ts.map +1 -1
- package/backend/dist/services/pluginCredentialsService.js +47 -14
- package/backend/dist/services/pluginCredentialsService.js.map +1 -1
- package/backend/dist/services/pluginEmbeddingService.d.ts +2 -2
- package/backend/dist/services/pluginEmbeddingService.d.ts.map +1 -1
- package/backend/dist/services/pluginEmbeddingService.js +33 -13
- package/backend/dist/services/pluginEmbeddingService.js.map +1 -1
- package/backend/dist/services/pluginImageGenerationService.d.ts +11 -7
- package/backend/dist/services/pluginImageGenerationService.d.ts.map +1 -1
- package/backend/dist/services/pluginImageGenerationService.js +265 -81
- package/backend/dist/services/pluginImageGenerationService.js.map +1 -1
- package/backend/dist/services/pluginService.d.ts +55 -25
- package/backend/dist/services/pluginService.d.ts.map +1 -1
- package/backend/dist/services/pluginService.js +663 -174
- package/backend/dist/services/pluginService.js.map +1 -1
- package/backend/dist/services/pluginTTSService.d.ts +4 -4
- package/backend/dist/services/pluginTTSService.d.ts.map +1 -1
- package/backend/dist/services/pluginTTSService.js +24 -28
- package/backend/dist/services/pluginTTSService.js.map +1 -1
- package/backend/dist/services/pluginVariablesService.d.ts +6 -1
- package/backend/dist/services/pluginVariablesService.d.ts.map +1 -1
- package/backend/dist/services/pluginVariablesService.js +59 -1
- package/backend/dist/services/pluginVariablesService.js.map +1 -1
- package/backend/dist/services/preferencesService.d.ts +2 -2
- package/backend/dist/services/preferencesService.d.ts.map +1 -1
- package/backend/dist/services/preferencesService.js +78 -10
- package/backend/dist/services/preferencesService.js.map +1 -1
- package/backend/dist/services/titleGenerationService.d.ts +6 -4
- package/backend/dist/services/titleGenerationService.d.ts.map +1 -1
- package/backend/dist/services/titleGenerationService.js +9 -5
- package/backend/dist/services/titleGenerationService.js.map +1 -1
- package/backend/dist/services/workAgentGuidance.d.ts +1 -0
- package/backend/dist/services/workAgentGuidance.d.ts.map +1 -1
- package/backend/dist/services/workAgentGuidance.js +2 -0
- package/backend/dist/services/workAgentGuidance.js.map +1 -1
- package/backend/dist/services/workAgentService.d.ts +5 -2
- package/backend/dist/services/workAgentService.d.ts.map +1 -1
- package/backend/dist/services/workAgentService.js +379 -53
- package/backend/dist/services/workAgentService.js.map +1 -1
- package/backend/dist/services/workModelProviderService.d.ts +8 -3
- package/backend/dist/services/workModelProviderService.d.ts.map +1 -1
- package/backend/dist/services/workModelProviderService.js +293 -33
- package/backend/dist/services/workModelProviderService.js.map +1 -1
- package/backend/dist/services/workTaskService.d.ts +2 -0
- package/backend/dist/services/workTaskService.d.ts.map +1 -1
- package/backend/dist/services/workTaskService.js +89 -14
- package/backend/dist/services/workTaskService.js.map +1 -1
- package/backend/dist/storage.d.ts.map +1 -1
- package/backend/dist/storage.js +9 -6
- package/backend/dist/storage.js.map +1 -1
- package/backend/dist/storageMappers.d.ts +3 -0
- package/backend/dist/storageMappers.d.ts.map +1 -1
- package/backend/dist/storageMappers.js +3 -0
- package/backend/dist/storageMappers.js.map +1 -1
- package/backend/dist/types/index.d.ts +54 -1
- package/backend/dist/types/index.d.ts.map +1 -1
- package/backend/dist/types/index.js.map +1 -1
- package/backend/dist/types/work.d.ts +1 -1
- package/backend/dist/types/work.d.ts.map +1 -1
- package/backend/dist/utils/chatContext.d.ts +13 -1
- package/backend/dist/utils/chatContext.d.ts.map +1 -1
- package/backend/dist/utils/chatContext.js +52 -0
- package/backend/dist/utils/chatContext.js.map +1 -1
- package/backend/dist/utils/chatProviderSelection.d.ts +15 -0
- package/backend/dist/utils/chatProviderSelection.d.ts.map +1 -0
- package/backend/dist/utils/chatProviderSelection.js +59 -0
- package/backend/dist/utils/chatProviderSelection.js.map +1 -0
- package/backend/dist/utils/imageGenerationValidation.d.ts +3 -0
- package/backend/dist/utils/imageGenerationValidation.d.ts.map +1 -0
- package/backend/dist/utils/imageGenerationValidation.js +55 -0
- package/backend/dist/utils/imageGenerationValidation.js.map +1 -0
- package/backend/dist/utils/ollamaLibrary.d.ts.map +1 -1
- package/backend/dist/utils/ollamaLibrary.js +81 -8
- package/backend/dist/utils/ollamaLibrary.js.map +1 -1
- package/backend/dist/utils/openAIResponsesAdapter.d.ts +88 -0
- package/backend/dist/utils/openAIResponsesAdapter.d.ts.map +1 -0
- package/backend/dist/utils/openAIResponsesAdapter.js +521 -0
- package/backend/dist/utils/openAIResponsesAdapter.js.map +1 -0
- package/backend/dist/utils/pluginChatAdapter.d.ts +7 -4
- package/backend/dist/utils/pluginChatAdapter.d.ts.map +1 -1
- package/backend/dist/utils/pluginChatAdapter.js +33 -5
- package/backend/dist/utils/pluginChatAdapter.js.map +1 -1
- package/backend/dist/utils/pluginChatContext.d.ts.map +1 -1
- package/backend/dist/utils/pluginChatContext.js +5 -1
- package/backend/dist/utils/pluginChatContext.js.map +1 -1
- package/backend/dist/utils/pluginConnectionVariables.d.ts +6 -0
- package/backend/dist/utils/pluginConnectionVariables.d.ts.map +1 -0
- package/backend/dist/utils/pluginConnectionVariables.js +52 -0
- package/backend/dist/utils/pluginConnectionVariables.js.map +1 -0
- package/backend/dist/utils/pluginDefinitionTrust.d.ts +9 -0
- package/backend/dist/utils/pluginDefinitionTrust.d.ts.map +1 -0
- package/backend/dist/utils/pluginDefinitionTrust.js +71 -0
- package/backend/dist/utils/pluginDefinitionTrust.js.map +1 -0
- package/backend/dist/utils/pluginStreamAdapter.d.ts +3 -0
- package/backend/dist/utils/pluginStreamAdapter.d.ts.map +1 -1
- package/backend/dist/utils/pluginStreamAdapter.js +410 -0
- package/backend/dist/utils/pluginStreamAdapter.js.map +1 -1
- package/backend/dist/utils/pluginStreaming.d.ts +7 -1
- package/backend/dist/utils/pluginStreaming.d.ts.map +1 -1
- package/backend/dist/utils/pluginStreaming.js +12 -1
- package/backend/dist/utils/pluginStreaming.js.map +1 -1
- package/backend/dist/utils/pluginValidation.d.ts +21 -2
- package/backend/dist/utils/pluginValidation.d.ts.map +1 -1
- package/backend/dist/utils/pluginValidation.js +190 -17
- package/backend/dist/utils/pluginValidation.js.map +1 -1
- package/backend/dist/utils/pluginVariableValidation.d.ts +7 -0
- package/backend/dist/utils/pluginVariableValidation.d.ts.map +1 -1
- package/backend/dist/utils/pluginVariableValidation.js +79 -4
- package/backend/dist/utils/pluginVariableValidation.js.map +1 -1
- package/backend/dist/websocketServer.d.ts.map +1 -1
- package/backend/dist/websocketServer.js +16 -3
- package/backend/dist/websocketServer.js.map +1 -1
- package/backend/package.json +1 -1
- package/frontend/dist/assets/index-CKV9Lnj8.js +5 -0
- package/frontend/dist/assets/index-Dme2Ozol.css +1 -0
- package/frontend/dist/index.html +4 -4
- package/frontend/dist/js/{ArtifactContainer-CXfmVx8T.js → ArtifactContainer-B0uVR8Z9.js} +1 -1
- package/frontend/dist/js/{ArtifactDemoPage-B1_AuMd2.js → ArtifactDemoPage-DBVPHFxk.js} +1 -1
- package/frontend/dist/js/{ArtifactSlideOutPanel-C0eTguNL.js → ArtifactSlideOutPanel-Cg5kAwv9.js} +1 -1
- package/frontend/dist/js/ChatPage-CPHlIZqU.js +16 -0
- package/frontend/dist/js/{FirstTimeSetup-DOoZUaRs.js → FirstTimeSetup-DksL5arK.js} +1 -1
- package/frontend/dist/js/GalleryPage-1mdUGo_N.js +1 -0
- package/frontend/dist/js/LibreClawPage-qGr1Jy8p.js +1 -0
- package/frontend/dist/js/{ModelManagerModals-DneqFQJC.js → ModelManagerModals-DltRprHU.js} +1 -1
- package/frontend/dist/js/ModelSelector-CUnwRmNU.js +1 -0
- package/frontend/dist/js/ModelsPage-DMYVg3LT.js +2 -0
- package/frontend/dist/js/PersonasPage-Ct-KKW-j.js +13 -0
- package/frontend/dist/js/{RichMessageContent-WQLdJ5jq.js → RichMessageContent-CGBAcUES.js} +1 -1
- package/frontend/dist/js/SettingsModal-BVj3gFCS.js +1 -0
- package/frontend/dist/js/UserManagementPage-D3tLGtTu.js +1 -0
- package/frontend/dist/js/WorkPage-DhTjy-kf.js +2 -0
- package/frontend/dist/js/api-YWoSobKv.js +19 -0
- package/frontend/dist/js/{ar-CCVY0-7X.js → ar-KceI0PfT.js} +1 -1
- package/frontend/dist/js/{bn-CoU5CSph.js → bn-DsKo2dzS.js} +1 -1
- package/frontend/dist/js/{cs-CR6pjIbG.js → cs-6UKhKj0A.js} +1 -1
- package/frontend/dist/js/{da-CkyYITRz.js → da-CJgZ3YuL.js} +1 -1
- package/frontend/dist/js/{de-DwNANzB-.js → de-BZAmOuCT.js} +1 -1
- package/frontend/dist/js/{es-TxygiaVQ.js → es-CD4u7no9.js} +1 -1
- package/frontend/dist/js/{estree-DPzMpjFm.js → estree-PDLT672k.js} +1 -1
- package/frontend/dist/js/{fr-DvEqVpZr.js → fr-Cou2jOS3.js} +1 -1
- package/frontend/dist/js/{hi-CyrWkyh7.js → hi-ZxkbNCyB.js} +1 -1
- package/frontend/dist/js/{html-R50jmBYq.js → html-CkhF9wDQ.js} +1 -1
- package/frontend/dist/js/{id-PJjIGHGM.js → id-DT9CqaWQ.js} +1 -1
- package/frontend/dist/js/{is-ChXcN1tI.js → is-BWSbAIoq.js} +1 -1
- package/frontend/dist/js/{it-DsgM8-MJ.js → it-CdNoTXm3.js} +1 -1
- package/frontend/dist/js/{ja-ivtt80N2.js → ja-CIuQg0xv.js} +1 -1
- package/frontend/dist/js/{ko-B8l_Ice1.js → ko-BWRUjDOH.js} +1 -1
- package/frontend/dist/js/{markdown-C08x2H-K.js → markdown-B1iNUbse.js} +1 -1
- package/frontend/dist/js/{ms-DoUBI6Qi.js → ms-gJstIj2a.js} +1 -1
- package/frontend/dist/js/{nl-DsX5Hj8m.js → nl-CydjVNUw.js} +1 -1
- package/frontend/dist/js/{pl-Db_NKL4x.js → pl-DPN9mAa5.js} +1 -1
- package/frontend/dist/js/{pt-CpzBhb5J.js → pt-MRXGqIjQ.js} +1 -1
- package/frontend/dist/js/{ru-DCXvJyXJ.js → ru-CvQ9GkJF.js} +1 -1
- package/frontend/dist/js/{standalone-DpMZUvjd.js → standalone-CCd6rkf-.js} +1 -1
- package/frontend/dist/js/{sv-DcVoybc7.js → sv-BxS2fvhU.js} +1 -1
- package/frontend/dist/js/{th-D0G7HyQn.js → th-DMxyn6e4.js} +1 -1
- package/frontend/dist/js/{tr-CMDOh4Aq.js → tr-BTPldUaA.js} +1 -1
- package/frontend/dist/js/{typescript-B6y_hxBm.js → typescript-DKJ1uM5L.js} +1 -1
- package/frontend/dist/js/ui-vendor-CAFEDSaO.js +177 -0
- package/frontend/dist/js/{uk-D2MMa6LP.js → uk-BGxZOKgp.js} +1 -1
- package/frontend/dist/js/useBaseQuery-MuQj7vA5.js +1 -0
- package/frontend/dist/js/useQuery-CYGPkxWR.js +1 -0
- package/frontend/dist/js/{vi-B8uHT8yh.js → vi-82JkwFsQ.js} +1 -1
- package/frontend/dist/js/{zh-CNqa_15K.js → zh-Bxxfwci3.js} +1 -1
- package/package.json +3 -2
- package/plugins/github.json +1 -2
- package/plugins/huggingface.json +30 -30
- package/plugins/mlx-lm.json +55 -0
- package/plugins/openai.json +53 -22
- package/frontend/dist/assets/index-Bv-twCuY.css +0 -1
- package/frontend/dist/assets/index-uCHBH_PM.js +0 -5
- package/frontend/dist/js/ChatPage-1bK0gDBy.js +0 -16
- package/frontend/dist/js/GalleryPage-6WwyH3kt.js +0 -1
- package/frontend/dist/js/LibreClawPage-ChHVfPLb.js +0 -1
- package/frontend/dist/js/ModelsPage-Dw6wdiMt.js +0 -2
- package/frontend/dist/js/PersonasPage-BxdI2e1-.js +0 -13
- package/frontend/dist/js/SettingsModal-C-cK6TNb.js +0 -1
- package/frontend/dist/js/UserManagementPage-DqjNXPkf.js +0 -1
- package/frontend/dist/js/WorkPage-MGR3odo6.js +0 -2
- package/frontend/dist/js/api-DD5qhhLf.js +0 -19
- package/frontend/dist/js/ui-vendor-DUqvZsfs.js +0 -177
- package/frontend/dist/js/useBaseQuery-Duml3DYp.js +0 -1
- package/frontend/dist/js/useQuery-C0lZwNoc.js +0 -1
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import workModelProviderService from './workModelProviderService.js';
|
|
17
|
+
import workModelProviderService, { WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY, WORK_TOOL_ARGUMENTS_ERROR_MESSAGE, } from './workModelProviderService.js';
|
|
18
18
|
import workEventService from './workEventService.js';
|
|
19
|
-
import { buildWorkAgentSystemPrompt, buildWorkBudgetExhaustionPrompt, WORK_AGENT_SKILLS, workToolCallBudget, } from './workAgentGuidance.js';
|
|
19
|
+
import { buildWorkAgentSystemPrompt, buildWorkBudgetExhaustionPrompt, WORK_AGENT_SKILLS, WORK_WRITE_FILE_RECOMMENDED_CHARS, workToolCallBudget, } from './workAgentGuidance.js';
|
|
20
20
|
import workRuntimeService from './workRuntimeService.js';
|
|
21
|
-
import workTaskService, { WorkConflictError, WorkNotFoundError, } from './workTaskService.js';
|
|
21
|
+
import workTaskService, { WORK_MESSAGE_METADATA_MAX_BYTES, WorkConflictError, WorkNotFoundError, } from './workTaskService.js';
|
|
22
22
|
import { createLogger } from '../utils/logger.js';
|
|
23
|
+
import { boundedOpenAIResponsesOutputItems, OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY, OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY, OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY, } from '../utils/openAIResponsesAdapter.js';
|
|
23
24
|
const logger = createLogger('services:work-agent');
|
|
25
|
+
export const WORK_PROVIDER_STATE_METADATA_KEY = 'workProviderState';
|
|
24
26
|
export const WORK_TOOL_SCHEMAS = [
|
|
25
27
|
functionTool('list_files', 'List direct children of a workspace directory.', {
|
|
26
28
|
path: stringProperty('Relative directory path. Defaults to workspace root.'),
|
|
@@ -28,7 +30,7 @@ export const WORK_TOOL_SCHEMAS = [
|
|
|
28
30
|
functionTool('read_file', 'Read a UTF-8 text file from the persistent workspace.', { path: stringProperty('Relative file path.') }, ['path']),
|
|
29
31
|
functionTool('write_file', 'Create or replace a UTF-8 text file in the persistent workspace.', {
|
|
30
32
|
path: stringProperty('Relative file path.'),
|
|
31
|
-
content: stringProperty(
|
|
33
|
+
content: stringProperty(`Complete file content. Keep one write below ${WORK_WRITE_FILE_RECOMMENDED_CHARS.toLocaleString('en-US')} characters and split larger implementations into focused files.`),
|
|
32
34
|
}, ['path', 'content']),
|
|
33
35
|
functionTool('search_files', 'Search text recursively in workspace files.', {
|
|
34
36
|
query: stringProperty('Literal text to find.'),
|
|
@@ -245,7 +247,13 @@ export class WorkAgentService {
|
|
|
245
247
|
workTaskService.updateTaskStatus(taskId, 'running');
|
|
246
248
|
const roundLimit = workRuntimeService.limits.maxRounds;
|
|
247
249
|
const toolCallLimit = workToolCallBudget(roundLimit);
|
|
248
|
-
const
|
|
250
|
+
const providerSelection = {
|
|
251
|
+
providerType: run.providerType,
|
|
252
|
+
providerId: run.providerId,
|
|
253
|
+
};
|
|
254
|
+
const providerRoutingFingerprint = workModelProviderService.getRoutingFingerprint(run.model, providerSelection, userId);
|
|
255
|
+
const providerStateScope = workModelProviderService.getResponsesStateScope(run.model, providerSelection, userId);
|
|
256
|
+
const messages = this.contextMessages(task, roundLimit, providerStateScope, run);
|
|
249
257
|
let totalToolCalls = 0;
|
|
250
258
|
let accumulatedInputTokens = 0;
|
|
251
259
|
let accumulatedOutputTokens = 0;
|
|
@@ -267,18 +275,13 @@ export class WorkAgentService {
|
|
|
267
275
|
const roundStartedAt = Date.now();
|
|
268
276
|
let response;
|
|
269
277
|
try {
|
|
278
|
+
this.assertStableProviderRouting(run, userId, providerRoutingFingerprint);
|
|
270
279
|
response = await workModelProviderService.generateChatStreamResponse({
|
|
271
280
|
model: run.model,
|
|
272
281
|
messages,
|
|
273
282
|
tools: WORK_TOOL_SCHEMAS,
|
|
274
|
-
options: run.providerType === 'plugin'
|
|
275
|
-
? { num_predict: 4096 }
|
|
276
|
-
: undefined,
|
|
277
283
|
stream: true,
|
|
278
|
-
}, {
|
|
279
|
-
providerType: run.providerType,
|
|
280
|
-
providerId: run.providerId,
|
|
281
|
-
}, userId, {
|
|
284
|
+
}, providerSelection, userId, {
|
|
282
285
|
onContent: delta => contentStream.push(delta),
|
|
283
286
|
onReasoning: delta => reasoningStream.push(delta),
|
|
284
287
|
onUsage: usage => {
|
|
@@ -303,9 +306,30 @@ export class WorkAgentService {
|
|
|
303
306
|
streamedReasoningTotal = reasoningStream.currentTotal;
|
|
304
307
|
}
|
|
305
308
|
this.throwIfCancelled(runId, controller);
|
|
309
|
+
assertCompleteProviderResponse(response);
|
|
306
310
|
accumulatedInputTokens += roundInputTokens;
|
|
307
311
|
accumulatedOutputTokens += roundOutputTokens;
|
|
308
312
|
const toolCalls = normalizeToolCalls(response);
|
|
313
|
+
const boundedProviderOutput = boundedOpenAIResponsesOutputItems(response.message.providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]);
|
|
314
|
+
const providerMetadata = response.message.providerMetadata;
|
|
315
|
+
const hasResponsesStateMetadata = providerMetadata?.[OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY] ===
|
|
316
|
+
true ||
|
|
317
|
+
providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY] !==
|
|
318
|
+
undefined ||
|
|
319
|
+
providerMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] !==
|
|
320
|
+
undefined;
|
|
321
|
+
const providerStateMetadata = toPersistedWorkProviderState(run, providerMetadata);
|
|
322
|
+
if (toolCalls.length > 0 &&
|
|
323
|
+
(providerStateScope || hasResponsesStateMetadata) &&
|
|
324
|
+
(providerMetadata?.[OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY] ===
|
|
325
|
+
true ||
|
|
326
|
+
!providerStateScope ||
|
|
327
|
+
providerMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] !==
|
|
328
|
+
providerStateScope ||
|
|
329
|
+
!boundedProviderOutput.items ||
|
|
330
|
+
!providerStateMetadata)) {
|
|
331
|
+
throw new WorkAgentHttpError('The provider returned tool calls without bounded durable replay state.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
332
|
+
}
|
|
309
333
|
totalToolCalls += toolCalls.length;
|
|
310
334
|
if (totalToolCalls > toolCallLimit) {
|
|
311
335
|
budgetReason = 'tool-call';
|
|
@@ -319,7 +343,7 @@ export class WorkAgentService {
|
|
|
319
343
|
if (toolCalls.length === 0) {
|
|
320
344
|
const finalContent = assistantContent ||
|
|
321
345
|
'The model completed without returning a text response.';
|
|
322
|
-
workTaskService.addMessage(taskId, runId, 'assistant', 'message', finalContent);
|
|
346
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'message', finalContent, providerStateMetadata);
|
|
323
347
|
// Keep completion hidden until the disposable container has either
|
|
324
348
|
// stopped or been retained for a verified live preview. Consumers
|
|
325
349
|
// can then refresh files immediately without racing teardown.
|
|
@@ -335,13 +359,26 @@ export class WorkAgentService {
|
|
|
335
359
|
});
|
|
336
360
|
return;
|
|
337
361
|
}
|
|
362
|
+
const toolValidationErrors = new Map();
|
|
363
|
+
for (const call of toolCalls) {
|
|
364
|
+
const validationError = validateToolCallArguments(call);
|
|
365
|
+
if (validationError)
|
|
366
|
+
toolValidationErrors.set(call, validationError);
|
|
367
|
+
}
|
|
368
|
+
const hasInvalidToolArguments = toolValidationErrors.size > 0;
|
|
338
369
|
messages.push({
|
|
339
370
|
role: 'assistant',
|
|
340
371
|
content: assistantContent,
|
|
341
372
|
tool_calls: toolCalls,
|
|
373
|
+
...(response.message.providerMetadata
|
|
374
|
+
? { providerMetadata: response.message.providerMetadata }
|
|
375
|
+
: {}),
|
|
342
376
|
});
|
|
343
377
|
if (assistantContent) {
|
|
344
|
-
workTaskService.addMessage(taskId, runId, 'assistant', 'message', assistantContent);
|
|
378
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'message', assistantContent, providerStateMetadata);
|
|
379
|
+
}
|
|
380
|
+
else if (providerStateMetadata) {
|
|
381
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'provider_state', '', providerStateMetadata);
|
|
345
382
|
}
|
|
346
383
|
for (const call of toolCalls) {
|
|
347
384
|
this.throwIfCancelled(runId, controller);
|
|
@@ -368,6 +405,13 @@ export class WorkAgentService {
|
|
|
368
405
|
toolName: call.function.name,
|
|
369
406
|
};
|
|
370
407
|
try {
|
|
408
|
+
const validationError = toolValidationErrors.get(call);
|
|
409
|
+
if (validationError) {
|
|
410
|
+
throw new WorkAgentHttpError(validationError, 400, 'WORK_INVALID_TOOL_ARGUMENTS');
|
|
411
|
+
}
|
|
412
|
+
if (hasInvalidToolArguments) {
|
|
413
|
+
throw new WorkAgentHttpError('This tool call was not executed because another call in the same model response had incomplete or invalid arguments. Retry the batch with smaller payloads.', 400, 'WORK_TOOL_BATCH_NOT_EXECUTED');
|
|
414
|
+
}
|
|
371
415
|
const result = await this.executeTool(task, call);
|
|
372
416
|
toolOutput = result.content;
|
|
373
417
|
toolMetadata = { ...toolMetadata, ...result.metadata };
|
|
@@ -397,6 +441,7 @@ export class WorkAgentService {
|
|
|
397
441
|
role: 'tool',
|
|
398
442
|
content: toolOutput,
|
|
399
443
|
tool_name: call.function.name,
|
|
444
|
+
tool_call_id: call.id,
|
|
400
445
|
});
|
|
401
446
|
}
|
|
402
447
|
}
|
|
@@ -415,6 +460,7 @@ export class WorkAgentService {
|
|
|
415
460
|
let handoffResponse;
|
|
416
461
|
try {
|
|
417
462
|
try {
|
|
463
|
+
this.assertStableProviderRouting(run, userId, providerRoutingFingerprint);
|
|
418
464
|
handoffResponse =
|
|
419
465
|
await workModelProviderService.generateChatStreamResponse({
|
|
420
466
|
model: run.model,
|
|
@@ -430,10 +476,7 @@ export class WorkAgentService {
|
|
|
430
476
|
? { num_predict: 2048 }
|
|
431
477
|
: undefined,
|
|
432
478
|
stream: true,
|
|
433
|
-
}, {
|
|
434
|
-
providerType: run.providerType,
|
|
435
|
-
providerId: run.providerId,
|
|
436
|
-
}, userId, {
|
|
479
|
+
}, providerSelection, userId, {
|
|
437
480
|
onContent: delta => handoffContentStream.push(delta),
|
|
438
481
|
onReasoning: delta => handoffReasoningStream.push(delta),
|
|
439
482
|
onUsage: usage => {
|
|
@@ -451,6 +494,7 @@ export class WorkAgentService {
|
|
|
451
494
|
});
|
|
452
495
|
},
|
|
453
496
|
}, controller.signal);
|
|
497
|
+
assertCompleteProviderResponse(handoffResponse);
|
|
454
498
|
}
|
|
455
499
|
catch (error) {
|
|
456
500
|
if (controller.signal.aborted)
|
|
@@ -479,7 +523,12 @@ export class WorkAgentService {
|
|
|
479
523
|
}
|
|
480
524
|
const handoffContent = boundUtf8(handoffResponse.message?.content?.trim() ||
|
|
481
525
|
`The run reached its ${budgetReason} budget. Start a follow-up run to continue in the same workspace.`, 100000);
|
|
482
|
-
|
|
526
|
+
const handoffProviderStateMetadata = toPersistedWorkProviderState(run, handoffResponse.message.providerMetadata);
|
|
527
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'message', handoffContent, {
|
|
528
|
+
budgetHandoff: true,
|
|
529
|
+
budgetReason,
|
|
530
|
+
...handoffProviderStateMetadata,
|
|
531
|
+
});
|
|
483
532
|
await settleExecutionContainer();
|
|
484
533
|
this.throwIfCancelled(runId, controller);
|
|
485
534
|
if (!isActiveRunStatus(workTaskService.getRun(runId)?.status))
|
|
@@ -589,13 +638,17 @@ export class WorkAgentService {
|
|
|
589
638
|
logger.error(`Could not stop idle Work container ${task.containerName}:`, error);
|
|
590
639
|
}
|
|
591
640
|
}
|
|
592
|
-
|
|
593
|
-
const
|
|
594
|
-
.
|
|
595
|
-
.
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
641
|
+
assertStableProviderRouting(run, userId, expectedFingerprint) {
|
|
642
|
+
const currentFingerprint = workModelProviderService.getRoutingFingerprint(run.model, {
|
|
643
|
+
providerType: run.providerType,
|
|
644
|
+
providerId: run.providerId,
|
|
645
|
+
}, userId);
|
|
646
|
+
if (currentFingerprint !== expectedFingerprint) {
|
|
647
|
+
throw new WorkAgentHttpError('The provider routing changed while this Work run was active. Start a new run with the updated provider configuration.', 409, 'WORK_PROVIDER_ROUTING_CHANGED');
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
contextMessages(task, roundLimit, providerStateScope, provider = task) {
|
|
651
|
+
const persisted = restorePersistedWorkContext(workTaskService.getRecentModelContextMessages(task.id, 30), provider, providerStateScope);
|
|
599
652
|
return [
|
|
600
653
|
{
|
|
601
654
|
role: 'system',
|
|
@@ -717,55 +770,298 @@ export class WorkAgentHttpError extends Error {
|
|
|
717
770
|
this.code = code;
|
|
718
771
|
}
|
|
719
772
|
}
|
|
773
|
+
function toPersistedWorkProviderState(run, providerMetadata) {
|
|
774
|
+
const boundedOutput = boundedOpenAIResponsesOutputItems(providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]);
|
|
775
|
+
if (providerMetadata?.[OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY] === true ||
|
|
776
|
+
!boundedOutput.items ||
|
|
777
|
+
typeof providerMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] !==
|
|
778
|
+
'string') {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
const persisted = {
|
|
782
|
+
[WORK_PROVIDER_STATE_METADATA_KEY]: {
|
|
783
|
+
providerType: run.providerType,
|
|
784
|
+
...(run.providerId ? { providerId: run.providerId } : {}),
|
|
785
|
+
model: run.model,
|
|
786
|
+
providerMetadata: {
|
|
787
|
+
...providerMetadata,
|
|
788
|
+
[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]: boundedOutput.items,
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
try {
|
|
793
|
+
return Buffer.byteLength(JSON.stringify(persisted), 'utf8') <=
|
|
794
|
+
WORK_MESSAGE_METADATA_MAX_BYTES
|
|
795
|
+
? persisted
|
|
796
|
+
: undefined;
|
|
797
|
+
}
|
|
798
|
+
catch {
|
|
799
|
+
return undefined;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
export function restorePersistedWorkContext(messages, provider, expectedStateScope) {
|
|
803
|
+
const restored = [];
|
|
804
|
+
let pendingGroup;
|
|
805
|
+
const flushPendingGroup = () => {
|
|
806
|
+
if (!pendingGroup)
|
|
807
|
+
return;
|
|
808
|
+
if (pendingGroup.invalid) {
|
|
809
|
+
if (pendingGroup.fallback)
|
|
810
|
+
restored.push(pendingGroup.fallback);
|
|
811
|
+
pendingGroup = undefined;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
restored.push(pendingGroup.assistant, ...pendingGroup.results);
|
|
815
|
+
for (const call of pendingGroup.calls) {
|
|
816
|
+
if (pendingGroup.resultIds.has(call.id))
|
|
817
|
+
continue;
|
|
818
|
+
restored.push({
|
|
819
|
+
role: 'tool',
|
|
820
|
+
content: 'Tool execution was interrupted; outcome unknown. Inspect the workspace before retrying.',
|
|
821
|
+
tool_name: call.name,
|
|
822
|
+
tool_call_id: call.id,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
pendingGroup = undefined;
|
|
826
|
+
};
|
|
827
|
+
for (const message of messages) {
|
|
828
|
+
if (message.kind === 'provider_state' ||
|
|
829
|
+
(message.role === 'assistant' && message.kind === 'message')) {
|
|
830
|
+
flushPendingGroup();
|
|
831
|
+
const providerMetadata = matchingWorkProviderMetadata(message, provider, expectedStateScope);
|
|
832
|
+
const responseCalls = providerMetadata
|
|
833
|
+
? responseFunctionCalls(providerMetadata)
|
|
834
|
+
: [];
|
|
835
|
+
const rawFunctionCallCount = Array.isArray(providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY])
|
|
836
|
+
? providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY].filter(item => objectValue(item)?.type === 'function_call').length
|
|
837
|
+
: 0;
|
|
838
|
+
const expectedCallIds = new Set(responseCalls.map(call => call.id));
|
|
839
|
+
if (message.kind === 'provider_state' && !providerMetadata) {
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
if (message.kind !== 'message' && message.kind !== 'provider_state') {
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
if (expectedCallIds.size !== responseCalls.length ||
|
|
846
|
+
rawFunctionCallCount !== responseCalls.length) {
|
|
847
|
+
if (message.kind === 'message') {
|
|
848
|
+
restored.push({ role: 'assistant', content: message.content });
|
|
849
|
+
}
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const assistant = {
|
|
853
|
+
role: 'assistant',
|
|
854
|
+
content: message.content,
|
|
855
|
+
...(responseCalls.length > 0
|
|
856
|
+
? {
|
|
857
|
+
tool_calls: responseCalls.map(call => ({
|
|
858
|
+
id: call.id,
|
|
859
|
+
type: 'function',
|
|
860
|
+
function: {
|
|
861
|
+
name: call.name,
|
|
862
|
+
arguments: call.arguments,
|
|
863
|
+
},
|
|
864
|
+
})),
|
|
865
|
+
}
|
|
866
|
+
: {}),
|
|
867
|
+
...(providerMetadata ? { providerMetadata } : {}),
|
|
868
|
+
};
|
|
869
|
+
if (responseCalls.length === 0) {
|
|
870
|
+
restored.push(assistant);
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
pendingGroup = {
|
|
874
|
+
assistant,
|
|
875
|
+
...(message.kind === 'message'
|
|
876
|
+
? {
|
|
877
|
+
fallback: {
|
|
878
|
+
role: 'assistant',
|
|
879
|
+
content: message.content,
|
|
880
|
+
},
|
|
881
|
+
}
|
|
882
|
+
: {}),
|
|
883
|
+
calls: responseCalls,
|
|
884
|
+
expectedCallIds,
|
|
885
|
+
results: [],
|
|
886
|
+
resultIds: new Set(),
|
|
887
|
+
invalid: false,
|
|
888
|
+
};
|
|
889
|
+
continue;
|
|
890
|
+
}
|
|
891
|
+
if (message.role === 'user' && message.kind === 'message') {
|
|
892
|
+
flushPendingGroup();
|
|
893
|
+
restored.push({ role: 'user', content: message.content });
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (message.role !== 'tool' || message.kind !== 'tool_result') {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const toolCallId = typeof message.metadata?.toolCallId === 'string'
|
|
900
|
+
? message.metadata.toolCallId
|
|
901
|
+
: undefined;
|
|
902
|
+
if (!toolCallId || !pendingGroup)
|
|
903
|
+
continue;
|
|
904
|
+
if (!pendingGroup.expectedCallIds.has(toolCallId) ||
|
|
905
|
+
pendingGroup.resultIds.has(toolCallId)) {
|
|
906
|
+
pendingGroup.invalid = true;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const matchingCall = pendingGroup.calls.find(call => call.id === toolCallId);
|
|
910
|
+
if (!matchingCall) {
|
|
911
|
+
pendingGroup.invalid = true;
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
const persistedName = typeof message.metadata?.name === 'string'
|
|
915
|
+
? message.metadata.name
|
|
916
|
+
: undefined;
|
|
917
|
+
pendingGroup.results.push({
|
|
918
|
+
role: 'tool',
|
|
919
|
+
content: message.content,
|
|
920
|
+
tool_name: persistedName || matchingCall.name,
|
|
921
|
+
tool_call_id: toolCallId,
|
|
922
|
+
});
|
|
923
|
+
pendingGroup.resultIds.add(toolCallId);
|
|
924
|
+
}
|
|
925
|
+
flushPendingGroup();
|
|
926
|
+
return restored;
|
|
927
|
+
}
|
|
928
|
+
function matchingWorkProviderMetadata(message, provider, expectedStateScope) {
|
|
929
|
+
const state = objectValue(message.metadata?.[WORK_PROVIDER_STATE_METADATA_KEY]);
|
|
930
|
+
const providerMetadata = objectValue(state?.providerMetadata);
|
|
931
|
+
const boundedOutput = boundedOpenAIResponsesOutputItems(providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]);
|
|
932
|
+
if (state?.providerType !== provider.providerType ||
|
|
933
|
+
(state.providerId || undefined) !== (provider.providerId || undefined) ||
|
|
934
|
+
state.model !== provider.model ||
|
|
935
|
+
!expectedStateScope ||
|
|
936
|
+
providerMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] !==
|
|
937
|
+
expectedStateScope ||
|
|
938
|
+
!boundedOutput.items) {
|
|
939
|
+
return undefined;
|
|
940
|
+
}
|
|
941
|
+
return {
|
|
942
|
+
...providerMetadata,
|
|
943
|
+
[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]: boundedOutput.items,
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function responseFunctionCalls(providerMetadata) {
|
|
947
|
+
const outputItems = providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY];
|
|
948
|
+
if (!Array.isArray(outputItems))
|
|
949
|
+
return [];
|
|
950
|
+
return outputItems.flatMap(itemValue => {
|
|
951
|
+
const item = objectValue(itemValue);
|
|
952
|
+
if (item?.type !== 'function_call' ||
|
|
953
|
+
typeof item.name !== 'string' ||
|
|
954
|
+
item.name.length === 0 ||
|
|
955
|
+
typeof item.call_id !== 'string' ||
|
|
956
|
+
item.call_id.length === 0) {
|
|
957
|
+
return [];
|
|
958
|
+
}
|
|
959
|
+
return [
|
|
960
|
+
{
|
|
961
|
+
id: item.call_id,
|
|
962
|
+
name: item.name,
|
|
963
|
+
arguments: item.arguments ?? '',
|
|
964
|
+
},
|
|
965
|
+
];
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function objectValue(value) {
|
|
969
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
970
|
+
? value
|
|
971
|
+
: undefined;
|
|
972
|
+
}
|
|
973
|
+
function assertCompleteProviderResponse(response) {
|
|
974
|
+
if (!response.done_reason?.startsWith('incomplete:'))
|
|
975
|
+
return;
|
|
976
|
+
const reason = response.done_reason.slice('incomplete:'.length) || 'unknown';
|
|
977
|
+
throw new WorkAgentHttpError(`The provider returned an incomplete response (${reason}). Retry with a larger output-token limit or a smaller request.`, 502, 'WORK_PROVIDER_INCOMPLETE_RESPONSE');
|
|
978
|
+
}
|
|
720
979
|
export function normalizeToolCalls(response) {
|
|
721
980
|
const raw = response.message?.tool_calls;
|
|
722
981
|
if (!Array.isArray(raw))
|
|
723
982
|
return [];
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
983
|
+
const responsesMetadata = response.message?.providerMetadata;
|
|
984
|
+
const isResponsesCallBatch = typeof responsesMetadata?.[OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY] ===
|
|
985
|
+
'string' ||
|
|
986
|
+
responsesMetadata?.[OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY] === true ||
|
|
987
|
+
Array.isArray(responsesMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]);
|
|
988
|
+
const calls = [];
|
|
989
|
+
for (const [index, value] of raw.entries()) {
|
|
990
|
+
if (!value || typeof value !== 'object') {
|
|
991
|
+
if (isResponsesCallBatch) {
|
|
992
|
+
throw new WorkAgentHttpError('The provider returned a malformed tool call.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
993
|
+
}
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
728
996
|
const record = value;
|
|
729
997
|
const fn = record.function && typeof record.function === 'object'
|
|
730
998
|
? record.function
|
|
731
999
|
: undefined;
|
|
732
|
-
if (!fn || typeof fn.name !== 'string')
|
|
733
|
-
|
|
1000
|
+
if (!fn || typeof fn.name !== 'string' || !fn.name) {
|
|
1001
|
+
if (isResponsesCallBatch) {
|
|
1002
|
+
throw new WorkAgentHttpError('The provider returned a tool call without a function name.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
1003
|
+
}
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
734
1006
|
let args = {};
|
|
1007
|
+
let argumentError;
|
|
735
1008
|
if (fn.arguments && typeof fn.arguments === 'object') {
|
|
736
1009
|
args = fn.arguments;
|
|
737
1010
|
}
|
|
738
1011
|
else if (typeof fn.arguments === 'string') {
|
|
739
1012
|
try {
|
|
740
1013
|
const parsed = JSON.parse(fn.arguments);
|
|
741
|
-
if (parsed && typeof parsed === 'object') {
|
|
1014
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
742
1015
|
args = parsed;
|
|
743
1016
|
}
|
|
1017
|
+
else if (fn.arguments.trim()) {
|
|
1018
|
+
argumentError = WORK_TOOL_ARGUMENTS_ERROR_MESSAGE;
|
|
1019
|
+
}
|
|
744
1020
|
}
|
|
745
1021
|
catch {
|
|
746
1022
|
args = {};
|
|
1023
|
+
if (fn.arguments.trim()) {
|
|
1024
|
+
argumentError = WORK_TOOL_ARGUMENTS_ERROR_MESSAGE;
|
|
1025
|
+
}
|
|
747
1026
|
}
|
|
748
1027
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1028
|
+
const providerMetadata = record.providerMetadata &&
|
|
1029
|
+
typeof record.providerMetadata === 'object' &&
|
|
1030
|
+
!Array.isArray(record.providerMetadata)
|
|
1031
|
+
? {
|
|
1032
|
+
...record.providerMetadata,
|
|
1033
|
+
}
|
|
1034
|
+
: {};
|
|
1035
|
+
if (argumentError) {
|
|
1036
|
+
providerMetadata[WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY] = argumentError;
|
|
1037
|
+
}
|
|
1038
|
+
const explicitId = typeof record.id === 'string' ? record.id : undefined;
|
|
1039
|
+
if (explicitId !== undefined && !explicitId.trim()) {
|
|
1040
|
+
throw new WorkAgentHttpError('The provider returned a tool call with an empty ID.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
1041
|
+
}
|
|
1042
|
+
if (isResponsesCallBatch && explicitId === undefined) {
|
|
1043
|
+
throw new WorkAgentHttpError('The Responses provider returned a tool call without an exact call_id.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
1044
|
+
}
|
|
1045
|
+
calls.push({
|
|
1046
|
+
id: explicitId ?? `tool-${Date.now()}-${index}`,
|
|
1047
|
+
...(typeof record.thoughtSignature === 'string'
|
|
1048
|
+
? { thoughtSignature: record.thoughtSignature }
|
|
1049
|
+
: {}),
|
|
1050
|
+
...(Object.keys(providerMetadata).length > 0 ? { providerMetadata } : {}),
|
|
1051
|
+
function: { name: fn.name, arguments: args },
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
if (calls.length > 16) {
|
|
1055
|
+
throw new WorkAgentHttpError('The provider returned more than 16 tool calls in one response.', 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
1056
|
+
}
|
|
1057
|
+
const callIds = new Set();
|
|
1058
|
+
for (const call of calls) {
|
|
1059
|
+
if (callIds.has(call.id)) {
|
|
1060
|
+
throw new WorkAgentHttpError(`The provider returned duplicate tool call ID "${call.id}".`, 502, 'WORK_PROVIDER_INVALID_TOOL_CALLS');
|
|
1061
|
+
}
|
|
1062
|
+
callIds.add(call.id);
|
|
1063
|
+
}
|
|
1064
|
+
return calls;
|
|
769
1065
|
}
|
|
770
1066
|
function functionTool(name, description, properties, required = []) {
|
|
771
1067
|
return {
|
|
@@ -798,6 +1094,36 @@ function optionalInteger(value) {
|
|
|
798
1094
|
? value
|
|
799
1095
|
: undefined;
|
|
800
1096
|
}
|
|
1097
|
+
function validateToolCallArguments(call) {
|
|
1098
|
+
const providerArgumentError = call.providerMetadata?.[WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY];
|
|
1099
|
+
if (typeof providerArgumentError === 'string') {
|
|
1100
|
+
return providerArgumentError;
|
|
1101
|
+
}
|
|
1102
|
+
const args = call.function.arguments;
|
|
1103
|
+
const invalidRequiredString = (name, allowEmpty = false) => {
|
|
1104
|
+
const value = args[name];
|
|
1105
|
+
if (typeof value === 'string' && (allowEmpty || value.trim())) {
|
|
1106
|
+
return undefined;
|
|
1107
|
+
}
|
|
1108
|
+
return `The model returned invalid arguments for ${call.function.name}: "${name}" must be a string. Retry with every required field; split large write_file content into focused files.`;
|
|
1109
|
+
};
|
|
1110
|
+
switch (call.function.name) {
|
|
1111
|
+
case 'list_files':
|
|
1112
|
+
case 'start_preview':
|
|
1113
|
+
case 'stop_preview':
|
|
1114
|
+
return undefined;
|
|
1115
|
+
case 'read_file':
|
|
1116
|
+
return invalidRequiredString('path');
|
|
1117
|
+
case 'write_file':
|
|
1118
|
+
return (invalidRequiredString('path') || invalidRequiredString('content', true));
|
|
1119
|
+
case 'search_files':
|
|
1120
|
+
return invalidRequiredString('query');
|
|
1121
|
+
case 'run_command':
|
|
1122
|
+
return invalidRequiredString('command');
|
|
1123
|
+
default:
|
|
1124
|
+
return `The model requested an unknown tool: ${call.function.name}.`;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
801
1127
|
function isActiveRunStatus(status) {
|
|
802
1128
|
return status === 'queued' || status === 'preparing' || status === 'running';
|
|
803
1129
|
}
|