Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 02945f9

Browse files
authored
fix: deep clone tools before modifying descriptions for skyfire to prevent state incostentcy (#297)
* fix: deep clone tools before modifying descriptions for skyfire to prevent state incostentcy * lint
1 parent 86077f0 commit 02945f9

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

src/utils/tools-loader.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* This eliminates duplication between stdio.ts and processParamsGetTools.
44
*/
55

6+
import type { ValidateFunction } from 'ajv';
67
import type { ApifyClient } from 'apify';
78

89
import log from '@apify/log';
@@ -12,7 +13,7 @@ import { callActor } from '../tools/actor.js';
1213
import { getActorOutput } from '../tools/get-actor-output.js';
1314
import { addTool } from '../tools/helpers.js';
1415
import { getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from '../tools/index.js';
15-
import type { Input, ToolCategory, ToolEntry } from '../types.js';
16+
import type { Input, InternalTool, InternalToolArgs, ToolCategory, ToolEntry } from '../types.js';
1617
import { getExpectedToolsByCategories } from './tools.js';
1718

1819
// Lazily-computed cache of internal tools by name to avoid circular init issues.
@@ -139,5 +140,37 @@ export async function loadToolsFromInput(
139140

140141
// De-duplicate by tool name for safety
141142
const seen = new Set<string>();
142-
return result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name));
143+
const filtered = result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name));
144+
145+
// TODO: rework this solition as it was quickly hacked together for hotfix
146+
// Deep clone except ajvValidate and call functions
147+
148+
// holds the original functions of the tools
149+
const toolFunctions = new Map<string, { ajvValidate?: ValidateFunction<unknown>; call?:(args: InternalToolArgs) => Promise<object> }>();
150+
for (const entry of filtered) {
151+
if (entry.type === 'internal') {
152+
toolFunctions.set(entry.tool.name, { ajvValidate: entry.tool.ajvValidate, call: (entry.tool as InternalTool).call });
153+
} else {
154+
toolFunctions.set(entry.tool.name, { ajvValidate: entry.tool.ajvValidate });
155+
}
156+
}
157+
158+
const cloned = JSON.parse(JSON.stringify(filtered, (key, value) => {
159+
if (key === 'ajvValidate' || key === 'call') return undefined;
160+
return value;
161+
})) as ToolEntry[];
162+
163+
// restore the original functions
164+
for (const entry of cloned) {
165+
const funcs = toolFunctions.get(entry.tool.name);
166+
if (funcs) {
167+
if (funcs.ajvValidate) {
168+
entry.tool.ajvValidate = funcs.ajvValidate;
169+
}
170+
if (entry.type === 'internal' && funcs.call) {
171+
(entry.tool as InternalTool).call = funcs.call;
172+
}
173+
}
174+
}
175+
return cloned;
143176
}

tests/integration/suite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ export function createIntegrationTestsSuite(
370370

371371
expect(infoResult.content).toBeDefined();
372372
const content = infoResult.content as { text: string }[];
373-
expect(content.some((item) => item.text.includes('Input Schema'))).toBe(true);
373+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(true);
374374

375375
// Step 2: Call with proper input (should work)
376376
const callResult = await client.callTool({

0 commit comments

Comments
 (0)