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'; ...@@ -5,6 +5,16 @@ import { PluginPermissionEnumSchema } from '../../../../../sdk/fastgpt-plugin';
import { SystemToolSystemSecretStatusEnum } from '../constants'; import { SystemToolSystemSecretStatusEnum } from '../constants';
import { JSONSchemaInputTypeSchema, JSONSchemaOutputTypeSchema } from '../../../jsonschema'; 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({ export const SystemToolBaseSchema = z.object({
id: z.string(), id: z.string(),
...@@ -74,8 +84,8 @@ export const SystemToolChildDetailSchema = z.object({ ...@@ -74,8 +84,8 @@ export const SystemToolChildDetailSchema = z.object({
icon: z.string().optional(), icon: z.string().optional(),
currentCost: z.number().meta({ description: '当前使用的费用' }), currentCost: z.number().meta({ description: '当前使用的费用' }),
systemKeyCost: z.number().meta({ description: '系统密钥的费用' }), systemKeyCost: z.number().meta({ description: '系统密钥的费用' }),
inputSchema: JSONSchemaInputTypeSchema.optional(), inputSchema: OptionalJSONSchemaInputTypeSchema,
outputSchema: JSONSchemaOutputTypeSchema.optional() outputSchema: OptionalJSONSchemaOutputTypeSchema
}); });
export type SystemToolChildDetailType = z.infer<typeof SystemToolChildDetailSchema>; export type SystemToolChildDetailType = z.infer<typeof SystemToolChildDetailSchema>;
...@@ -85,9 +95,9 @@ export const SystemToolDetailSchema = z.object({ ...@@ -85,9 +95,9 @@ export const SystemToolDetailSchema = z.object({
...SystemToolListItemSchema.shape, ...SystemToolListItemSchema.shape,
children: z.array(SystemToolChildDetailSchema).optional(), children: z.array(SystemToolChildDetailSchema).optional(),
inputSchema: JSONSchemaInputTypeSchema.optional(), inputSchema: OptionalJSONSchemaInputTypeSchema,
outputSchema: JSONSchemaOutputTypeSchema.optional(), outputSchema: OptionalJSONSchemaOutputTypeSchema,
secretSchema: JSONSchemaInputTypeSchema.optional(), secretSchema: OptionalJSONSchemaInputTypeSchema,
secretsVal: z.record(z.string(), z.any()).nullish(), secretsVal: z.record(z.string(), z.any()).nullish(),
isLatestVersion: z.boolean().optional(), isLatestVersion: z.boolean().optional(),
associatedPluginId: z.string().optional(), associatedPluginId: z.string().optional(),
......
import { describe, expect, it } from 'vitest'; 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 { PluginStatusEnum } from '@fastgpt/global/core/plugin/type';
import { SystemToolSystemSecretStatusEnum } from '@fastgpt/global/core/app/tool/systemTool/constants'; import { SystemToolSystemSecretStatusEnum } from '@fastgpt/global/core/app/tool/systemTool/constants';
import { TeamToolDetailSchema } from '@fastgpt/global/openapi/core/plugin/team/tool/api';
const createAdminToolDetail = () => ({ const createAdminToolDetail = () => ({
id: 'systemTool-null-schema', id: 'systemTool-null-schema',
...@@ -67,3 +71,44 @@ describe('AdminSystemToolDetailSchema', () => { ...@@ -67,3 +71,44 @@ describe('AdminSystemToolDetailSchema', () => {
expect(result.children?.[0]).not.toHaveProperty('outputSchema'); 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 = ({ ...@@ -128,6 +128,8 @@ const getPluginClientSource = ({
return runtimeSource || 'system'; return runtimeSource || 'system';
}; };
const normalizeOptionalJsonSchema = <T>(schema: T | null | undefined) => schema ?? undefined;
const getSystemToolConfigIds = (pluginId: string) => { const getSystemToolConfigIds = (pluginId: string) => {
const systemToolPrefix = `${AppToolSourceEnum.systemTool}-`; const systemToolPrefix = `${AppToolSourceEnum.systemTool}-`;
const commercialPrefix = `${AppToolSourceEnum.commercial}-`; const commercialPrefix = `${AppToolSourceEnum.commercial}-`;
...@@ -421,6 +423,9 @@ export class SystemToolRepo { ...@@ -421,6 +423,9 @@ export class SystemToolRepo {
const children = tool.isToolset const children = tool.isToolset
? tool.children?.map((item) => { ? tool.children?.map((item) => {
const dbChild = getFirstSystemToolConfig(dbChildrenMap, `${pluginId}/${item.id}`); const dbChild = getFirstSystemToolConfig(dbChildrenMap, `${pluginId}/${item.id}`);
const inputSchema = normalizeOptionalJsonSchema(item.inputSchema);
const outputSchema = normalizeOptionalJsonSchema(item.outputSchema);
return { return {
id: item.id, id: item.id,
name: parseI18nString(item.name, lang), name: parseI18nString(item.name, lang),
...@@ -432,13 +437,20 @@ export class SystemToolRepo { ...@@ -432,13 +437,20 @@ export class SystemToolRepo {
systemKeyCost: dbChild?.systemKeyCost ?? 0, systemKeyCost: dbChild?.systemKeyCost ?? 0,
currentCost: dbChild?.currentCost ?? 0, currentCost: dbChild?.currentCost ?? 0,
icon: item.icon, icon: item.icon,
inputSchema: item.inputSchema, ...(inputSchema !== undefined ? { inputSchema } : {}),
outputSchema: item.outputSchema ...(outputSchema !== undefined ? { outputSchema } : {})
} satisfies SystemToolChildDetailType; } satisfies SystemToolChildDetailType;
}) })
: undefined; : 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 configuredSecretsVal = SystemToolCodec.getConfiguredSecretsVal(dbTool);
const hasSystemSecret = !!configuredSecretsVal; const hasSystemSecret = !!configuredSecretsVal;
...@@ -483,17 +495,10 @@ export class SystemToolRepo { ...@@ -483,17 +495,10 @@ export class SystemToolRepo {
hideTags: dbTool?.hideTags ?? [], hideTags: dbTool?.hideTags ?? [],
promoteTags: dbTool?.promoteTags ?? [], promoteTags: dbTool?.promoteTags ?? [],
pluginOrder: dbTool?.pluginOrder, pluginOrder: dbTool?.pluginOrder,
secretSchema: tool.secretSchema, ...(secretSchema !== undefined ? { secretSchema } : {}),
isLatestVersion: tool.isLatestVersion, isLatestVersion: tool.isLatestVersion,
...(childPluginId ...(inputSchema !== undefined ? { inputSchema } : {}),
? { ...(outputSchema !== undefined ? { outputSchema } : {}),
inputSchema: child!.inputSchema,
outputSchema: child!.outputSchema
}
: {
inputSchema: tool.inputSchema,
outputSchema: tool.outputSchema
}),
permissions: tool.permission permissions: tool.permission
}; };
......
...@@ -408,6 +408,52 @@ describe('SystemToolRepo.getSystemToolDetail', () => { ...@@ -408,6 +408,52 @@ describe('SystemToolRepo.getSystemToolDetail', () => {
expect(tool).not.toHaveProperty('outputs'); expect(tool).not.toHaveProperty('outputs');
expect(tool).not.toHaveProperty('secrets'); 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', () => { 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