Commit 9c60f089 by Finley Ge Committed by GitHub

fix(system-tool): omit null json schema fields (#7239)

parent 85fbe9dc
......@@ -5,6 +5,16 @@ import { PluginPermissionEnumSchema } from '../../../../../sdk/fastgpt-plugin';
import { SystemToolSystemSecretStatusEnum } from '../constants';
import { JSONSchemaInputTypeSchema, JSONSchemaOutputTypeSchema } from '../../../jsonschema';
const OptionalJSONSchemaInputTypeSchema = z.preprocess(
(value) => (value === null ? undefined : value),
JSONSchemaInputTypeSchema.optional()
);
const OptionalJSONSchemaOutputTypeSchema = z.preprocess(
(value) => (value === null ? undefined : value),
JSONSchemaOutputTypeSchema.optional()
);
// 系统工具最基础最通用的类型
export const SystemToolBaseSchema = z.object({
id: z.string(),
......@@ -74,8 +84,8 @@ export const SystemToolChildDetailSchema = z.object({
icon: z.string().optional(),
currentCost: z.number().meta({ description: '当前使用的费用' }),
systemKeyCost: z.number().meta({ description: '系统密钥的费用' }),
inputSchema: JSONSchemaInputTypeSchema.optional(),
outputSchema: JSONSchemaOutputTypeSchema.optional()
inputSchema: OptionalJSONSchemaInputTypeSchema,
outputSchema: OptionalJSONSchemaOutputTypeSchema
});
export type SystemToolChildDetailType = z.infer<typeof SystemToolChildDetailSchema>;
......@@ -85,9 +95,9 @@ export const SystemToolDetailSchema = z.object({
...SystemToolListItemSchema.shape,
children: z.array(SystemToolChildDetailSchema).optional(),
inputSchema: JSONSchemaInputTypeSchema.optional(),
outputSchema: JSONSchemaOutputTypeSchema.optional(),
secretSchema: JSONSchemaInputTypeSchema.optional(),
inputSchema: OptionalJSONSchemaInputTypeSchema,
outputSchema: OptionalJSONSchemaOutputTypeSchema,
secretSchema: OptionalJSONSchemaInputTypeSchema,
secretsVal: z.record(z.string(), z.any()).nullish(),
isLatestVersion: z.boolean().optional(),
associatedPluginId: z.string().optional(),
......
import { describe, expect, it } from 'vitest';
import { AdminSystemToolDetailSchema } from '@fastgpt/global/core/app/tool/systemTool/type';
import {
AdminSystemToolDetailSchema,
SystemToolDetailSchema
} from '@fastgpt/global/core/app/tool/systemTool/type';
import { PluginStatusEnum } from '@fastgpt/global/core/plugin/type';
import { SystemToolSystemSecretStatusEnum } from '@fastgpt/global/core/app/tool/systemTool/constants';
import { TeamToolDetailSchema } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
const createAdminToolDetail = () => ({
id: 'systemTool-null-schema',
......@@ -67,3 +71,44 @@ describe('AdminSystemToolDetailSchema', () => {
expect(result.children?.[0]).not.toHaveProperty('outputSchema');
});
});
describe('SystemToolDetailSchema', () => {
it('treats null input and output schemas as missing schema fields', () => {
const result = SystemToolDetailSchema.parse({
...createAdminToolDetail(),
inputSchema: null,
outputSchema: null,
secretSchema: null
});
expect(result.inputSchema).toBeUndefined();
expect(result.outputSchema).toBeUndefined();
expect(result.secretSchema).toBeUndefined();
});
});
describe('TeamToolDetailSchema', () => {
it('accepts null tool schemas from legacy plugin definitions', () => {
const result = TeamToolDetailSchema.parse({
...createAdminToolDetail(),
inputSchema: null,
outputSchema: null,
children: [
{
id: 'child',
name: 'Child tool',
status: PluginStatusEnum.Normal,
currentCost: 0,
systemKeyCost: 0,
inputSchema: null,
outputSchema: null
}
]
});
expect(result.inputSchema).toBeUndefined();
expect(result.outputSchema).toBeUndefined();
expect(result.children?.[0].inputSchema).toBeUndefined();
expect(result.children?.[0].outputSchema).toBeUndefined();
});
});
......@@ -128,6 +128,8 @@ const getPluginClientSource = ({
return runtimeSource || 'system';
};
const normalizeOptionalJsonSchema = <T>(schema: T | null | undefined) => schema ?? undefined;
const getSystemToolConfigIds = (pluginId: string) => {
const systemToolPrefix = `${AppToolSourceEnum.systemTool}-`;
const commercialPrefix = `${AppToolSourceEnum.commercial}-`;
......@@ -421,6 +423,9 @@ export class SystemToolRepo {
const children = tool.isToolset
? tool.children?.map((item) => {
const dbChild = getFirstSystemToolConfig(dbChildrenMap, `${pluginId}/${item.id}`);
const inputSchema = normalizeOptionalJsonSchema(item.inputSchema);
const outputSchema = normalizeOptionalJsonSchema(item.outputSchema);
return {
id: item.id,
name: parseI18nString(item.name, lang),
......@@ -432,13 +437,20 @@ export class SystemToolRepo {
systemKeyCost: dbChild?.systemKeyCost ?? 0,
currentCost: dbChild?.currentCost ?? 0,
icon: item.icon,
inputSchema: item.inputSchema,
outputSchema: item.outputSchema
...(inputSchema !== undefined ? { inputSchema } : {}),
...(outputSchema !== undefined ? { outputSchema } : {})
} satisfies SystemToolChildDetailType;
})
: undefined;
const secrets = jsonSchema2SecretInput({ jsonSchema: tool.secretSchema });
const secretSchema = normalizeOptionalJsonSchema(tool.secretSchema);
const inputSchema = normalizeOptionalJsonSchema(
childPluginId ? child!.inputSchema : tool.inputSchema
);
const outputSchema = normalizeOptionalJsonSchema(
childPluginId ? child!.outputSchema : tool.outputSchema
);
const secrets = jsonSchema2SecretInput({ jsonSchema: secretSchema });
const configuredSecretsVal = SystemToolCodec.getConfiguredSecretsVal(dbTool);
const hasSystemSecret = !!configuredSecretsVal;
......@@ -483,17 +495,10 @@ export class SystemToolRepo {
hideTags: dbTool?.hideTags ?? [],
promoteTags: dbTool?.promoteTags ?? [],
pluginOrder: dbTool?.pluginOrder,
secretSchema: tool.secretSchema,
...(secretSchema !== undefined ? { secretSchema } : {}),
isLatestVersion: tool.isLatestVersion,
...(childPluginId
? {
inputSchema: child!.inputSchema,
outputSchema: child!.outputSchema
}
: {
inputSchema: tool.inputSchema,
outputSchema: tool.outputSchema
}),
...(inputSchema !== undefined ? { inputSchema } : {}),
...(outputSchema !== undefined ? { outputSchema } : {}),
permissions: tool.permission
};
......
......@@ -408,6 +408,52 @@ describe('SystemToolRepo.getSystemToolDetail', () => {
expect(tool).not.toHaveProperty('outputs');
expect(tool).not.toHaveProperty('secrets');
});
it('omits null schemas returned by plugin client', async () => {
mocks.findSystemTool.mockResolvedValue({
pluginId: 'systemTool-perplexity',
status: 'Normal',
currentCost: 0,
hasTokenFee: false,
systemKeyCost: 0,
customConfig: {}
});
mocks.findSystemTools.mockResolvedValue([]);
mocks.getTool.mockResolvedValue({
source: 'system',
isToolset: true,
name: { en: 'Perplexity' },
description: { en: 'Perplexity intro' },
pluginId: 'perplexity',
version: '0.0.1',
icon: 'perplexity.svg',
tags: [],
toolDescription: 'Perplexity tool',
inputSchema: null,
outputSchema: null,
secretSchema: null,
children: [
{
id: 'search',
name: { en: 'Search' },
description: { en: 'Search intro' },
toolDescription: 'Search tool',
inputSchema: null,
outputSchema: null
}
]
});
const tool = await SystemToolRepo.getInstance().getSystemToolDetail({
pluginId: 'systemTool-perplexity'
});
expect(tool).not.toHaveProperty('inputSchema');
expect(tool).not.toHaveProperty('outputSchema');
expect(tool).not.toHaveProperty('secretSchema');
expect(tool.children?.[0]).not.toHaveProperty('inputSchema');
expect(tool.children?.[0]).not.toHaveProperty('outputSchema');
});
});
describe('SystemToolRepo.getSystemToolDisplayInfo', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment