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,20 @@
/*
* 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 { variableUtils } from './variable-utils';
export { WorkflowVariableService } from './workflow-variable-service';
export { WorkflowBatchService } from './workflow-batch-service';
export { WorkflowVariableValidationService } from './workflow-variable-validation-service';

View File

@@ -0,0 +1,670 @@
/*
* 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.
*/
/* eslint-disable max-lines */
/* eslint-disable complexity */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { isBoolean, isInteger, isNil, isNumber } from 'lodash-es';
import { nanoid } from '@flowgram-adapter/free-layout-editor';
import type {
InputTypeValueDTO,
ObjectRefExpression,
} from '@coze-workflow/base/src/types';
import {
BatchMode,
type InputValueDTO,
type InputValueVO,
type RefExpression,
ValueExpression,
type ValueExpressionDTO,
ValueExpressionType,
type VariableMetaDTO,
VariableTypeDTO,
AssistTypeDTO,
type ViewVariableMeta,
ViewVariableType,
type LiteralExpression,
type InputTypeValueVO,
reporter,
} from '@coze-workflow/base';
import { type GetKeyPathCtx } from '../core/types';
import { type WorkflowVariableService } from './workflow-variable-service';
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace variableUtils {
export const ASSIST_TYPE_TO_VIEW_TYPE: Record<
AssistTypeDTO,
ViewVariableType
> = {
[AssistTypeDTO.file]: ViewVariableType.File,
[AssistTypeDTO.image]: ViewVariableType.Image,
[AssistTypeDTO.doc]: ViewVariableType.Doc,
[AssistTypeDTO.code]: ViewVariableType.Code,
[AssistTypeDTO.ppt]: ViewVariableType.Ppt,
[AssistTypeDTO.txt]: ViewVariableType.Txt,
[AssistTypeDTO.excel]: ViewVariableType.Excel,
[AssistTypeDTO.audio]: ViewVariableType.Audio,
[AssistTypeDTO.zip]: ViewVariableType.Zip,
[AssistTypeDTO.video]: ViewVariableType.Video,
[AssistTypeDTO.svg]: ViewVariableType.Svg,
[AssistTypeDTO.voice]: ViewVariableType.Voice,
[AssistTypeDTO.time]: ViewVariableType.Time,
};
export const VIEW_TYPE_TO_ASSIST_TYPE: Partial<
Record<ViewVariableType, AssistTypeDTO>
> = Object.entries(ASSIST_TYPE_TO_VIEW_TYPE).reduce((acc, [key, value]) => {
acc[value] = Number(key);
return acc;
}, {});
/**
* 转换处 list 之外的类型
* @param type·
* @private
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export function DTOTypeToViewType(
type: VariableTypeDTO,
{
arrayItemType,
assistType,
}: {
arrayItemType?: VariableTypeDTO;
assistType?: AssistTypeDTO;
} = {},
): ViewVariableType {
switch (type) {
case VariableTypeDTO.boolean:
return ViewVariableType.Boolean;
case VariableTypeDTO.float:
return ViewVariableType.Number;
case VariableTypeDTO.integer:
return ViewVariableType.Integer;
case VariableTypeDTO.string:
if (assistType) {
const targetType = ASSIST_TYPE_TO_VIEW_TYPE[assistType];
if (targetType) {
return targetType;
}
}
return ViewVariableType.String;
case VariableTypeDTO.object:
return ViewVariableType.Object;
// 原后端 type: image 兼容
case VariableTypeDTO.image:
return ViewVariableType.Image;
case VariableTypeDTO.list:
if (!arrayItemType) {
throw new Error(
`Unkown variable DTO list need sub type but get ${arrayItemType}`,
);
}
switch (arrayItemType) {
case VariableTypeDTO.boolean:
return ViewVariableType.ArrayBoolean;
case VariableTypeDTO.float:
return ViewVariableType.ArrayNumber;
case VariableTypeDTO.integer:
return ViewVariableType.ArrayInteger;
case VariableTypeDTO.string:
if (assistType) {
const targetType = ASSIST_TYPE_TO_VIEW_TYPE[assistType];
if (targetType) {
return ViewVariableType.wrapToArrayType(targetType);
}
}
return ViewVariableType.ArrayString;
case VariableTypeDTO.object:
return ViewVariableType.ArrayObject;
case VariableTypeDTO.image:
return ViewVariableType.ArrayImage;
default:
throw new Error(
`Unknown variable DTO Type: ${type}:${arrayItemType}`,
);
}
default:
throw new Error(`Unknown variable DTO Type: ${type}:${arrayItemType}`);
}
}
export function viewTypeToDTOType(type: ViewVariableType): {
type: VariableTypeDTO;
subType?: VariableTypeDTO;
assistType?: AssistTypeDTO;
subAssistType?: AssistTypeDTO;
} {
// 如果是数组类型的变量
if (ViewVariableType.isArrayType(type)) {
const subViewType = ViewVariableType.getArraySubType(type);
const { type: subType, assistType: subAssistType } =
viewTypeToDTOType(subViewType);
return {
type: VariableTypeDTO.list,
subType,
subAssistType,
};
}
// AssistType 映射
const assistType = VIEW_TYPE_TO_ASSIST_TYPE[type];
if (assistType) {
return {
type: VariableTypeDTO.string,
assistType: Number(assistType) as AssistTypeDTO,
};
}
// 普通类型映射
switch (type) {
case ViewVariableType.String:
return { type: VariableTypeDTO.string };
case ViewVariableType.Integer:
return { type: VariableTypeDTO.integer };
case ViewVariableType.Number:
return { type: VariableTypeDTO.float };
case ViewVariableType.Boolean:
return { type: VariableTypeDTO.boolean };
case ViewVariableType.Object:
return { type: VariableTypeDTO.object };
// case ViewVariableType.Image:
// // return { type: VariableTypeDTO.image };
default:
throw new Error(`Unkonwn variable view type: ${type}`);
}
}
export const DEFAULT_OUTPUT_NAME = {
[BatchMode.Batch]: 'outputList',
[BatchMode.Single]: 'output',
};
export const ARRAY_TYPES = ViewVariableType.ArrayTypes;
/**
* 校验下Meta合法性不合法上报错误
* @param meta
*/
function checkDtoMetaValid(meta: VariableMetaDTO) {
if (!meta?.type) {
return;
}
// 非object和list类型schema有值的场景上报, 比如 { type: 'string', schema: []}
if (
![VariableTypeDTO.list, VariableTypeDTO.object].includes(meta.type) &&
meta.schema
) {
reporter.event({
eventName: 'workflow_invalid_variable_meta',
meta: {
name: meta.name,
},
});
}
}
/**
* 后端变量转前端变量,并补齐 key
* @param meta
*/
export function dtoMetaToViewMeta(meta: VariableMetaDTO): ViewVariableMeta {
checkDtoMetaValid(meta);
switch (meta.type) {
case VariableTypeDTO.list:
return {
key: nanoid(),
type: DTOTypeToViewType(meta.type, {
arrayItemType: meta.schema?.type,
assistType: meta.schema?.assistType,
}),
name: meta.name,
// 数组要多下钻一层
children: meta.schema?.schema?.map(subMeta =>
dtoMetaToViewMeta(subMeta),
),
required: meta.required,
description: meta.description,
readonly: meta.readonly,
defaultValue: meta.defaultValue,
};
default:
return {
key: nanoid(),
type: DTOTypeToViewType(meta.type, {
assistType: meta.assistType,
}),
name: meta.name,
children: meta.schema?.map(subMeta => dtoMetaToViewMeta(subMeta)),
required: meta.required,
description: meta.description,
readonly: meta.readonly,
defaultValue: meta.defaultValue,
};
// default:
// throw new Error(`Unknown variable type: ${meta.type}`);
}
}
export function viewMetaToDTOMeta(meta: ViewVariableMeta): VariableMetaDTO {
const { type, subType, assistType, subAssistType } = viewTypeToDTOType(
meta.type,
);
let schema: any = meta.children?.map(child => viewMetaToDTOMeta(child));
if (subType) {
if (!schema || schema.length === 0) {
// 空的object 需要加上空数组
if (subType === VariableTypeDTO.object) {
schema = [];
} else {
schema = undefined;
}
}
schema = {
type: subType,
assistType: subAssistType,
schema,
};
} else if (type === VariableTypeDTO.object && !schema) {
// 空 object 需要加上空数组
schema = [];
}
return {
type,
assistType,
name: meta.name,
schema,
readonly: meta.readonly,
required: meta.required,
description: meta.description,
defaultValue: meta.defaultValue,
};
}
/**
* @deprecated 使用 viewTypeToDTOType
* @param type
* @returns
*/
function getAssistTypeByViewType(
type?: ViewVariableType,
): AssistTypeDTO | undefined {
if (isNil(type)) {
return undefined;
}
return VIEW_TYPE_TO_ASSIST_TYPE[
ViewVariableType.isArrayType(type)
? ViewVariableType.getArraySubType(type)
: type
];
}
/**
* 前端表达式转后端数据
* @param value
*/
export function valueExpressionToDTO(
value: ValueExpression | undefined,
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): ValueExpressionDTO {
if (value?.rawMeta?.type) {
const viewType = value?.rawMeta?.type as ViewVariableType;
const {
type: dtoType,
assistType,
subType,
subAssistType,
} = viewTypeToDTOType(viewType);
if (value.type === ValueExpressionType.LITERAL) {
let schema: any = undefined;
// Array<T> 类型的 schema 指定 array 的泛型类型
if (dtoType === VariableTypeDTO.list) {
schema = {
type: subType,
assistType: subAssistType,
};
if (subType === VariableTypeDTO.object) {
schema.schema = [];
}
// object 类型的 schema 指定成空数组,字面量没有下钻字段信息
} else if (dtoType === VariableTypeDTO.object) {
schema = [];
}
// 其他基础类型string、int、number、boolean以及 image 等带 assistType 额类型,不传 schema。
const res: ValueExpressionDTO = {
type: dtoType,
assistType,
value: {
type: 'literal',
content: value.content ?? '',
rawMeta: value.rawMeta,
},
};
if (schema) {
res.schema = schema;
}
return res;
} else {
const refExpression = service.refExpressionToDTO(
value as RefExpression,
ctx,
);
let schema = subType
? {
...refExpression.schema,
type: subType,
assistType: subAssistType,
}
: refExpression.schema;
// 变量选择复杂类型再将类型手动改成简单类型会有schema残留
// 只有 object 和 list 类型才需要 schema
if (![VariableTypeDTO.object, VariableTypeDTO.list].includes(dtoType)) {
schema = undefined;
}
// rawMeta 里有类型时,使用 rawMeta 里的类型,后端会对引用变量进行类型转换
return {
type: dtoType,
assistType,
schema,
value: {
...refExpression.value,
rawMeta: value.rawMeta,
},
};
}
}
// rawMeta 不存在时,需要走兜底逻辑
if (value && value.type === ValueExpressionType.LITERAL) {
const assistType = getAssistTypeByViewType(value?.rawMeta?.type);
// TODO 这里获取不到变量类型,只能简单先这么处理,需要重构解决
if (Array.isArray(value.content)) {
const listRes: ValueExpressionDTO = {
type: 'list',
schema: {
type: 'string',
},
value: {
type: 'literal',
content: value.content ?? '',
rawMeta: value.rawMeta,
},
};
if (!isNil(assistType)) {
listRes.schema.assistType = assistType;
}
return listRes;
}
const res: ValueExpressionDTO = {
type: getLiteralExpressionValueDTOType(value.content),
value: {
type: 'literal',
content: !isNil(value.content) ? String(value.content) : '',
rawMeta: value.rawMeta,
},
};
if (!isNil(assistType)) {
res.assistType = assistType;
}
return res;
}
return service.refExpressionToDTO(value as RefExpression, ctx);
}
export function getValueExpressionViewType(
value: ValueExpression,
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): ViewVariableType | undefined {
if (ValueExpression.isEmpty(value)) {
return undefined;
}
const rawMetaType = value.rawMeta?.type;
if (rawMetaType) {
return rawMetaType;
}
if (ValueExpression.isRef(value)) {
return service.getWorkflowVariableByKeyPath(value.content?.keyPath, ctx)
?.viewType;
}
if (ValueExpression.isLiteral(value)) {
const dtoType = getLiteralExpressionValueDTOType(value.content);
return dtoType ? DTOTypeToViewType(dtoType) : undefined;
}
}
export function getValueExpressionDTOMeta(
value: ValueExpression,
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): VariableMetaDTO | undefined {
if (ValueExpression.isEmpty(value)) {
return undefined;
}
const rawMetaType = value.rawMeta?.type;
if (ValueExpression.isRef(value)) {
const workflowVariable = service.getWorkflowVariableByKeyPath(
value.content?.keyPath,
ctx,
);
const refVariableType = workflowVariable?.viewType;
// 如果 rawMetaType 不存在或者 rawMetaType 与 refVariableType 相同,则直接返回 workflowVariable?.dtoMeta
if (!rawMetaType || refVariableType === rawMetaType) {
return workflowVariable?.dtoMeta;
}
}
if (!rawMetaType) {
return undefined;
}
// 如果 rawMetaType 存在但与 refVariableType 不同,说明发生了类型转换,则需要根据 rawMetaType 转换为 VariableMetaDTO
return viewMetaToDTOMeta({
key: nanoid(),
name: String(value.content ?? ''),
type: rawMetaType,
});
}
/**
* 优先使用 literalExpression rawMeta.type 字段获取 literal 类型, 参考 variableUtils.valueExpressionToDTO
* @param content
* @returns
*/
export function getLiteralExpressionValueDTOType(
content: LiteralExpression['content'],
) {
if (isNil(content)) {
return VariableTypeDTO.string;
}
if (isInteger(content)) {
return VariableTypeDTO.integer;
} else if (isNumber(content)) {
return VariableTypeDTO.float;
} else if (isBoolean(content)) {
return VariableTypeDTO.boolean;
} else {
return VariableTypeDTO.string;
}
}
export function getLiteralValueWithType(
type: VariableTypeDTO,
content?: any,
) {
if (type === VariableTypeDTO.float || type === VariableTypeDTO.integer) {
return isNumber(Number(content)) ? Number(content) : content;
} else if (type === VariableTypeDTO.boolean) {
return ![false, 'false'].includes(content);
} else {
return content;
}
}
/**
* 后端表达式转前端数据
* @param value
*/
export function valueExpressionToVO(
value: ValueExpressionDTO,
service: WorkflowVariableService,
): ValueExpression {
// 空数据兜底
if (!value?.value?.type) {
return {} as any;
}
if (value.value.type === 'literal') {
return {
type: ValueExpressionType.LITERAL,
content: getLiteralValueWithType(
value.type as VariableTypeDTO,
value.value.content as string,
),
rawMeta: value.value.rawMeta,
};
}
const refExpression = service.refExpressionToVO(value);
refExpression.rawMeta = value.value.rawMeta;
return refExpression;
}
export function inputObjectRefToDTO(
value: InputValueVO,
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): InputValueDTO | undefined {
const schema = value.children
?.map(child => inputValueToDTO(child, service, ctx))
.filter(Boolean) as InputValueDTO[] | undefined;
const dto: InputValueDTO = {
name: value.name,
input: {
value: {
type: 'object_ref',
},
type: 'object',
schema,
},
};
return dto;
}
export function inputValueToDTO(
value: InputValueVO,
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): InputValueDTO | undefined {
if (ValueExpression.isObjectRef(value.input)) {
return inputObjectRefToDTO(value, service, ctx);
}
if (ValueExpression.isEmpty(value.input)) {
return undefined;
}
const dto: InputValueDTO = {
name: value.name,
input: valueExpressionToDTO(value.input, service, ctx),
};
return dto;
}
export function inputObjectRefToVO(
value: InputValueDTO,
service: WorkflowVariableService,
): InputValueVO {
const input: ObjectRefExpression = {
type: ValueExpressionType.OBJECT_REF,
rawMeta: { type: ViewVariableType.Object },
};
const vo: InputValueVO = {
name: value.name,
key: nanoid(),
input,
children: (value.input?.schema || [])
.map(child => inputValueToVO(child, service))
.filter(Boolean),
};
return vo;
}
export function inputValueToVO(
value: InputValueDTO,
service: WorkflowVariableService,
): InputValueVO {
if (value.input?.value?.type === 'object_ref') {
return inputObjectRefToVO(value, service);
}
const vo: InputValueVO = {
name: value.name,
input: valueExpressionToVO(value.input, service) as any,
};
return vo;
}
/**
* input-type-value 前端格式转后端格式
*/
export function inputTypeValueVOToDTO(
value: InputTypeValueVO[],
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): InputTypeValueDTO[] {
return value.map(param => {
const transType = variableUtils.viewTypeToDTOType(param.type);
return {
name: param.name,
input: variableUtils.valueExpressionToDTO(param.input, service, ctx),
type: transType.type,
};
});
}
/**
* input-type-value 后端格式转前端格式
*/
export function inputTypeValueDTOToVO(
value: InputTypeValueDTO[],
service: WorkflowVariableService,
ctx: GetKeyPathCtx,
): InputTypeValueVO[] {
return value.map(param => ({
name: param.name,
input: variableUtils.valueExpressionToVO(param.input, service),
type: variableUtils.DTOTypeToViewType(param.type),
}));
}
}

View File

@@ -0,0 +1,83 @@
/*
* 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 { inject, injectable } from 'inversify';
import { EntityManager } from '@flowgram-adapter/free-layout-editor';
import { nanoid } from '@flowgram-adapter/free-layout-editor';
import { BatchMode } from '@coze-workflow/base';
import { type ViewVariableTreeNode, ViewVariableType } from '../typings';
import { WorkflowVariableService } from './workflow-variable-service';
import { variableUtils } from './variable-utils';
@injectable()
export class WorkflowBatchService {
@inject(WorkflowVariableService)
readonly variablesService: WorkflowVariableService;
@inject(EntityManager) readonly entityManager: EntityManager;
static singleOutputMetasToList(
metas: ViewVariableTreeNode[] | undefined,
): ViewVariableTreeNode[] {
const singleMetas = metas || [
WorkflowBatchService.getDefaultBatchModeOutputMeta(BatchMode.Single),
];
return [
{
key: nanoid(),
type: ViewVariableType.ArrayObject,
name: variableUtils.DEFAULT_OUTPUT_NAME[BatchMode.Batch],
children: singleMetas,
},
];
}
static listOutputMetasToSingle(
metas: ViewVariableTreeNode[] | undefined,
): ViewVariableTreeNode[] | undefined {
const listMetas = metas || [
WorkflowBatchService.getDefaultBatchModeOutputMeta(BatchMode.Batch),
];
return listMetas[0].children;
}
static getDefaultBatchModeOutputMeta = (
batchMode: BatchMode,
): ViewVariableTreeNode => {
if (batchMode === BatchMode.Batch) {
return {
key: nanoid(),
type: ViewVariableType.ArrayObject,
name: variableUtils.DEFAULT_OUTPUT_NAME[BatchMode.Batch],
children: [
{
key: nanoid(),
type: ViewVariableType.ArrayString,
name: variableUtils.DEFAULT_OUTPUT_NAME[BatchMode.Single],
},
],
};
}
if (batchMode === BatchMode.Single) {
return {
key: nanoid(),
type: ViewVariableType.String,
name: variableUtils.DEFAULT_OUTPUT_NAME[BatchMode.Single],
};
}
throw new Error('WorkflowBatchService Error: Unknown batchMode');
};
}

View File

@@ -0,0 +1,230 @@
/*
* 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 { inject, injectable } from 'inversify';
import { EntityManager } from '@flowgram-adapter/free-layout-editor';
import { type Disposable } from '@flowgram-adapter/common';
import {
type DTODefine,
type RefExpression,
type ValueExpressionDTO,
ValueExpressionType,
type ViewVariableMeta,
ViewVariableType,
} from '@coze-workflow/base';
import { type GetKeyPathCtx } from '../core/types';
import { type WorkflowVariable, WorkflowVariableFacadeService } from '../core';
import { type GlobalVariableKey, isGlobalVariableKey } from '../constants';
/**
* 变量相关服务
*/
@injectable()
export class WorkflowVariableService {
@inject(EntityManager) protected readonly entityManager: EntityManager;
@inject(WorkflowVariableFacadeService)
protected readonly variableFacadeService: WorkflowVariableFacadeService;
/**
* 表达式引用转后端,如果无数据,默认给一个空的 ref 引用
* 输入:
* {
* type: ValueExpressionType.REF,
* content: {
* keyPath: ['nodeId', 'xxx', 'xxx']
* }
*
* }
*/
refExpressionToDTO(
refExpression: RefExpression | undefined,
ctx: GetKeyPathCtx,
): ValueExpressionDTO {
const keyPath = refExpression?.content?.keyPath || [];
const workflowVariable =
this.variableFacadeService.getVariableFacadeByKeyPath(keyPath, ctx);
const dtoMeta = workflowVariable?.dtoMeta;
// 如果引用的变量,属于全局变量
if (isGlobalVariableKey(keyPath[0])) {
const path = workflowVariable
? [
...workflowVariable.parentVariables
.slice(1)
.map(_variable => {
// Hack: 全局变量特化逻辑,后端要求数组下钻把数组的下标也带上
if (
_variable.viewType &&
ViewVariableType.isArrayType(_variable.viewType)
) {
return [_variable.key, '[0]'];
}
return [_variable.key];
})
.flat(),
workflowVariable.key,
]
: // 没有 workflowVariable则直接使用原来的 keyPath
keyPath.slice(1);
return {
type: dtoMeta?.type || 'string',
schema: dtoMeta?.schema,
assistType: dtoMeta?.assistType,
value: {
type: 'ref',
content: {
source: keyPath[0] as GlobalVariableKey,
path,
blockID: '',
name: '',
},
},
};
}
return {
type: dtoMeta?.type || 'string',
schema: dtoMeta?.schema,
assistType: dtoMeta?.assistType,
value: {
type: 'ref',
content: {
source: 'block-output',
blockID: keyPath[0] || '',
name: keyPath.slice(1).join('.'),
},
},
};
}
/**
* 表达式引用转前端
* @param value
*/
refExpressionToVO(valueDTO: ValueExpressionDTO): RefExpression {
const value = valueDTO?.value as DTODefine.RefExpression;
if (!value) {
return {
type: ValueExpressionType.REF,
content: {
keyPath: [],
},
};
}
if (value.content?.source?.startsWith('global_variable_')) {
const { source, path } =
(value.content as {
source: `global_variable_${string}`;
path: string[];
}) || {};
return {
type: ValueExpressionType.REF,
content: {
keyPath: [
source,
// Hack: 全局变量特化逻辑,后端要求数组下钻把数组的下标也带上,前端不需要 [0] 下钻,因此转化时过滤掉
...(path || []).filter(_v => !['[0]'].includes(_v)),
],
},
};
}
const name = value.content?.name || '';
const nameList = name.split('.').filter(Boolean); // 过滤空字符串
// 灰度命中时,直接使用 namePath
return {
type: ValueExpressionType.REF,
content: {
keyPath: [value.content?.blockID || '', ...nameList],
},
};
}
/**
* 直接返回 Variable 或者 SubVariable 的 ViewVariableMeta
* @param keyPath ViewVariableMeta 的 keyPath 路径
* @returns
*/
getViewVariableByKeyPath(
keyPath: string[] | undefined,
ctx: GetKeyPathCtx,
): ViewVariableMeta | null {
return (
this.variableFacadeService.getVariableFacadeByKeyPath(keyPath, ctx)
?.viewMeta || null
);
}
getWorkflowVariableByKeyPath(
keyPath: string[] | undefined,
ctx: GetKeyPathCtx,
): WorkflowVariable | undefined {
return this.variableFacadeService.getVariableFacadeByKeyPath(keyPath, ctx);
}
/**
* 监听指定 keyPath 变量类型变化
* @param keyPath
* @param cb
* @returns
*/
onListenVariableTypeChange(
keyPath: string[],
cb: (v?: ViewVariableMeta | null) => void,
ctx: GetKeyPathCtx,
): Disposable {
return this.variableFacadeService.listenKeyPathTypeChange(keyPath, cb, ctx);
}
/**
* @deprecated 变量销毁存在部分 Bad Case
* - 全局变量因切换 Project 销毁后,变量引用会被置空,导致变量引用失效
*
* 监听指定 keyPath 变量类型变化
* @param keyPath
* @param cb
* @returns
*/
onListenVariableDispose(
keyPath: string[],
cb: () => void,
ctx: GetKeyPathCtx,
): Disposable {
return this.variableFacadeService.listenKeyPathDispose(keyPath, cb, ctx);
}
/**
* 监听指定 keyPath 变量的变化
* @param keyPath
* @param cb
* @returns
*/
onListenVariableChange(
keyPath: string[],
cb: (v?: ViewVariableMeta | null) => void,
ctx: GetKeyPathCtx,
): Disposable {
return this.variableFacadeService.listenKeyPathVarChange(keyPath, cb, ctx);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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 { inject, injectable } from 'inversify';
import { type WorkflowNodeEntity } from '@flowgram-adapter/free-layout-editor';
import { type RefExpression } from '@coze-workflow/base';
import { I18n } from '@coze-arch/i18n';
import { WorkflowVariableFacadeService } from '../core';
import { WorkflowVariableService } from './workflow-variable-service';
@injectable()
export class WorkflowVariableValidationService {
@inject(WorkflowVariableService)
protected readonly variableService: WorkflowVariableService;
@inject(WorkflowVariableFacadeService)
protected readonly variableFacadeService: WorkflowVariableFacadeService;
isRefVariableEligible(value: RefExpression, node: WorkflowNodeEntity) {
const variable = this.variableFacadeService.getVariableFacadeByKeyPath(
value?.content?.keyPath,
{ node },
);
if (!variable || !variable.canAccessByNode(node.id)) {
return I18n.t('workflow_detail_variable_referenced_error');
}
return;
}
}