Enhance ToolLibrary by adding sourceKey to ToolParameterDraft and updating related functions for improved schema management. Introduce normalization functions for object schemas and defaults, and refactor buildToolParameterConfig to utilize these enhancements. Update state management in ToolLibraryPage to accommodate new schema handling and defaults integration.

This commit is contained in:
Xin Wang
2026-03-02 02:18:28 +08:00
parent 3f22e2b875
commit 31b3969b96

View File

@@ -24,6 +24,7 @@ type ToolParameterType = 'string' | 'number' | 'integer' | 'boolean' | 'object'
type ToolParameterDraft = {
id: string;
key: string;
sourceKey: string;
type: ToolParameterType;
required: boolean;
description: string;
@@ -36,6 +37,7 @@ const TOOL_PARAMETER_TYPES: ToolParameterType[] = ['string', 'number', 'integer'
const newParameterDraft = (): ToolParameterDraft => ({
id: `param_${Date.now()}_${Math.random().toString(16).slice(2, 8)}`,
key: '',
sourceKey: '',
type: 'string',
required: false,
description: '',
@@ -106,6 +108,7 @@ const draftsFromSchema = (schema: Record<string, any> | undefined, defaults: Rec
return {
id: `param_${key}_${Math.random().toString(16).slice(2, 8)}`,
key,
sourceKey: key,
type: schemaTypeOrDefault(typedSpec.type),
required: requiredSet.has(key),
description: String(typedSpec.description || ''),
@@ -119,6 +122,7 @@ const draftsFromSchema = (schema: Record<string, any> | undefined, defaults: Rec
drafts.push({
id: `param_${key}_${Math.random().toString(16).slice(2, 8)}`,
key,
sourceKey: key,
type: typeof value === 'number' && Number.isInteger(value) ? 'integer' : (typeof value as ToolParameterType) || 'string',
required: false,
description: '',
@@ -129,7 +133,31 @@ const draftsFromSchema = (schema: Record<string, any> | undefined, defaults: Rec
return drafts;
};
const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Record<string, any>; defaults: Record<string, any>; error?: string } => {
const normalizeObjectSchema = (value: any): Record<string, any> => {
const schema = value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};
const properties = schema.properties && typeof schema.properties === 'object' && !Array.isArray(schema.properties)
? schema.properties
: {};
const required = Array.isArray(schema.required) ? schema.required.map((item: any) => String(item)) : [];
return {
...schema,
type: 'object',
properties,
required,
};
};
const normalizeDefaultsObject = (value: any): Record<string, any> =>
value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};
const buildToolParameterConfig = (
drafts: ToolParameterDraft[],
baseSchemaInput?: Record<string, any>
): { schema: Record<string, any>; defaults: Record<string, any>; error?: string } => {
const baseSchema = normalizeObjectSchema(baseSchemaInput);
const baseProperties = baseSchema.properties && typeof baseSchema.properties === 'object' && !Array.isArray(baseSchema.properties)
? (baseSchema.properties as Record<string, any>)
: {};
const properties: Record<string, any> = {};
const required: string[] = [];
const defaults: Record<string, any> = {};
@@ -138,18 +166,26 @@ const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Recor
for (const draft of drafts) {
const key = draft.key.trim();
if (!key) continue;
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
return { schema: {}, defaults: {}, error: `参数标识不合法: ${key}` };
}
if (seen.has(key)) {
return { schema: {}, defaults: {}, error: `参数标识重复: ${key}` };
}
seen.add(key);
properties[key] = {
type: draft.type,
...(draft.description.trim() ? { description: draft.description.trim() } : {}),
};
const sourceKey = String(draft.sourceKey || '').trim();
const preservedRaw = (
(sourceKey && Object.prototype.hasOwnProperty.call(baseProperties, sourceKey) ? baseProperties[sourceKey] : undefined)
?? baseProperties[key]
);
const preservedSpec = preservedRaw && typeof preservedRaw === 'object' && !Array.isArray(preservedRaw)
? { ...(preservedRaw as Record<string, any>) }
: {};
properties[key] = preservedSpec;
properties[key].type = draft.type;
if (draft.description.trim()) {
properties[key].description = draft.description.trim();
} else {
delete properties[key].description;
}
if (draft.required) required.push(key);
if (draft.hasDefault) {
const parsed = parseDefaultValueByType(draft.type, draft.defaultValueText);
@@ -157,11 +193,15 @@ const buildToolParameterConfig = (drafts: ToolParameterDraft[]): { schema: Recor
return { schema: {}, defaults: {}, error: parsed.error };
}
defaults[key] = parsed.value;
properties[key].default = parsed.value;
} else {
delete properties[key].default;
}
}
return {
schema: {
...baseSchema,
type: 'object',
properties,
required,
@@ -202,6 +242,8 @@ export const ToolLibraryPage: React.FC = () => {
const [toolHttpUrl, setToolHttpUrl] = useState('');
const [toolHttpHeadersText, setToolHttpHeadersText] = useState('{}');
const [toolHttpTimeoutMs, setToolHttpTimeoutMs] = useState(10000);
const [toolSchema, setToolSchema] = useState<Record<string, any>>({ type: 'object', properties: {}, required: [] });
const [toolParameterDefaults, setToolParameterDefaults] = useState<Record<string, any>>({});
const [toolParameters, setToolParameters] = useState<ToolParameterDraft[]>([]);
const [schemaDrawerOpen, setSchemaDrawerOpen] = useState(false);
const [schemaEditorText, setSchemaEditorText] = useState('');
@@ -237,9 +279,12 @@ export const ToolLibraryPage: React.FC = () => {
setToolHttpUrl('');
setToolHttpHeadersText('{}');
setToolHttpTimeoutMs(10000);
const emptySchema = { type: 'object', properties: {}, required: [] };
setToolSchema(emptySchema);
setToolParameterDefaults({});
setToolParameters([]);
setSchemaEditorError('');
setSchemaEditorText(JSON.stringify({ type: 'object', properties: {}, required: [] }, null, 2));
setSchemaEditorText(JSON.stringify(emptySchema, null, 2));
setSchemaDrawerOpen(false);
setIsToolModalOpen(true);
};
@@ -257,13 +302,18 @@ export const ToolLibraryPage: React.FC = () => {
setToolHttpUrl(tool.httpUrl || '');
setToolHttpHeadersText(JSON.stringify(tool.httpHeaders || {}, null, 2));
setToolHttpTimeoutMs(tool.httpTimeoutMs || 10000);
setToolParameters(draftsFromSchema(tool.parameterSchema, tool.parameterDefaults));
const normalizedSchema = normalizeObjectSchema(tool.parameterSchema);
const mergedDefaults = {
...defaultsFromSchema(normalizedSchema),
...normalizeDefaultsObject(tool.parameterDefaults),
};
setToolSchema(normalizedSchema);
setToolParameterDefaults(mergedDefaults);
setToolParameters(draftsFromSchema(normalizedSchema, mergedDefaults));
setSchemaEditorError('');
setSchemaEditorText(
JSON.stringify(
(tool.parameterSchema && typeof tool.parameterSchema === 'object')
? tool.parameterSchema
: { type: 'object', properties: {}, required: [] },
normalizedSchema,
null,
2
)
@@ -273,11 +323,14 @@ export const ToolLibraryPage: React.FC = () => {
};
const openSchemaDrawer = () => {
const parameterConfig = buildToolParameterConfig(toolParameters);
const parameterConfig = buildToolParameterConfig(toolParameters, toolSchema);
if (!parameterConfig.error) {
setToolSchema(parameterConfig.schema);
setToolParameterDefaults(parameterConfig.defaults);
setToolParameters((prev) => prev.map((item) => ({ ...item, sourceKey: item.key.trim() || item.sourceKey })));
setSchemaEditorText(JSON.stringify(parameterConfig.schema, null, 2));
} else if (editingTool?.parameterSchema && typeof editingTool.parameterSchema === 'object') {
setSchemaEditorText(JSON.stringify(editingTool.parameterSchema, null, 2));
} else if (toolSchema && typeof toolSchema === 'object') {
setSchemaEditorText(JSON.stringify(toolSchema, null, 2));
} else if (!schemaEditorText.trim()) {
setSchemaEditorText(JSON.stringify({ type: 'object', properties: {}, required: [] }, null, 2));
}
@@ -325,19 +378,11 @@ export const ToolLibraryPage: React.FC = () => {
return;
}
const normalizedSchema: Record<string, any> = {
...parsedSchema,
type: 'object',
properties: parsedSchema.properties && typeof parsedSchema.properties === 'object' && !Array.isArray(parsedSchema.properties)
? parsedSchema.properties
: {},
required: Array.isArray(parsedSchema.required) ? parsedSchema.required : [],
};
const currentParameterConfig = buildToolParameterConfig(toolParameters);
const currentDefaults = currentParameterConfig.error ? {} : currentParameterConfig.defaults;
const mergedDefaults = { ...currentDefaults, ...defaultsFromSchema(normalizedSchema) };
const normalizedSchema = normalizeObjectSchema(parsedSchema);
const mergedDefaults = { ...normalizeDefaultsObject(toolParameterDefaults), ...defaultsFromSchema(normalizedSchema) };
setToolSchema(normalizedSchema);
setToolParameterDefaults(mergedDefaults);
setToolParameters(draftsFromSchema(normalizedSchema, mergedDefaults));
setSchemaEditorError('');
setSchemaEditorText(JSON.stringify(normalizedSchema, null, 2));
@@ -439,7 +484,7 @@ export const ToolLibraryPage: React.FC = () => {
try {
setSaving(true);
let parsedHeaders: Record<string, string> = {};
const parameterConfig = buildToolParameterConfig(toolParameters);
const parameterConfig = buildToolParameterConfig(toolParameters, toolSchema);
if (parameterConfig.error) {
alert(parameterConfig.error);
setSaving(false);
@@ -447,6 +492,10 @@ export const ToolLibraryPage: React.FC = () => {
}
const parsedParameterSchema = parameterConfig.schema;
const parsedParameterDefaults = parameterConfig.defaults;
setToolSchema(parsedParameterSchema);
setToolParameterDefaults(parsedParameterDefaults);
setToolParameters((prev) => prev.map((item) => ({ ...item, sourceKey: item.key.trim() || item.sourceKey })));
setSchemaEditorText(JSON.stringify(parsedParameterSchema, null, 2));
if (toolCategory === 'query') {
if (
@@ -807,7 +856,7 @@ export const ToolLibraryPage: React.FC = () => {
</div>
)}
<p className="text-[11px] text-muted-foreground">
Default Args
JSON Schema items/anyOf/minItems Schema
</p>
</div>