Skip to content

Commit 00153ea

Browse files
committed
fix(plugin): normalize tool param metadata in tool detail
1 parent 5f44741 commit 00153ea

2 files changed

Lines changed: 169 additions & 0 deletions

File tree

packages/infrastructure/src/plugin/tool.impl.test.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,102 @@ describe('ToolManager.detail', () => {
113113
version: '1.10.0'
114114
});
115115
});
116+
117+
it('normalizes input schema tool parameter metadata in tool detail', async () => {
118+
const toolManager = createToolManager();
119+
listVersions.mockResolvedValue(successResult([{ version: '1.0.0' }]));
120+
getPluginByUserPluginId.mockResolvedValue(
121+
successResult({
122+
...makeTool('1.0.0'),
123+
inputSchema: {
124+
type: 'object',
125+
properties: {
126+
withToolDescription: {
127+
type: 'string',
128+
toolDescription: 'Existing model-facing description'
129+
},
130+
manualByDefault: {
131+
type: 'string',
132+
description: {
133+
en: 'Fallback manual description',
134+
'zh-CN': '手动参数说明'
135+
}
136+
},
137+
explicitFalse: {
138+
type: 'string',
139+
description: {
140+
en: 'Explicit false fallback'
141+
},
142+
isToolParams: false
143+
},
144+
explicitTrue: {
145+
type: 'string',
146+
description: {
147+
en: 'Explicit true fallback'
148+
},
149+
isToolParams: true
150+
}
151+
}
152+
},
153+
children: [
154+
{
155+
id: 'child',
156+
name: { en: 'Child' },
157+
description: { en: 'Child tool' },
158+
icon: 'https://example.com/child.svg',
159+
toolDescription: 'Child tool',
160+
inputSchema: {
161+
type: 'object',
162+
properties: {
163+
childParam: {
164+
type: 'string',
165+
description: {
166+
en: 'Child fallback'
167+
}
168+
}
169+
}
170+
},
171+
outputSchema: {
172+
type: 'object'
173+
}
174+
}
175+
]
176+
} satisfies ToolType)
177+
);
178+
179+
const [result, err] = await toolManager.detail({
180+
pluginId: 'getTime',
181+
source: 'system',
182+
version: '1.0.0'
183+
});
184+
185+
const properties = (result?.inputSchema as { properties: Record<string, unknown> }).properties;
186+
const childProperties = (result?.children?.[0]?.inputSchema as {
187+
properties: Record<string, unknown>;
188+
}).properties;
189+
190+
expect(err).toBeNull();
191+
expect(properties.withToolDescription).toMatchObject({
192+
toolDescription: 'Existing model-facing description',
193+
isToolParams: true
194+
});
195+
expect(properties.manualByDefault).toMatchObject({
196+
toolDescription: 'Fallback manual description',
197+
isToolParams: false
198+
});
199+
expect(properties.explicitFalse).toMatchObject({
200+
toolDescription: 'Explicit false fallback',
201+
isToolParams: false
202+
});
203+
expect(properties.explicitTrue).toMatchObject({
204+
toolDescription: 'Explicit true fallback',
205+
isToolParams: true
206+
});
207+
expect(childProperties.childParam).toMatchObject({
208+
toolDescription: 'Child fallback',
209+
isToolParams: false
210+
});
211+
});
116212
});
117213

118214
describe('ToolManager.run', () => {

packages/infrastructure/src/plugin/tool.impl.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { ErrorCode } from '@infrastructure/errors/error.registry';
2828
import { InvokeManager } from './invoke/invoke.impl';
2929
import { Semver } from './utils/semver';
3030

31+
type JsonObject = Record<string, unknown>;
32+
3133
export type ToolManagerDeps = {
3234
pluginRepo: PluginRepoPort;
3335
pluginRuntimeManager: PluginRuntimeManagerPort;
@@ -41,6 +43,72 @@ export type PluginToolRunPayloadType = {
4143
childId?: string;
4244
};
4345

46+
function isJsonObject(value: unknown): value is JsonObject {
47+
return typeof value === 'object' && value !== null && !Array.isArray(value);
48+
}
49+
50+
function getDescriptionFallback(description: unknown): string | undefined {
51+
if (typeof description === 'string' && description.length > 0) {
52+
return description;
53+
}
54+
55+
if (!isJsonObject(description)) {
56+
return undefined;
57+
}
58+
59+
const en = description.en;
60+
return typeof en === 'string' && en.length > 0 ? en : undefined;
61+
}
62+
63+
function normalizeInputSchemaToolParams(inputSchema: unknown): unknown {
64+
if (!isJsonObject(inputSchema) || !isJsonObject(inputSchema.properties)) {
65+
return inputSchema;
66+
}
67+
68+
const properties = Object.fromEntries(
69+
Object.entries(inputSchema.properties).map(([key, property]) => {
70+
if (!isJsonObject(property)) {
71+
return [key, property];
72+
}
73+
74+
const normalizedProperty = { ...property };
75+
const hasToolDescription =
76+
typeof normalizedProperty.toolDescription === 'string' &&
77+
normalizedProperty.toolDescription.length > 0;
78+
const hasIsToolParams = normalizedProperty.isToolParams !== undefined;
79+
80+
if (hasIsToolParams) {
81+
if (!hasToolDescription) {
82+
const fallback = getDescriptionFallback(normalizedProperty.description);
83+
if (fallback !== undefined) {
84+
normalizedProperty.toolDescription = fallback;
85+
}
86+
}
87+
88+
return [key, normalizedProperty];
89+
}
90+
91+
if (hasToolDescription) {
92+
normalizedProperty.isToolParams = true;
93+
return [key, normalizedProperty];
94+
}
95+
96+
const fallback = getDescriptionFallback(normalizedProperty.description);
97+
if (fallback !== undefined) {
98+
normalizedProperty.toolDescription = fallback;
99+
}
100+
normalizedProperty.isToolParams = false;
101+
102+
return [key, normalizedProperty];
103+
})
104+
);
105+
106+
return {
107+
...inputSchema,
108+
properties
109+
};
110+
}
111+
44112
export class ToolManager implements ToolManagerPort {
45113
private static instance: ToolManager;
46114
private constructor(private deps: ToolManagerDeps) {}
@@ -61,6 +129,11 @@ export class ToolManager implements ToolManagerPort {
61129
}): ToolDetailType {
62130
return {
63131
...tool,
132+
inputSchema: normalizeInputSchemaToolParams(tool.inputSchema),
133+
children: tool.children?.map((child) => ({
134+
...child,
135+
inputSchema: normalizeInputSchemaToolParams(child.inputSchema)
136+
})),
64137
source,
65138
isToolset: Boolean(tool.children?.length),
66139
isLatestVersion

0 commit comments

Comments
 (0)