Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fastgpt-plugin/cli",
"version": "0.2.1",
"version": "0.2.2-alpha.1",
"description": "CLI for creating, debugging, building, checking, and packaging FastGPT Plugin tools.",
"keywords": [
"fastgpt",
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/templates/tool/index.tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ const handler = createToolHandler({
inputSchema: z.object({
delay: z.number().meta({
title: 'Delay',
description: 'Delay duration in milliseconds'
description: 'Delay duration in milliseconds',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({}).meta({} satisfies OutputSchemaMetaType),
Expand Down
6 changes: 4 additions & 2 deletions apps/cli/templates/tool/index.toolset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ const secretSchema = z.object({
const mysqlHandler = createToolHandler({
inputSchema: z.object({
query: z.string().meta({
title: 'SQL Query'
title: 'SQL Query',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -62,7 +63,8 @@ const mysqlHandler = createToolHandler({
const pgsqlHandler = createToolHandler({
inputSchema: z.object({
query: z.string().meta({
title: 'SQL Query'
title: 'SQL Query',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down
5 changes: 3 additions & 2 deletions docs/dev/how-to-devlop-plugin.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ const handler = createToolHandler({
inputSchema: z.object({
query: z.string().min(1).meta({
title: 'Query',
description: 'Search keyword'
description: 'Search keyword',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -218,7 +219,7 @@ Core rules:
- Keep `pluginId`, child tool `id`, input field names, and output field names stable after publishing.
- Use `{ en, 'zh-CN' }` for `manifest.name`, `manifest.description`, and `versionDescription`.
- Describe inputs, outputs, and secrets with Zod schemas.
- Add `InputSchemaMetaType` to input fields and `OutputSchemaMetaType` to output fields.
- Add `InputSchemaMetaType` to input fields and set `isToolParam: true` for parameters recommended to be managed by AI; add `OutputSchemaMetaType` to output fields.
- Add `SecretSchemaMetaType` to secret fields and set `isSecret: true` for sensitive fields.
- Handler return values must match `outputSchema`.
- Convert external API errors into actionable messages and avoid exposing secrets, tokens, or complete sensitive responses.
Expand Down
5 changes: 3 additions & 2 deletions docs/dev/how-to-devlop-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ const handler = createToolHandler({
inputSchema: z.object({
query: z.string().min(1).meta({
title: 'Query',
description: 'Search keyword'
description: 'Search keyword',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -218,7 +219,7 @@ export default defineTool({
- `pluginId`、子工具 `id`、输入字段名、输出字段名发布后保持稳定。
- `manifest.name`、`manifest.description` 和 `versionDescription` 使用 `{ en, 'zh-CN' }`。
- 输入、输出和密钥都用 Zod schema 描述。
- 输入字段补充 `InputSchemaMetaType`,输出字段补充 `OutputSchemaMetaType`。
- 输入字段补充 `InputSchemaMetaType`,推荐由 AI 托管补充的参数设置 `isToolParam: true`;输出字段补充 `OutputSchemaMetaType`。
- 密钥字段补充 `SecretSchemaMetaType`,敏感字段设置 `isSecret: true`。
- handler 返回值必须匹配 `outputSchema`。
- 外部 API 错误需要转成可定位的错误信息,同时避免输出密钥、令牌和完整敏感响应。
Expand Down
96 changes: 96 additions & 0 deletions packages/infrastructure/src/plugin/tool.impl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,102 @@ describe('ToolManager.detail', () => {
version: '1.10.0'
});
});

it('normalizes input schema tool parameter metadata in tool detail', async () => {
const toolManager = createToolManager();
listVersions.mockResolvedValue(successResult([{ version: '1.0.0' }]));
getPluginByUserPluginId.mockResolvedValue(
successResult({
...makeTool('1.0.0'),
inputSchema: {
type: 'object',
properties: {
withToolDescription: {
type: 'string',
toolDescription: 'Existing model-facing description'
},
manualByDefault: {
type: 'string',
description: {
en: 'Fallback manual description',
'zh-CN': '手动参数说明'
}
},
explicitFalse: {
type: 'string',
description: {
en: 'Explicit false fallback'
},
isToolParam: false
},
explicitTrue: {
type: 'string',
description: {
en: 'Explicit true fallback'
},
isToolParam: true
}
}
},
children: [
{
id: 'child',
name: { en: 'Child' },
description: { en: 'Child tool' },
icon: 'https://example.com/child.svg',
toolDescription: 'Child tool',
inputSchema: {
type: 'object',
properties: {
childParam: {
type: 'string',
description: {
en: 'Child fallback'
}
}
}
},
outputSchema: {
type: 'object'
}
}
]
} satisfies ToolType)
);

const [result, err] = await toolManager.detail({
pluginId: 'getTime',
source: 'system',
version: '1.0.0'
});

const properties = (result?.inputSchema as { properties: Record<string, unknown> }).properties;
const childProperties = (result?.children?.[0]?.inputSchema as {
properties: Record<string, unknown>;
}).properties;

expect(err).toBeNull();
expect(properties.withToolDescription).toMatchObject({
toolDescription: 'Existing model-facing description',
isToolParam: true
});
expect(properties.manualByDefault).toMatchObject({
toolDescription: 'Fallback manual description',
isToolParam: false
});
expect(properties.explicitFalse).toMatchObject({
toolDescription: 'Explicit false fallback',
isToolParam: false
});
expect(properties.explicitTrue).toMatchObject({
toolDescription: 'Explicit true fallback',
isToolParam: true
});
expect(childProperties.childParam).toMatchObject({
toolDescription: 'Child fallback',
isToolParam: false
});
});
});

describe('ToolManager.run', () => {
Expand Down
73 changes: 73 additions & 0 deletions packages/infrastructure/src/plugin/tool.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { ErrorCode } from '@infrastructure/errors/error.registry';
import { InvokeManager } from './invoke/invoke.impl';
import { Semver } from './utils/semver';

type JsonObject = Record<string, unknown>;

export type ToolManagerDeps = {
pluginRepo: PluginRepoPort;
pluginRuntimeManager: PluginRuntimeManagerPort;
Expand All @@ -41,6 +43,72 @@ export type PluginToolRunPayloadType = {
childId?: string;
};

function isJsonObject(value: unknown): value is JsonObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function getDescriptionFallback(description: unknown): string | undefined {
if (typeof description === 'string' && description.length > 0) {
return description;
}

if (!isJsonObject(description)) {
return undefined;
}

const en = description.en;
return typeof en === 'string' && en.length > 0 ? en : undefined;
}

function normalizeInputSchemaToolParams(inputSchema: unknown): unknown {
if (!isJsonObject(inputSchema) || !isJsonObject(inputSchema.properties)) {
return inputSchema;
}

const properties = Object.fromEntries(
Object.entries(inputSchema.properties).map(([key, property]) => {
if (!isJsonObject(property)) {
return [key, property];
}

const normalizedProperty = { ...property };
const hasToolDescription =
typeof normalizedProperty.toolDescription === 'string' &&
normalizedProperty.toolDescription.length > 0;
const hasIsToolParam = normalizedProperty.isToolParam !== undefined;

if (hasIsToolParam) {
if (!hasToolDescription) {
const fallback = getDescriptionFallback(normalizedProperty.description);
if (fallback !== undefined) {
normalizedProperty.toolDescription = fallback;
}
}

return [key, normalizedProperty];
}

if (hasToolDescription) {
normalizedProperty.isToolParam = true;
return [key, normalizedProperty];
}

const fallback = getDescriptionFallback(normalizedProperty.description);
if (fallback !== undefined) {
normalizedProperty.toolDescription = fallback;
}
normalizedProperty.isToolParam = false;

return [key, normalizedProperty];
})
);

return {
...inputSchema,
properties
};
}

export class ToolManager implements ToolManagerPort {
private static instance: ToolManager;
private constructor(private deps: ToolManagerDeps) {}
Expand All @@ -61,6 +129,11 @@ export class ToolManager implements ToolManagerPort {
}): ToolDetailType {
return {
...tool,
inputSchema: normalizeInputSchemaToolParams(tool.inputSchema),
children: tool.children?.map((child) => ({
...child,
inputSchema: normalizeInputSchemaToolParams(child.inputSchema)
})),
source,
isToolset: Boolean(tool.children?.length),
isLatestVersion
Expand Down
2 changes: 1 addition & 1 deletion sdk/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fastgpt-plugin/sdk-client",
"version": "0.0.1",
"version": "0.1.0-alpha.2",
"description": "TypeScript client SDK for calling the FastGPT Plugin service APIs.",
"keywords": [
"fastgpt",
Expand Down
8 changes: 5 additions & 3 deletions sdk/factory/README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import z from 'zod';
const handler = createToolHandler({
inputSchema: z.object({
text: z.string().meta({
title: 'Text'
title: 'Text',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -118,7 +119,7 @@ Defines a tool set with multiple child tools. All child tools share the top-leve

A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.

Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields, `OutputSchemaMetaType` for output fields, and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.
Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields and optionally set `isToolParam: true` for input parameters recommended to be managed by AI. Use `OutputSchemaMetaType` for output fields and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.

## Host Invocation

Expand All @@ -128,7 +129,8 @@ Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes fi
const handler = createToolHandler({
inputSchema: z.object({
content: z.string().meta({
title: 'Content'
title: 'Content',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down
22 changes: 14 additions & 8 deletions sdk/factory/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import z from 'zod';
const handler = createToolHandler({
inputSchema: z.object({
text: z.string().meta({
title: 'Text'
title: 'Text',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -83,7 +84,8 @@ Defines a tool handler and infers `input`, `output`, and `secrets` types from Zo
const handler = createToolHandler({
inputSchema: z.object({
query: z.string().meta({
title: 'Query'
title: 'Query',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -144,7 +146,8 @@ Defines a tool set with multiple child tools. All child tools share the top-leve
const searchHandler = createToolHandler({
inputSchema: z.object({
query: z.string().meta({
title: 'Query'
title: 'Query',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand All @@ -158,7 +161,8 @@ const searchHandler = createToolHandler({
const summaryHandler = createToolHandler({
inputSchema: z.object({
content: z.string().meta({
title: 'Content'
title: 'Content',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down Expand Up @@ -240,9 +244,9 @@ export default defineToolSet({

A single tool can declare `secretSchema` in its handler. A tool set can declare a shared `secretSchema` at the top level of `defineToolSet()`.

Schema 字段元数据通过 Zod `.meta()` 写入构建后的 JSON Schema。输入字段使用 `InputSchemaMetaType`,输出字段使用 `OutputSchemaMetaType`,密钥字段使用 `SecretSchemaMetaType`。`secretSchema` 的每个字段都要包含 `isSecret`,需要加密存储时设为 `true`。
Schema 字段元数据通过 Zod `.meta()` 写入构建后的 JSON Schema。输入字段使用 `InputSchemaMetaType`,推荐由 AI 托管补充的输入参数可设置 `isToolParam: true`。输出字段使用 `OutputSchemaMetaType`,密钥字段使用 `SecretSchemaMetaType`。`secretSchema` 的每个字段都要包含 `isSecret`,需要加密存储时设为 `true`。

Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields, `OutputSchemaMetaType` for output fields, and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.
Schema field metadata is written into the built JSON Schema through Zod `.meta()`. Use `InputSchemaMetaType` for input fields and optionally set `isToolParam: true` for input parameters recommended to be managed by AI. Use `OutputSchemaMetaType` for output fields and `SecretSchemaMetaType` for secret fields. Every `secretSchema` field must include `isSecret`; set it to `true` for values that need encrypted storage.

```ts
const secretSchema = z.object({
Expand All @@ -259,7 +263,8 @@ const secretSchema = z.object({
const handler = createToolHandler({
inputSchema: z.object({
prompt: z.string().meta({
title: 'Prompt'
title: 'Prompt',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand All @@ -286,7 +291,8 @@ Use `ctx.invoke` to call FastGPT host capabilities. The SDK currently exposes fi
const handler = createToolHandler({
inputSchema: z.object({
content: z.string().meta({
title: 'Content'
title: 'Content',
isToolParam: true
} satisfies InputSchemaMetaType)
}),
outputSchema: z.object({
Expand Down
2 changes: 1 addition & 1 deletion sdk/factory/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fastgpt-plugin/sdk-factory",
"version": "0.0.1",
"version": "0.1.0-alpha.1",
"description": "TypeScript factory SDK for authoring FastGPT Plugin tools and tool suites.",
"keywords": [
"fastgpt",
Expand Down
Loading
Loading