From 68ad6d461dcb1fe1a1ac4de8babf967807e19df3 Mon Sep 17 00:00:00 2001 From: MQ37 Date: Tue, 30 Sep 2025 14:07:31 +0200 Subject: [PATCH 1/2] fix: deep clone tools before modifying descriptions for skyfire to prevent state incostentcy --- src/utils/tools-loader.ts | 37 +++++++++++++++++++++++++++++++++++-- tests/integration/suite.ts | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/utils/tools-loader.ts b/src/utils/tools-loader.ts index f1ee806d..fb09e685 100644 --- a/src/utils/tools-loader.ts +++ b/src/utils/tools-loader.ts @@ -12,8 +12,9 @@ import { callActor } from '../tools/actor.js'; import { getActorOutput } from '../tools/get-actor-output.js'; import { addTool } from '../tools/helpers.js'; import { getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from '../tools/index.js'; -import type { Input, ToolCategory, ToolEntry } from '../types.js'; +import type { Input, InternalTool, InternalToolArgs, ToolCategory, ToolEntry } from '../types.js'; import { getExpectedToolsByCategories } from './tools.js'; +import { ValidateFunction } from 'ajv'; // Lazily-computed cache of internal tools by name to avoid circular init issues. let INTERNAL_TOOL_BY_NAME_CACHE: Map | null = null; @@ -139,5 +140,37 @@ export async function loadToolsFromInput( // De-duplicate by tool name for safety const seen = new Set(); - return result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name)); + const filtered = result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name)); + + // TODO: rework this solition as it was quickly hacked together for hotfix + // Deep clone except ajvValidate and call functions + + // holds the original functions of the tools + const toolFunctions = new Map; call?: (args: InternalToolArgs) => Promise }>(); + for (const entry of filtered) { + if (entry.type === 'internal') { + toolFunctions.set(entry.tool.name, { ajvValidate: entry.tool.ajvValidate, call: (entry.tool as InternalTool).call }); + } else { + toolFunctions.set(entry.tool.name, { ajvValidate: entry.tool.ajvValidate }); + } + } + + const cloned = JSON.parse(JSON.stringify(filtered, (key, value) => { + if (key === 'ajvValidate' || key === 'call') return undefined; + return value; + })) as ToolEntry[]; + + // restore the original functions + for (const entry of cloned) { + const funcs = toolFunctions.get(entry.tool.name); + if (funcs) { + if (funcs.ajvValidate) { + entry.tool.ajvValidate = funcs.ajvValidate; + } + if (entry.type === 'internal' && funcs.call) { + (entry.tool as InternalTool).call = funcs.call; + } + } + } + return cloned; } diff --git a/tests/integration/suite.ts b/tests/integration/suite.ts index 4e92eb5a..76fddb90 100644 --- a/tests/integration/suite.ts +++ b/tests/integration/suite.ts @@ -370,7 +370,7 @@ export function createIntegrationTestsSuite( expect(infoResult.content).toBeDefined(); const content = infoResult.content as { text: string }[]; - expect(content.some((item) => item.text.includes('Input Schema'))).toBe(true); + expect(content.some((item) => item.text.includes('Input schema'))).toBe(true); // Step 2: Call with proper input (should work) const callResult = await client.callTool({ From bb267e3b9337fb3c3e9925cc7332f4c72eb67270 Mon Sep 17 00:00:00 2001 From: MQ37 Date: Tue, 30 Sep 2025 14:08:47 +0200 Subject: [PATCH 2/2] lint --- src/utils/tools-loader.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/tools-loader.ts b/src/utils/tools-loader.ts index fb09e685..5e7fb1a8 100644 --- a/src/utils/tools-loader.ts +++ b/src/utils/tools-loader.ts @@ -3,6 +3,7 @@ * This eliminates duplication between stdio.ts and processParamsGetTools. */ +import type { ValidateFunction } from 'ajv'; import type { ApifyClient } from 'apify'; import log from '@apify/log'; @@ -14,7 +15,6 @@ import { addTool } from '../tools/helpers.js'; import { getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from '../tools/index.js'; import type { Input, InternalTool, InternalToolArgs, ToolCategory, ToolEntry } from '../types.js'; import { getExpectedToolsByCategories } from './tools.js'; -import { ValidateFunction } from 'ajv'; // Lazily-computed cache of internal tools by name to avoid circular init issues. let INTERNAL_TOOL_BY_NAME_CACHE: Map | null = null; @@ -141,12 +141,12 @@ export async function loadToolsFromInput( // De-duplicate by tool name for safety const seen = new Set(); const filtered = result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name)); - + // TODO: rework this solition as it was quickly hacked together for hotfix // Deep clone except ajvValidate and call functions - + // holds the original functions of the tools - const toolFunctions = new Map; call?: (args: InternalToolArgs) => Promise }>(); + const toolFunctions = new Map; call?:(args: InternalToolArgs) => Promise }>(); for (const entry of filtered) { if (entry.type === 'internal') { toolFunctions.set(entry.tool.name, { ajvValidate: entry.tool.ajvValidate, call: (entry.tool as InternalTool).call }); @@ -159,7 +159,7 @@ export async function loadToolsFromInput( if (key === 'ajvValidate' || key === 'call') return undefined; return value; })) as ToolEntry[]; - + // restore the original functions for (const entry of cloned) { const funcs = toolFunctions.get(entry.tool.name);