feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { typeSafeJSONParse } from './safe-json-parse';
export { stringifyValue } from './stringify-value';
export {
getTestsetNameRules,
validateTestsetSchema,
traverseTestsetNodeFormSchemas,
getTestsetFormSubFieldName,
isTestsetFormSameFieldType,
assignTestsetFormDefaultValue,
getTestsetFormSubFieldType,
getTestsetFormItemPlaceholder,
transTestsetBool2BoolSelect,
transTestsetBoolSelect2Bool,
transTestsetFormItemSchema2Form,
getTestDataByTestset,
getTestsetFormItemCustomProps,
} from './testset';

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import JSONBig from 'json-bigint';
import { REPORT_EVENTS } from '@coze-arch/report-events';
import { reporter } from '@coze-arch/logger';
interface TypeSafeJSONParseOptions {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
emptyValue?: any;
needReport?: boolean;
enableBigInt?: boolean;
}
export const typeSafeJSONParse = (
v: unknown,
options?: TypeSafeJSONParseOptions,
): unknown => {
if (typeof v === 'object') {
return v;
}
try {
if (options?.enableBigInt) {
return JSONBig.parse(String(v));
}
return JSON.parse(String(v));
} catch (e) {
// 日志解析
if (options?.needReport) {
reporter.errorEvent({
error: e as Error,
eventName: REPORT_EVENTS.parseJSON,
});
}
return options?.emptyValue;
}
};

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isBoolean, isNumber } from 'lodash-es';
// 将表单值转换为testrun接口协议格式
export const stringifyValue = (
values: any,
stringifyKeys?: string[],
): Record<string, string> | undefined => {
if (!values) {
return undefined;
}
return Object.entries(values).reduce<Record<string, string>>(
(buf, [k, v]) => {
if (isBoolean(v) || isNumber(v)) {
buf[k] = String(v);
} else if (stringifyKeys?.includes(k)) {
buf[k] = JSON.stringify(v);
} else {
buf[k] = v as string;
}
return buf;
},
{},
);
};

View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { isNil } from 'lodash-es';
import { type FormItemSchema } from '../../types';
import { FormItemSchemaType } from '../../constants';
export function assignTestsetFormDefaultValue(ipt: FormItemSchema) {
if (!isNil(ipt.value)) {
return;
}
switch (ipt.type) {
case FormItemSchemaType.BOOLEAN:
// ipt.value = true;
break;
case FormItemSchemaType.OBJECT:
ipt.value = '{}';
break;
case FormItemSchemaType.LIST:
ipt.value = '[]';
break;
default:
break;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Ajv from 'ajv';
import { I18n } from '@coze-arch/i18n';
import { type FormItemSchema } from '../../types';
import { FormItemSchemaType } from '../../constants';
export enum VariableTypeDTO {
object = 'object',
list = 'list',
string = 'string',
integer = 'integer',
float = 'float',
number = 'number',
boolean = 'boolean',
}
const VariableType2JsonSchemaProps = {
[VariableTypeDTO.object]: { type: 'object' },
[VariableTypeDTO.list]: { type: 'array' },
[VariableTypeDTO.float]: { type: 'number' },
[VariableTypeDTO.number]: { type: 'number' },
[VariableTypeDTO.integer]: { type: 'integer' },
[VariableTypeDTO.boolean]: { type: 'boolean' },
[VariableTypeDTO.string]: { type: 'string' },
};
function validateByJsonSchema(val: any, jsonSchema: any) {
if (!jsonSchema || !val) {
return true;
}
const ajv = new Ajv();
try {
const validate = ajv.compile(jsonSchema);
const valid = validate(JSON.parse(val));
return valid;
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no-catch
} catch {
return false;
}
}
function workflowJsonToJsonSchema(workflowJson: any) {
const { type, description } = workflowJson;
const props = VariableType2JsonSchemaProps[type];
if (type === VariableTypeDTO.object) {
const properties = {};
const required: string[] = [];
for (const field of workflowJson.schema) {
properties[field.name] = workflowJsonToJsonSchema(field);
if (field.required) {
required.push(field.name);
}
}
return {
...props,
description,
required,
properties,
};
} else if (type === VariableTypeDTO.list) {
return {
...props,
description,
items: workflowJsonToJsonSchema(workflowJson.schema),
};
}
return { ...props, description };
}
/**
* 自定义表单的额外参数
* 目前只对array和object表单加jsonSchema校验
*/
export function getTestsetFormItemCustomProps(
formItemSchema: FormItemSchema,
projectId?: string,
) {
switch (formItemSchema.type) {
case FormItemSchemaType.CHAT:
return {
projectId,
};
case FormItemSchemaType.LIST:
case FormItemSchemaType.OBJECT: {
const jsonSchema = workflowJsonToJsonSchema(formItemSchema);
return {
trigger: ['blur'],
jsonSchema,
rules: [
{
validator: (_rules, v, cb) => {
if (formItemSchema.required && !v) {
cb(
I18n.t('workflow_testset_required_tip', {
param_name: formItemSchema.name,
}),
);
return false;
}
if (!validateByJsonSchema(v, jsonSchema)) {
cb(I18n.t('workflow_debug_wrong_json'));
return false;
}
return true;
},
},
] as any[],
};
}
default:
return {};
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
import { type FormItemSchema } from '../../types';
import { FormItemSchemaType } from '../../constants';
/**
* placeholder
* - bot请选择bot
* - 其他xx必填
*/
export function getTestsetFormItemPlaceholder({ name, type }: FormItemSchema) {
if (type === FormItemSchemaType.BOT) {
return I18n.t('workflow_testset_vardatabase_placeholder');
} else if (type === FormItemSchemaType.BOOLEAN) {
return I18n.t('workflow_testset_please_select');
} else if (type === FormItemSchemaType.CHAT) {
return I18n.t('wf_chatflow_74');
}
return I18n.t('workflow_detail_title_testrun_error_input', {
a: name || '',
});
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ComponentType,
type CaseDataDetail,
} from '@coze-arch/bot-api/debugger_api';
import { typeSafeJSONParse } from '../safe-json-parse';
import { FieldName } from '../../constants';
const getTestDataByTestset = (testsetData?: CaseDataDetail) => {
const dataArray = (typeSafeJSONParse(testsetData?.caseBase?.input) ||
[]) as any[];
let botData: string | undefined;
let chatData: string | undefined;
let nodeData: Record<string, unknown> | undefined;
dataArray.forEach(data => {
/** 特殊虚拟节点 */
if (data?.component_type === ComponentType.CozeVariableBot) {
botData = data.inputs?.[0]?.value;
} else if (data?.component_type === ComponentType.CozeVariableChat) {
chatData = data.inputs?.[0]?.value;
} else {
nodeData = data.inputs?.reduce(
(prev, current) => ({
...prev,
[current.name]: current.value,
}),
nodeData,
);
}
});
const value = {};
if (nodeData) {
value[FieldName.Node] = {
[FieldName.Input]: nodeData,
};
}
if (botData) {
value[FieldName.Bot] = botData;
}
if (chatData) {
value[FieldName.Chat] = chatData;
}
return value;
};
export { getTestDataByTestset };

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type NodeFormSchema, type FormItemSchema } from '../../types';
export const getTestsetFormSubFieldName = (
formSchema: NodeFormSchema,
itemSchema: FormItemSchema,
) => `${itemSchema.name}_${formSchema.component_id}`;

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormItemSchemaType } from '../../constants';
export function getTestsetFormSubFieldType(type: string) {
switch (type) {
case FormItemSchemaType.STRING:
return 'String';
case FormItemSchemaType.FLOAT:
case FormItemSchemaType.NUMBER:
return 'Number';
case FormItemSchemaType.OBJECT:
return 'Object';
case FormItemSchemaType.BOOLEAN:
return 'Boolean';
case FormItemSchemaType.INTEGER:
return 'Integer';
default:
return `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { logger } from '@coze-arch/logger';
import { I18n } from '@coze-arch/i18n';
import {
type ComponentSubject,
type BizCtx,
} from '@coze-arch/bot-api/debugger_api';
import { debuggerApi } from '@coze-arch/bot-api';
interface GetTestsetNameRulesProps {
/** bizCtx */
bizCtx?: BizCtx;
/** bizComponentSubject */
bizComponentSubject?: ComponentSubject;
/** 原始值 */
originVal?: string;
/** 是否为海外(海外不允许输入中文 与PluginName校验规则对齐 */
isOversea?: boolean;
}
/**
* 校验名称格式(参考插件名称)
* - 海外:仅支持输入字母、数字、下划线或空格
* - 国内:仅支持输入中文、字母、数字、下划线或空格
*/
function validateNamePattern(
name: string,
isOversea?: boolean,
): string | undefined {
try {
const pattern = isOversea ? /^[\w\s]+$/ : /^[\w\s\u4e00-\u9fa5]+$/u;
const msg = isOversea
? I18n.t('create_plugin_modal_nameerror')
: I18n.t('create_plugin_modal_nameerror_cn');
return pattern.test(name) ? undefined : msg;
} catch (e: any) {
logger.error(e);
return undefined;
}
}
/**
* Testset名称表单校验规则
*
* @param param.bizCtx - bizCtx
* @param param.bizComponentSubject - bizComponentSubject
* @param param.originVal - 原始值
* @param param.isOversea - 是否为海外(海外不允许输入中文 与PluginName校验规则对齐
*/
export function getTestsetNameRules({
bizCtx,
bizComponentSubject,
originVal,
isOversea,
}: GetTestsetNameRulesProps): any[] {
const requiredMsg = I18n.t('workflow_testset_required_tip', {
param_name: I18n.t('workflow_testset_name'),
});
return [
{ required: true, message: requiredMsg },
{
asyncValidator: async (_rules, value: string, cb) => {
// required
if (!value) {
cb(requiredMsg);
return;
}
// 编辑模式下,名称与原名相同时跳过
if (originVal && value === originVal) {
return;
}
// 中文、字母等等等等
const formatMsg = validateNamePattern(value, isOversea);
if (formatMsg) {
cb(formatMsg);
return;
}
// 检查重复
try {
const { isPass } = await debuggerApi.CheckCaseDuplicate({
bizCtx,
bizComponentSubject,
caseName: value,
});
if (isPass) {
cb();
return;
}
cb(I18n.t('workflow_testset_name_duplicated'));
// eslint-disable-next-line @coze-arch/use-error-in-catch -- no catch
} catch {
cb();
}
},
},
];
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { getTestsetNameRules } from './get-testset-name-rules';
export { validateTestsetSchema } from './validate-schema';
export { traverseTestsetNodeFormSchemas } from './traverse-testset-node-form-schemas';
export { getTestsetFormSubFieldName } from './get-testset-form-sub-field-name';
export { isTestsetFormSameFieldType } from './is-testset-form-same-field-type';
export { assignTestsetFormDefaultValue } from './assign-testset-form-default-value';
export { getTestsetFormSubFieldType } from './get-testset-form-sub-field-type';
export { getTestsetFormItemPlaceholder } from './get-form-item-placeholder';
export {
transTestsetBool2BoolSelect,
transTestsetBoolSelect2Bool,
transTestsetFormItemSchema2Form,
} from './trans-form-value';
export { getTestDataByTestset } from './get-test-data-by-testset';
export { getTestsetFormItemCustomProps } from './get-form-item-custom-props';

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FormItemSchemaType } from '../../constants';
function isNumberType(t: string) {
return t === FormItemSchemaType.NUMBER || t === FormItemSchemaType.FLOAT;
}
/** 判断类型一致,**特化:**`number`和`float`视为同一类型 */
export const isTestsetFormSameFieldType = (t1?: string, t2?: string) => {
if (typeof t1 === 'undefined' || typeof t2 === 'undefined') {
return false;
}
return isNumberType(t1) ? isNumberType(t2) : t1 === t2;
};

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FormItemSchema } from '../../types';
import {
TestsetFormValuesForBoolSelect,
FormItemSchemaType,
} from '../../constants';
export function transTestsetBoolSelect2Bool(
val?: TestsetFormValuesForBoolSelect,
) {
switch (val) {
case TestsetFormValuesForBoolSelect.TRUE:
return true;
case TestsetFormValuesForBoolSelect.FALSE:
return false;
default:
return undefined;
}
}
export function transTestsetBool2BoolSelect(val?: boolean) {
switch (val) {
case true:
return TestsetFormValuesForBoolSelect.TRUE;
case false:
return TestsetFormValuesForBoolSelect.FALSE;
default:
return undefined;
}
}
export function transTestsetFormItemSchema2Form(ipt?: FormItemSchema) {
if (ipt?.type === FormItemSchemaType.BOOLEAN) {
return {
...ipt,
value: transTestsetBool2BoolSelect(ipt.value as boolean | undefined),
};
}
return ipt;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type NodeFormSchema, type FormItemSchema } from '../../types';
export const traverseTestsetNodeFormSchemas = (
schemas: NodeFormSchema[],
cb: (s: NodeFormSchema, ip: FormItemSchema) => any,
) => {
for (const schema of schemas) {
for (const ipt of schema.inputs) {
cb(schema, ipt);
}
}
};

View File

@@ -0,0 +1,179 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { logger } from '@coze-arch/logger';
import {
ComponentType,
type BizCtx,
type ComponentSubject,
} from '@coze-arch/bot-api/debugger_api';
import { debuggerApi } from '@coze-arch/bot-api';
import type {
ArrayFieldSchema,
FormItemSchema,
ObjectFieldSchema,
NodeFormSchema,
ValidateSchemaResult,
} from '../../types';
import { FormItemSchemaType } from '../../constants';
/** 变量命名校验规则对齐workflow得参数名校验 */
const PARAM_NAME_VALIDATION_RULE =
/^(?!.*\b(true|false|and|AND|or|OR|not|NOT|null|nil|If|Switch)\b)[a-zA-Z_][a-zA-Z_$0-9]*$/;
function validateParamName(name?: string) {
return Boolean(name && PARAM_NAME_VALIDATION_RULE.test(name));
}
function isArrayOrObjectField(field: FormItemSchema) {
return (
field.type === FormItemSchemaType.LIST ||
field.type === FormItemSchemaType.OBJECT
);
}
function validateArrayOrObjectSchema(
schema?: ObjectFieldSchema | ArrayFieldSchema,
) {
if (!schema) {
return false;
}
if (Array.isArray(schema)) {
const nameSet = new Set<string>();
for (const sub of schema) {
if (!validateParamName(sub.name) || nameSet.has(sub.name)) {
return false;
}
nameSet.add(sub.name);
if (
isArrayOrObjectField(sub) &&
!validateArrayOrObjectSchema(sub.schema)
) {
return false;
}
}
return true;
}
return Boolean(schema.type);
}
function checkArrayOrObjectField(field: FormItemSchema) {
if (!isArrayOrObjectField(field)) {
return true;
}
if (!field.schema) {
return false;
}
if (Array.isArray(field.schema)) {
const nameSet = new Set<string>();
for (const item of field.schema) {
if (!validateParamName(item.name) || nameSet.has(item.name)) {
return false;
}
nameSet.add(item.name);
if (
isArrayOrObjectField(item) &&
!validateArrayOrObjectSchema(item.schema)
) {
return false;
}
}
}
return true;
}
function checkNodeFormSchema(schema: NodeFormSchema) {
// 节点参数为空
if (!schema.inputs.length) {
return false;
}
const nameSet = new Set<string>();
for (const ipt of schema.inputs) {
// 名称非法 or 重复
if (!validateParamName(ipt.name) || nameSet.has(ipt.name)) {
return false;
}
nameSet.add(ipt.name);
// 单独检测复杂类型
if (!checkArrayOrObjectField(ipt)) {
return false;
}
}
return true;
}
function validate(json?: string): ValidateSchemaResult {
if (!json) {
return 'invalid';
}
try {
const schemas = JSON.parse(json) as NodeFormSchema[];
// schema为空 or start节点的inputs为空
if (
schemas.length === 0 ||
(schemas[0].component_type === ComponentType.CozeStartNode &&
schemas[0].inputs.length === 0)
) {
return 'empty';
}
for (const schema of schemas) {
if (!checkNodeFormSchema(schema)) {
return 'invalid';
}
}
return 'ok';
} catch (e: any) {
logger.error(e);
return 'ok';
}
}
interface ValidateSchemaOptions {
bizCtx?: BizCtx;
bizComponentSubject?: ComponentSubject;
}
/** 检查workflow节点表单是否为空(schema为空 or start节点的inputs为空) */
export const validateTestsetSchema = async (
options: ValidateSchemaOptions,
): Promise<ValidateSchemaResult> => {
try {
const resp = await debuggerApi.GetSchemaByID(options);
const err = validate(resp.schemaJson);
return err;
} catch (e: any) {
logger.error(e);
return 'ok';
}
};

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { nanoid } from 'nanoid';
export type ExtraType = Record<string, any>;
/**
* cache 类型
*/
export interface CacheMapType {
start: number;
disabled: boolean;
extra?: ExtraType;
}
export class Tracker {
cache = new Map<string, CacheMapType>();
start(extra?: ExtraType) {
const key = nanoid();
const start = performance.now();
const prev = this.cache.get(key);
const value = {
start,
extra,
// 如果已经存在,则永久禁用该事件上报
disabled: !!prev,
};
this.cache.set(key, value);
return key;
}
end(key: string): null | (CacheMapType & { duration: number }) {
const prev = this.cache.get(key);
if (!prev || prev.disabled) {
return null;
}
const duration = performance.now() - prev.start;
// 成功上报重置状态,等待下一次上报
this.cache.delete(key);
return { ...prev, duration };
}
}