chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,540 @@
import { getEnv, isTruthy } from '@/lib/core/config/env'
import { isHosted } from '@/lib/core/config/env-flags'
import type { SubBlockConfig } from '@/blocks/types'
export type CanonicalMode = 'basic' | 'advanced'
export interface CanonicalGroup {
canonicalId: string
basicId?: string
advancedIds: string[]
}
export interface CanonicalIndex {
groupsById: Record<string, CanonicalGroup>
canonicalIdBySubBlockId: Record<string, string>
}
export interface SubBlockCondition {
field: string
value: string | number | boolean | Array<string | number | boolean> | undefined
not?: boolean
and?: SubBlockCondition
}
export interface CanonicalModeOverrides {
[canonicalId: string]: CanonicalMode | undefined
}
export interface CanonicalValueSelection {
basicValue: unknown
advancedValue: unknown
advancedSourceId?: string
}
interface TriggerVisibilityBlockConfig {
category?: string
triggers?: {
enabled?: boolean
}
}
export function parseDependsOn(dependsOn: SubBlockConfig['dependsOn']): {
allFields: string[]
anyFields: string[]
allDependsOnFields: string[]
} {
if (!dependsOn) {
return { allFields: [], anyFields: [], allDependsOnFields: [] }
}
if (Array.isArray(dependsOn)) {
return { allFields: dependsOn, anyFields: [], allDependsOnFields: dependsOn }
}
const allFields = dependsOn.all || []
const anyFields = dependsOn.any || []
return {
allFields,
anyFields,
allDependsOnFields: [...allFields, ...anyFields],
}
}
export function normalizeDependencyValue(rawValue: unknown): unknown {
if (rawValue === null || rawValue === undefined) return null
if (typeof rawValue === 'object') {
if (Array.isArray(rawValue)) {
if (rawValue.length === 0) return null
return rawValue.map((item) => normalizeDependencyValue(item))
}
const record = rawValue as Record<string, unknown>
if ('value' in record) return normalizeDependencyValue(record.value)
if ('id' in record) return record.id
return record
}
return rawValue
}
/**
* Build a flat map of subblock values keyed by subblock id.
*/
export function buildSubBlockValues(
subBlocks: Record<string, { value?: unknown } | null | undefined>
): Record<string, unknown> {
return Object.entries(subBlocks).reduce<Record<string, unknown>>((acc, [key, subBlock]) => {
acc[key] = subBlock?.value
return acc
}, {})
}
/**
* Build canonical group indices for a block's subblocks.
*/
export function buildCanonicalIndex(subBlocks: SubBlockConfig[]): CanonicalIndex {
const groupsById: Record<string, CanonicalGroup> = {}
const canonicalIdBySubBlockId: Record<string, string> = {}
subBlocks.forEach((subBlock) => {
if (!subBlock.canonicalParamId) return
const canonicalId = subBlock.canonicalParamId
if (!groupsById[canonicalId]) {
groupsById[canonicalId] = { canonicalId, advancedIds: [] }
}
const group = groupsById[canonicalId]
if (subBlock.mode === 'advanced' || subBlock.mode === 'trigger-advanced') {
// Deduplicate: trigger spreads may repeat the same advanced ID as the regular block
if (!group.advancedIds.includes(subBlock.id)) {
group.advancedIds.push(subBlock.id)
}
} else {
// A trigger-mode subblock must not overwrite a basicId already claimed by a non-trigger subblock.
// Blocks spread their trigger's subBlocks after their own, so the regular subblock always wins.
if (!group.basicId || subBlock.mode !== 'trigger') {
group.basicId = subBlock.id
}
}
canonicalIdBySubBlockId[subBlock.id] = canonicalId
})
return { groupsById, canonicalIdBySubBlockId }
}
/**
* Resolve if a canonical group is a swap pair (basic + advanced).
*/
export function isCanonicalPair(group?: CanonicalGroup): boolean {
return Boolean(group?.basicId && group?.advancedIds?.length)
}
/**
* Builds default canonical mode overrides for a block's subblocks.
* All canonical pairs default to `'basic'`.
*/
export function buildDefaultCanonicalModes(
subBlocks: SubBlockConfig[]
): Record<string, 'basic' | 'advanced'> {
const index = buildCanonicalIndex(subBlocks)
const modes: Record<string, 'basic' | 'advanced'> = {}
for (const group of Object.values(index.groupsById)) {
if (isCanonicalPair(group)) {
modes[group.canonicalId] = 'basic'
}
}
return modes
}
/**
* Determine the active mode for a canonical group.
*/
export function resolveCanonicalMode(
group: CanonicalGroup,
values: Record<string, unknown>,
overrides?: CanonicalModeOverrides
): CanonicalMode {
const override = overrides?.[group.canonicalId]
if (override === 'advanced' && group.advancedIds.length > 0) return 'advanced'
if (override === 'basic' && group.basicId) return 'basic'
const { basicValue, advancedValue } = getCanonicalValues(group, values)
const hasBasic = isNonEmptyValue(basicValue)
const hasAdvanced = isNonEmptyValue(advancedValue)
if (!group.basicId) return 'advanced'
if (!hasBasic && hasAdvanced) return 'advanced'
return 'basic'
}
/**
* Evaluate a subblock condition against a map of raw values.
*/
export function evaluateSubBlockCondition(
condition:
| SubBlockCondition
| ((values?: Record<string, unknown>) => SubBlockCondition)
| undefined,
values: Record<string, unknown>
): boolean {
if (!condition) return true
const actual = typeof condition === 'function' ? condition(values) : condition
const fieldValue = values[actual.field]
const valueMatch = Array.isArray(actual.value)
? fieldValue != null &&
(actual.not
? !actual.value.includes(fieldValue as any)
: actual.value.includes(fieldValue as any))
: actual.not
? fieldValue !== actual.value
: fieldValue === actual.value
const andMatch = !actual.and
? true
: (() => {
const andFieldValue = values[actual.and!.field]
const andValueMatch = Array.isArray(actual.and!.value)
? andFieldValue != null &&
(actual.and!.not
? !actual.and!.value.includes(andFieldValue as any)
: actual.and!.value.includes(andFieldValue as any))
: actual.and!.not
? andFieldValue !== actual.and!.value
: andFieldValue === actual.and!.value
return andValueMatch
})()
return valueMatch && andMatch
}
/**
* Check if a value is considered set for advanced visibility/selection.
*/
export function isNonEmptyValue(value: unknown): boolean {
if (value === null || value === undefined) return false
if (typeof value === 'string') return value.trim().length > 0
if (Array.isArray(value)) return value.length > 0
return true
}
/**
* Resolve basic and advanced values for a canonical group.
*/
export function getCanonicalValues(
group: CanonicalGroup,
values: Record<string, unknown>
): CanonicalValueSelection {
const basicValue = group.basicId ? values[group.basicId] : undefined
let advancedValue: unknown
let advancedSourceId: string | undefined
group.advancedIds.forEach((advancedId) => {
if (advancedValue !== undefined) return
const candidate = values[advancedId]
if (isNonEmptyValue(candidate)) {
advancedValue = candidate
advancedSourceId = advancedId
}
})
return { basicValue, advancedValue, advancedSourceId }
}
/**
* Resolve the ACTIVE canonical member's value for a group: the basic value in basic mode, the
* advanced value in advanced mode (per {@link resolveCanonicalMode} - honoring an explicit
* override, then the value heuristic). Strict: returns ONLY the active member's value with no
* cross-mode fallback, so a dormant mode's stale value can never leak. The single source of truth
* for "what value is live for this canonical pair" - use it instead of basic-first `||` /
* `?? 'basic'` reads or last-write-wins scans.
*/
export function resolveActiveCanonicalValue(
group: CanonicalGroup,
values: Record<string, unknown>,
overrides?: CanonicalModeOverrides
): unknown {
const mode = resolveCanonicalMode(group, values, overrides)
const { basicValue, advancedValue } = getCanonicalValues(group, values)
return mode === 'advanced' ? advancedValue : basicValue
}
/** Extract override entries matching a `${prefix}` key into a bare-`canonicalId`-keyed object. */
function extractPrefixedModes(
overrides: CanonicalModeOverrides,
prefix: string
): CanonicalModeOverrides | undefined {
let scoped: CanonicalModeOverrides | undefined
for (const [key, value] of Object.entries(overrides)) {
if (key.startsWith(prefix) && value) {
scoped = scoped ?? {}
scoped[key.slice(prefix.length)] = value
}
}
return scoped
}
/**
* Strip the `${toolIndex}:` prefix from canonical-mode override keys, returning the overrides for a
* nested tool keyed by bare `canonicalId`. An agent block stores its nested tools' modes scoped as
* `${toolIndex}:${canonicalId}` — keyed by the tool's position in the `tool-input` array, not its
* `type` — so that two tool entries of the SAME type (e.g. two Table tools on one Agent block) get
* independent canonical modes instead of colliding on a shared `${toolType}:${canonicalId}` key.
*
* Falls back to the legacy `${legacyToolType}:` prefix (the pre-instance-scoping format) when no
* index-scoped key matches, so an override saved before this scoping change isn't silently dropped -
* it keeps applying (type-shared, the old behavior) until the user re-toggles it explicitly, at which
* point it's rewritten under the new index-scoped key.
*
* Returns `undefined` when there are no overrides, no `toolIndex`, and no legacy match.
*/
export function scopeCanonicalModesForTool(
overrides: CanonicalModeOverrides | undefined,
toolIndex: number | undefined,
legacyToolType?: string
): CanonicalModeOverrides | undefined {
if (!overrides) return undefined
const scoped =
toolIndex !== undefined ? extractPrefixedModes(overrides, `${toolIndex}:`) : undefined
if (scoped) return scoped
return legacyToolType ? extractPrefixedModes(overrides, `${legacyToolType}:`) : undefined
}
const INDEX_SCOPED_KEY = /^(\d+):(.+)$/
/**
* Canonical-mode overrides are keyed by a tool's position in its `tool-input` array
* (`${toolIndex}:${canonicalId}`), so anything that reorders or removes tools - the editor
* (drag-reorder, remove, delete), fork/promote copy (dropping an unresolved custom-tool/MCP
* entry) - must carry each surviving tool's overrides to its new position and DROP the
* vacated index. Otherwise a saved basic/advanced choice can attach to whichever DIFFERENT
* tool later lands on that old index (e.g. a newly-added tool, always appended at the end,
* can refill a slot a removal just freed).
*
* Returns the full replacement `canonicalModes` object (for an atomic whole-map write - a
* per-key merge can't drop a key, and sequential per-key writes can clobber each other when
* two tools swap positions), or `undefined` when nothing needs to change. A legacy,
* non-index-scoped key (the `${toolType}:` fallback format) isn't tied to any array position,
* so it's carried over unchanged.
*/
export function reindexCanonicalModesByPosition(
newIndexByOldIndex: ReadonlyMap<number, number>,
overrides: CanonicalModeOverrides | undefined
): Record<string, 'basic' | 'advanced'> | undefined {
if (!overrides) return undefined
let changed = false
const result: Record<string, 'basic' | 'advanced'> = {}
for (const [key, mode] of Object.entries(overrides)) {
if (!mode) continue
const match = INDEX_SCOPED_KEY.exec(key)
if (!match) {
result[key] = mode
continue
}
const newIndex = newIndexByOldIndex.get(Number(match[1]))
if (newIndex === undefined) {
changed = true // Tool removed (or an already-stale index) - drop the key.
continue
}
if (newIndex !== Number(match[1])) changed = true
result[`${newIndex}:${match[2]}`] = mode
}
return changed ? result : undefined
}
/**
* {@link reindexCanonicalModesByPosition}, diffing `oldTools` against `newTools` by OBJECT
* IDENTITY to derive the old-index -> new-index map. Callers must not clone the tool objects
* they keep (only filter/splice/reorder the array itself) - a kept-but-cloned tool (e.g. a
* `{ ...tool, someField: x }` spread) won't match its old reference and will be treated as
* removed. Use {@link reindexCanonicalModesByPosition} directly when a caller's remap can
* clone kept entries (fork/promote does, to rewrite a remapped id) and already knows each
* surviving old index's new position by other means (e.g. tracking it during the same pass
* that builds the new array, rather than post-hoc identity comparison).
*/
export function reindexToolCanonicalModes<T>(
oldTools: readonly T[],
newTools: readonly T[],
overrides: CanonicalModeOverrides | undefined
): Record<string, 'basic' | 'advanced'> | undefined {
const newIndexByRef = new Map(newTools.map((tool, index) => [tool, index]))
const newIndexByOldIndex = new Map<number, number>()
oldTools.forEach((tool, oldIndex) => {
const newIndex = newIndexByRef.get(tool)
if (newIndex !== undefined) newIndexByOldIndex.set(oldIndex, newIndex)
})
return reindexCanonicalModesByPosition(newIndexByOldIndex, overrides)
}
/**
* Check if a block has any standalone advanced-only fields (not part of canonical pairs).
* These require the block-level advanced mode toggle to be visible.
*/
export function hasStandaloneAdvancedFields(
subBlocks: SubBlockConfig[],
canonicalIndex: CanonicalIndex
): boolean {
for (const subBlock of subBlocks) {
if (!isStandaloneAdvancedMode(subBlock.mode)) continue
if (!canonicalIndex.canonicalIdBySubBlockId[subBlock.id]) return true
}
return false
}
/**
* True for the modes that make a field advanced-only when it is not part of a
* canonical basic/advanced pair: a standalone `advanced` field, or a standalone
* `trigger-advanced` field (an advanced option on a trigger block). Both are
* hidden until the block-level advanced toggle is on.
*/
export function isStandaloneAdvancedMode(mode: SubBlockConfig['mode']): boolean {
return mode === 'advanced' || mode === 'trigger-advanced'
}
/**
* Check if any advanced-only or canonical advanced values are present.
*/
export function hasAdvancedValues(
subBlocks: SubBlockConfig[],
values: Record<string, unknown>,
canonicalIndex: CanonicalIndex
): boolean {
const checkedCanonical = new Set<string>()
for (const subBlock of subBlocks) {
const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlock.id]
if (canonicalId) {
const group = canonicalIndex.groupsById[canonicalId]
if (group && isCanonicalPair(group) && !checkedCanonical.has(canonicalId)) {
checkedCanonical.add(canonicalId)
const { advancedValue } = getCanonicalValues(group, values)
if (isNonEmptyValue(advancedValue)) return true
}
continue
}
if (isStandaloneAdvancedMode(subBlock.mode) && isNonEmptyValue(values[subBlock.id])) {
return true
}
}
return false
}
/**
* Determine whether a subblock is visible based on mode and canonical swaps.
*/
export function isSubBlockVisibleForMode(
subBlock: SubBlockConfig,
displayAdvancedOptions: boolean,
canonicalIndex: CanonicalIndex,
values: Record<string, unknown>,
overrides?: CanonicalModeOverrides
): boolean {
const canonicalId = canonicalIndex.canonicalIdBySubBlockId[subBlock.id]
const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined
if (group && isCanonicalPair(group)) {
const mode = resolveCanonicalMode(group, values, overrides)
if (mode === 'advanced') return group.advancedIds.includes(subBlock.id)
return group.basicId === subBlock.id
}
if (subBlock.mode === 'basic' && displayAdvancedOptions) return false
// Standalone advanced-only fields (`advanced` or a trigger's `trigger-advanced`)
// hide until the block-level advanced toggle is on.
if (isStandaloneAdvancedMode(subBlock.mode) && !displayAdvancedOptions) return false
return true
}
export function isTriggerModeSubBlock(subBlock: Pick<SubBlockConfig, 'mode'>): boolean {
return subBlock.mode === 'trigger' || subBlock.mode === 'trigger-advanced'
}
export function isTriggerConfigSubBlock(subBlock: Pick<SubBlockConfig, 'type'>): boolean {
return String(subBlock.type) === 'trigger-config'
}
export function shouldUseSubBlockForTriggerModeCanonicalIndex(
subBlock: Pick<SubBlockConfig, 'mode' | 'type'>
): boolean {
return isTriggerModeSubBlock(subBlock) || isTriggerConfigSubBlock(subBlock)
}
export function isPureTriggerBlockConfig(blockConfig?: TriggerVisibilityBlockConfig): boolean {
return Boolean(blockConfig?.triggers?.enabled && blockConfig.category === 'triggers')
}
export function isSubBlockVisibleForTriggerMode(
subBlock: Pick<SubBlockConfig, 'mode' | 'type'>,
displayTriggerMode: boolean,
blockConfig?: TriggerVisibilityBlockConfig
): boolean {
if (isTriggerConfigSubBlock(subBlock)) {
return displayTriggerMode || isPureTriggerBlockConfig(blockConfig)
}
if (isTriggerModeSubBlock(subBlock)) return displayTriggerMode
if (displayTriggerMode) return false
return true
}
/**
* Resolve the dependency value for a dependsOn key, honoring canonical swaps.
*/
export function resolveDependencyValue(
dependencyKey: string,
values: Record<string, unknown>,
canonicalIndex: CanonicalIndex,
overrides?: CanonicalModeOverrides
): unknown {
const canonicalId =
canonicalIndex.groupsById[dependencyKey]?.canonicalId ||
canonicalIndex.canonicalIdBySubBlockId[dependencyKey]
if (!canonicalId) {
return values[dependencyKey]
}
const group = canonicalIndex.groupsById[canonicalId]
if (!group) return values[dependencyKey]
const { basicValue, advancedValue } = getCanonicalValues(group, values)
const mode = resolveCanonicalMode(group, values, overrides)
const canonicalResult =
mode === 'advanced' ? (advancedValue ?? basicValue) : (basicValue ?? advancedValue)
if (canonicalResult != null) return canonicalResult
for (const [memberId, memberCanonicalId] of Object.entries(
canonicalIndex.canonicalIdBySubBlockId
)) {
if (memberCanonicalId === canonicalId && isNonEmptyValue(values[memberId])) {
return values[memberId]
}
}
return values[dependencyKey]
}
/**
* Check if a subblock is gated by a feature flag.
*/
export function isSubBlockFeatureEnabled(subBlock: SubBlockConfig): boolean {
if (!subBlock.showWhenEnvSet) return true
return isTruthy(getEnv(subBlock.showWhenEnvSet))
}
/**
* Check if a subblock should be hidden based on environment conditions.
* Covers two cases:
* - `hideWhenHosted`: hidden when running on hosted Sim (tool API key fields)
* - `hideWhenEnvSet`: hidden when a specific NEXT_PUBLIC_ env var is truthy
* (credential fields hidden when the deployment provides them server-side)
*/
export function isSubBlockHidden(subBlock: SubBlockConfig): boolean {
if (subBlock.hideWhenHosted && isHosted) return true
if (subBlock.hideWhenEnvSet && isTruthy(getEnv(subBlock.hideWhenEnvSet))) return true
return false
}