feat(slack): ability to have DM channels as destination for slack tools (#2388)

* feat(slack): tool to allow dms

* don't make new tool but separate out destination

* add log for message limit

* consolidate slack selector code

* add scopes correctly

* fix zod validation

* update message logs

* add console logs

* fix

* remove from tools where feature not needed

* add correct condition

* fix type

* fix cond eval logic
This commit is contained in:
Vikhyath Mondreti
2025-12-15 17:39:53 -08:00
committed by GitHub
parent bdcc42e566
commit 300aaa5368
23 changed files with 911 additions and 460 deletions

View File

@@ -38,6 +38,57 @@ function shouldIncludeField(subBlockConfig: SubBlockConfig, isAdvancedMode: bool
return true
}
/**
* Evaluates a condition object against current field values.
* Used to determine if a conditionally-visible field should be included in params.
*/
function evaluateCondition(
condition:
| {
field: string
value: any
not?: boolean
and?: { field: string; value: any; not?: boolean }
}
| (() => {
field: string
value: any
not?: boolean
and?: { field: string; value: any; not?: boolean }
})
| undefined,
values: Record<string, any>
): boolean {
if (!condition) return true
const actual = typeof condition === 'function' ? condition() : condition
const fieldValue = values[actual.field]
const valueMatch = Array.isArray(actual.value)
? fieldValue != null &&
(actual.not ? !actual.value.includes(fieldValue) : actual.value.includes(fieldValue))
: 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)
: actual.and!.value.includes(andFieldValue))
: actual.and!.not
? andFieldValue !== actual.and!.value
: andFieldValue === actual.and!.value
return andValueMatch
})()
return valueMatch && andMatch
}
/**
* Helper function to migrate agent block params from old format to messages array
* Transforms systemPrompt/userPrompt into messages array format
@@ -343,9 +394,15 @@ export class Serializer {
const isStarterBlock = block.type === 'starter'
const isAgentBlock = block.type === 'agent'
// First collect all current values from subBlocks, filtering by mode
// First pass: collect ALL raw values for condition evaluation
const allValues: Record<string, any> = {}
Object.entries(block.subBlocks).forEach(([id, subBlock]) => {
// Find the corresponding subblock config to check its mode
allValues[id] = subBlock.value
})
// Second pass: filter by mode and conditions
Object.entries(block.subBlocks).forEach(([id, subBlock]) => {
// Find the corresponding subblock config to check its mode and condition
const subBlockConfig = blockConfig.subBlocks.find((config) => config.id === id)
// Include field if it matches current mode OR if it's the starter inputFormat with values
@@ -360,9 +417,14 @@ export class Serializer {
const isLegacyAgentField =
isAgentBlock && ['systemPrompt', 'userPrompt', 'memories'].includes(id)
// Check if field's condition is met (conditionally-hidden fields should be excluded)
const conditionMet = subBlockConfig
? evaluateCondition(subBlockConfig.condition, allValues)
: true
if (
(subBlockConfig &&
(shouldIncludeField(subBlockConfig, isAdvancedMode) || hasStarterInputFormatValues)) ||
(subBlockConfig && shouldIncludeField(subBlockConfig, isAdvancedMode) && conditionMet) ||
hasStarterInputFormatValues ||
isLegacyAgentField
) {
params[id] = subBlock.value
@@ -475,52 +537,6 @@ export class Serializer {
// Check required user-only parameters for the current tool
const missingFields: string[] = []
// Helper function to evaluate conditions
const evalCond = (
condition:
| {
field: string
value: any
not?: boolean
and?: { field: string; value: any; not?: boolean }
}
| (() => {
field: string
value: any
not?: boolean
and?: { field: string; value: any; not?: boolean }
})
| undefined,
values: Record<string, any>
): boolean => {
if (!condition) return true
const actual = typeof condition === 'function' ? condition() : condition
const fieldValue = values[actual.field]
const valueMatch = Array.isArray(actual.value)
? fieldValue != null &&
(actual.not ? !actual.value.includes(fieldValue) : actual.value.includes(fieldValue))
: actual.not
? fieldValue !== actual.value
: fieldValue === actual.value
const andMatch = !actual.and
? true
: (() => {
const andFieldValue = values[actual.and!.field]
return Array.isArray(actual.and!.value)
? andFieldValue != null &&
(actual.and!.not
? !actual.and!.value.includes(andFieldValue)
: actual.and!.value.includes(andFieldValue))
: actual.and!.not
? andFieldValue !== actual.and!.value
: andFieldValue === actual.and!.value
})()
return valueMatch && andMatch
}
// Iterate through the tool's parameters, not the block's subBlocks
Object.entries(currentTool.params || {}).forEach(([paramId, paramConfig]) => {
if (paramConfig.required && paramConfig.visibility === 'user-only') {
@@ -533,14 +549,14 @@ export class Serializer {
const includedByMode = shouldIncludeField(subBlockConfig, isAdvancedMode)
// Check visibility condition
const includedByCondition = evalCond(subBlockConfig.condition, params)
const includedByCondition = evaluateCondition(subBlockConfig.condition, params)
// Check if field is required based on its required condition (if it's a condition object)
const isRequired = (() => {
if (!subBlockConfig.required) return false
if (typeof subBlockConfig.required === 'boolean') return subBlockConfig.required
// If required is a condition object, evaluate it
return evalCond(subBlockConfig.required, params)
return evaluateCondition(subBlockConfig.required, params)
})()
shouldValidateParam = includedByMode && includedByCondition && isRequired