Commit 6591565e by Finley Ge Committed by GitHub

fix(workflow): restore legacy HTTP tool input defaults (#7462)

parent 0591a4f5
......@@ -111,6 +111,31 @@ export const canInputBeAgentGenerated = (
};
/**
* 恢复旧版工作流 HTTP 节点动态工具参数的默认来源。
*
* 旧编辑器只保存 canEdit 和 toolDescription;显式来源配置始终优先,避免覆盖用户手动选择。
*/
export const normalizeLegacyWorkflowHttpToolInputsDefaultMode = <T extends FlowNodeInputItemType>(
inputs: T[]
): T[] =>
inputs.map((input) => {
if (
input.canEdit !== true ||
!input.toolDescription ||
input.isToolParam !== undefined ||
input.selectedType !== undefined ||
!canInputBeAgentGenerated(input)
) {
return input;
}
return {
...input,
isToolParam: true
};
});
/**
* 将节点输入升级为 selectedType 协议。
*
* 显式 selectedType 优先;旧 selectedTypeIndex 仅在没有新字段时用于恢复选择。
......
......@@ -13,6 +13,7 @@ import {
getToolConfigStatus,
initToolInputTypeByDefaultMode,
isAgentGeneratedToolInput,
normalizeLegacyWorkflowHttpToolInputsDefaultMode,
normalizeFlowNodeInputType,
stripToolInputDefaultMode
} from '@fastgpt/global/core/app/formEdit/utils';
......@@ -903,6 +904,57 @@ describe('getToolConfigStatus', () => {
});
describe('agent generated tool input helpers', () => {
it('should restore only legacy editable HTTP tool params', () => {
const legacyInput = createMockInput({
canEdit: true,
toolDescription: 'Generated query',
renderTypeList: [FlowNodeInputTypeEnum.reference]
});
const explicitManualInput = createMockInput({
key: 'manual',
canEdit: true,
toolDescription: 'Manual query',
isToolParam: false
});
const selectedManualInput = createMockInput({
key: 'selected-manual',
canEdit: true,
toolDescription: 'Selected manual query',
selectedType: FlowNodeInputTypeEnum.reference
});
const staticInput = createMockInput({
key: 'static',
toolDescription: 'Static input'
});
const emptyDescriptionInput = createMockInput({
key: 'empty-description',
canEdit: true,
toolDescription: ''
});
const unsafeInput = createMockInput({
key: 'unsafe',
canEdit: true,
toolDescription: 'Secret input',
renderTypeList: [FlowNodeInputTypeEnum.password]
});
const result = normalizeLegacyWorkflowHttpToolInputsDefaultMode([
legacyInput,
explicitManualInput,
selectedManualInput,
staticInput,
emptyDescriptionInput,
unsafeInput
]);
expect(result[0]).toMatchObject({ isToolParam: true });
expect(result[1]).toBe(explicitManualInput);
expect(result[2]).toBe(selectedManualInput);
expect(result[3]).toBe(staticInput);
expect(result[4]).toBe(emptyDescriptionInput);
expect(result[5]).toBe(unsafeInput);
});
it.each([
FlowNodeInputTypeEnum.hidden,
FlowNodeInputTypeEnum.fileSelect,
......
......@@ -7,6 +7,7 @@ import type { StoreNodeItemType } from '@fastgpt/global/core/workflow/type/node'
import { nodeInputIsReference } from '@fastgpt/global/core/workflow/utils';
import {
initAgentToolInputType,
normalizeLegacyWorkflowHttpToolInputsDefaultMode,
normalizeFlowNodeInputType
} from '@fastgpt/global/core/app/formEdit/utils';
import { getClientToolPreviewNode } from './tool/utils/client';
......@@ -221,6 +222,9 @@ export async function rewriteAppWorkflowToDetail({
if (allowLegacyFallback) {
node.inputs = normalizeWorkflowToolInputsDefaultMode(node.inputs);
}
if (node.flowNodeType === FlowNodeTypeEnum.httpRequest468) {
node.inputs = normalizeLegacyWorkflowHttpToolInputsDefaultMode(node.inputs);
}
if (node.flowNodeType !== FlowNodeTypeEnum.pluginInput) {
node.inputs = node.inputs.map((input) =>
normalizeFlowNodeInputType(input, { deferDefaultSelection: true })
......
......@@ -39,6 +39,7 @@ import { getLastInteractiveValue } from '@fastgpt/global/core/workflow/runtime/u
import {
getSavedToolInputSelectedType,
initToolInputsTypeByDefaultMode,
normalizeLegacyWorkflowHttpToolInputsDefaultMode,
normalizeFlowNodeInputType
} from '@fastgpt/global/core/app/formEdit/utils';
import { jsonSchema2NodeInput } from '@fastgpt/global/core/app/jsonschema';
......@@ -772,10 +773,15 @@ export const rewriteRuntimeWorkFlow = async ({
node.pluginId?.startsWith('systemTool-') ||
node.pluginId?.startsWith('commercial-')
);
const inputsWithLegacyDefaults =
node.flowNodeType === FlowNodeTypeEnum.pluginModule && isTool
? normalizeWorkflowToolInputsDefaultMode(node.inputs)
: node.inputs;
const inputsWithLegacyDefaults = (() => {
if (node.flowNodeType === FlowNodeTypeEnum.pluginModule && isTool) {
return normalizeWorkflowToolInputsDefaultMode(node.inputs);
}
if (node.flowNodeType === FlowNodeTypeEnum.httpRequest468 && isTool) {
return normalizeLegacyWorkflowHttpToolInputsDefaultMode(node.inputs);
}
return node.inputs;
})();
node.inputs = inputsWithLegacyDefaults.map((input) =>
normalizeFlowNodeInputType(input, {
......
......@@ -1025,6 +1025,62 @@ describe('rewriteRuntimeWorkFlow', () => {
expect(runtimeInputs.find((input) => input.key === 'text2')?.value).toBe('');
});
it('should restore legacy HTTP workflow tool params at runtime', async () => {
const toolCallNode = makeNode('tool-call', FlowNodeTypeEnum.toolCall);
const httpToolNode = makeNode('http-tool', FlowNodeTypeEnum.httpRequest468, {
inputs: [
{
key: 'query',
label: 'query',
valueType: 'string',
required: true,
canEdit: true,
renderTypeList: [FlowNodeInputTypeEnum.reference],
toolDescription: 'Search query'
},
{
key: 'manual',
label: 'manual',
valueType: 'string',
required: false,
canEdit: true,
renderTypeList: [FlowNodeInputTypeEnum.reference],
toolDescription: 'Manual value',
isToolParam: false
}
]
});
const nodes = [toolCallNode, httpToolNode];
const edges = [
makeEdge('tool-call', 'http-tool', {
sourceHandle: NodeOutputKeyEnum.selectedTools,
targetHandle: NodeOutputKeyEnum.selectedTools
})
];
await rewriteRuntimeWorkFlow({ teamId: 'team1', nodes, edges });
expect(httpToolNode.inputs[0]).toMatchObject({
selectedType: FlowNodeInputTypeEnum.agentGenerated,
isToolParam: true,
renderTypeList: [FlowNodeInputTypeEnum.agentGenerated, FlowNodeInputTypeEnum.reference]
});
expect(httpToolNode.inputs[1]).toMatchObject({
selectedType: FlowNodeInputTypeEnum.reference,
isToolParam: false
});
const runtimeInputs = updateAgentLoopCoreWorkflowToolInputValue({
params: {
query: 'generated query',
manual: 'ignored model value'
},
inputs: httpToolNode.inputs
});
expect(runtimeInputs[0].value).toBe('generated query');
expect(runtimeInputs[1].value).toBeUndefined();
});
it('should normalize legacy system tool inputs at the runtime boundary', async () => {
const toolCallNode = makeNode('tool-call', FlowNodeTypeEnum.toolCall);
const systemToolNode = makeNode('system-tool', FlowNodeTypeEnum.tool, {
......
import React, { useCallback, useRef } from 'react';
import React, { useCallback } from 'react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import type { EditFieldModalProps } from './type';
import { useTranslation } from 'next-i18next';
......@@ -69,7 +69,7 @@ const EditFieldModal = ({
});
const onclickSubmitError = useCallback(
(e: Object) => {
(e: object) => {
for (const item of Object.values(e)) {
if (item.message) {
toast({
......@@ -143,6 +143,7 @@ export default React.memo(EditFieldModal);
export const defaultEditFormData: FlowNodeInputItemType = {
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.reference],
isToolParam: true,
key: '',
label: '',
toolDescription: '',
......
import { describe, expect, it } from 'vitest';
import { defaultEditFormData } from '@/pageComponents/app/detail/WorkflowComponents/Flow/nodes/render/RenderToolInput/EditFieldModal';
describe('RenderToolInput EditFieldModal', () => {
it('creates new dynamic tool params with the Agent-generated default', () => {
expect(defaultEditFormData.isToolParam).toBe(true);
});
});
......@@ -41,6 +41,53 @@ vi.mock('@fastgpt/service/support/permission/app/auth', async (importOriginal) =
const { rewriteAppWorkflowToDetail } = await import('@fastgpt/service/core/app/utils');
describe('rewriteAppWorkflowToDetail - legacy HTTP workflow tool inputs', () => {
it('恢复旧版 HTTP 动态工具参数并保留显式手动配置', async () => {
const nodes = [
{
nodeId: 'http-tool',
flowNodeType: FlowNodeTypeEnum.httpRequest468,
inputs: [
{
key: 'query',
label: 'query',
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.reference],
toolDescription: 'Search query',
canEdit: true
},
{
key: 'manual',
label: 'manual',
valueType: WorkflowIOValueTypeEnum.string,
renderTypeList: [FlowNodeInputTypeEnum.reference],
toolDescription: 'Manual value',
canEdit: true,
isToolParam: false
}
],
outputs: []
} as StoreNodeItemType
];
await rewriteAppWorkflowToDetail({
nodes,
teamId: 'team-1',
ownerTmbId: 'tmb-1',
isRoot: false
});
expect(nodes[0].inputs[0]).toMatchObject({
isToolParam: true,
selectedType: undefined
});
expect(nodes[0].inputs[1]).toMatchObject({
isToolParam: false,
selectedType: FlowNodeInputTypeEnum.reference
});
});
});
describe('rewriteAppWorkflowToDetail - legacy workflow tool inputs', () => {
it('回显旧版工作流工具输入的默认 AI 生成配置并保留显式关闭', async () => {
const legacyInput = {
......
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