11import { createLogger } from '@sim/logger'
2+ import type { StructuredFilter } from '@/lib/knowledge/types'
23import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format'
34import {
45 evaluateSubBlockCondition ,
56 type SubBlockCondition ,
67} from '@/lib/workflows/subblocks/visibility'
78import type { SubBlockConfig as BlockSubBlockConfig } from '@/blocks/types'
8- import { isEmptyTagValue } from '@/tools/tag-utils'
99import type { ParameterVisibility , ToolConfig } from '@/tools/types'
1010import { getTool } from '@/tools/utils'
1111
12- // Re-export for backwards compatibility
13- export { isEmptyTagValue } from '@/tools/tag-utils'
14-
1512const logger = createLogger ( 'ToolsParams' )
1613type ToolParamDefinition = ToolConfig [ 'params' ] [ string ]
1714
@@ -22,6 +19,198 @@ export function isNonEmpty(value: unknown): boolean {
2219 return value !== undefined && value !== null && value !== ''
2320}
2421
22+ // ============================================================================
23+ // Tag/Value Parsing Utilities
24+ // ============================================================================
25+
26+ /**
27+ * Document tag entry format used in create_document tool
28+ */
29+ export interface DocumentTagEntry {
30+ tagName : string
31+ value : string
32+ }
33+
34+ /**
35+ * Tag filter entry format used in search tool
36+ */
37+ export interface TagFilterEntry {
38+ tagName : string
39+ tagSlot ?: string
40+ tagValue : string | number | boolean
41+ fieldType ?: string
42+ operator ?: string
43+ valueTo ?: string | number
44+ }
45+
46+ /**
47+ * Checks if a tag value is effectively empty (unfilled/default entry)
48+ */
49+ function isEmptyTagEntry ( entry : Record < string , unknown > ) : boolean {
50+ if ( ! entry . tagName || ( typeof entry . tagName === 'string' && entry . tagName . trim ( ) === '' ) ) {
51+ return true
52+ }
53+ return false
54+ }
55+
56+ /**
57+ * Checks if a tag-based value is effectively empty (only contains default/unfilled entries).
58+ * Works for both documentTags and tagFilters parameters in various formats.
59+ *
60+ * @param value - The tag value to check (can be JSON string, array, or object)
61+ * @returns true if the value is empty or only contains unfilled entries
62+ */
63+ export function isEmptyTagValue ( value : unknown ) : boolean {
64+ if ( ! value ) return true
65+
66+ // Handle JSON string format
67+ if ( typeof value === 'string' ) {
68+ try {
69+ const parsed = JSON . parse ( value )
70+ if ( ! Array . isArray ( parsed ) ) return false
71+ if ( parsed . length === 0 ) return true
72+ return parsed . every ( ( entry : Record < string , unknown > ) => isEmptyTagEntry ( entry ) )
73+ } catch {
74+ return false
75+ }
76+ }
77+
78+ // Handle array format directly
79+ if ( Array . isArray ( value ) ) {
80+ if ( value . length === 0 ) return true
81+ return value . every ( ( entry : Record < string , unknown > ) => isEmptyTagEntry ( entry ) )
82+ }
83+
84+ // Handle object format (LLM format: { "Category": "foo", "Priority": 5 })
85+ if ( typeof value === 'object' && value !== null ) {
86+ const entries = Object . entries ( value )
87+ if ( entries . length === 0 ) return true
88+ return entries . every ( ( [ , val ] ) => val === undefined || val === null || val === '' )
89+ }
90+
91+ return false
92+ }
93+
94+ /**
95+ * Filters valid document tags from an array, removing empty entries
96+ */
97+ function filterValidDocumentTags ( tags : unknown [ ] ) : DocumentTagEntry [ ] {
98+ return tags
99+ . filter ( ( entry ) : entry is Record < string , unknown > => {
100+ if ( typeof entry !== 'object' || entry === null ) return false
101+ const e = entry as Record < string , unknown >
102+ if ( ! e . tagName || ( typeof e . tagName === 'string' && e . tagName . trim ( ) === '' ) ) return false
103+ if ( e . value === undefined || e . value === null || e . value === '' ) return false
104+ return true
105+ } )
106+ . map ( ( entry ) => ( {
107+ tagName : String ( entry . tagName ) ,
108+ value : String ( entry . value ) ,
109+ } ) )
110+ }
111+
112+ /**
113+ * Parses document tags from various formats into a normalized array format.
114+ * Used by create_document tool to handle tags from both UI and LLM sources.
115+ *
116+ * @param value - Document tags in object, array, or JSON string format
117+ * @returns Normalized array of document tag entries, or empty array if invalid
118+ */
119+ export function parseDocumentTags ( value : unknown ) : DocumentTagEntry [ ] {
120+ if ( ! value ) return [ ]
121+
122+ // Handle object format from LLM: { "Category": "foo", "Priority": 5 }
123+ if ( typeof value === 'object' && ! Array . isArray ( value ) && value !== null ) {
124+ return Object . entries ( value )
125+ . filter ( ( [ tagName , tagValue ] ) => {
126+ if ( ! tagName || tagName . trim ( ) === '' ) return false
127+ if ( tagValue === undefined || tagValue === null || tagValue === '' ) return false
128+ return true
129+ } )
130+ . map ( ( [ tagName , tagValue ] ) => ( {
131+ tagName,
132+ value : String ( tagValue ) ,
133+ } ) )
134+ }
135+
136+ // Handle JSON string format from UI
137+ if ( typeof value === 'string' ) {
138+ try {
139+ const parsed = JSON . parse ( value )
140+ if ( Array . isArray ( parsed ) ) {
141+ return filterValidDocumentTags ( parsed )
142+ }
143+ } catch {
144+ // Invalid JSON, return empty
145+ }
146+ return [ ]
147+ }
148+
149+ // Handle array format directly
150+ if ( Array . isArray ( value ) ) {
151+ return filterValidDocumentTags ( value )
152+ }
153+
154+ return [ ]
155+ }
156+
157+ /**
158+ * Parses tag filters from various formats into a normalized StructuredFilter array.
159+ * Used by search tool to handle tag filters from both UI and LLM sources.
160+ *
161+ * @param value - Tag filters in array or JSON string format
162+ * @returns Normalized array of structured filters, or empty array if invalid
163+ */
164+ export function parseTagFilters ( value : unknown ) : StructuredFilter [ ] {
165+ if ( ! value ) return [ ]
166+
167+ let tagFilters = value
168+
169+ // Handle JSON string format
170+ if ( typeof tagFilters === 'string' ) {
171+ try {
172+ tagFilters = JSON . parse ( tagFilters )
173+ } catch {
174+ return [ ]
175+ }
176+ }
177+
178+ // Must be an array at this point
179+ if ( ! Array . isArray ( tagFilters ) ) return [ ]
180+
181+ return tagFilters
182+ . filter ( ( filter ) : filter is Record < string , unknown > => {
183+ if ( typeof filter !== 'object' || filter === null ) return false
184+ const f = filter as Record < string , unknown >
185+ if ( ! f . tagName || ( typeof f . tagName === 'string' && f . tagName . trim ( ) === '' ) ) return false
186+ if ( f . fieldType === 'boolean' ) {
187+ return f . tagValue !== undefined
188+ }
189+ if ( f . tagValue === undefined || f . tagValue === null ) return false
190+ if ( typeof f . tagValue === 'string' && f . tagValue . trim ( ) . length === 0 ) return false
191+ return true
192+ } )
193+ . map ( ( filter ) => ( {
194+ tagName : filter . tagName as string ,
195+ tagSlot : ( filter . tagSlot as string ) || '' ,
196+ fieldType : ( filter . fieldType as string ) || 'text' ,
197+ operator : ( filter . operator as string ) || 'eq' ,
198+ value : filter . tagValue as string | number | boolean ,
199+ valueTo : filter . valueTo as string | number | undefined ,
200+ } ) )
201+ }
202+
203+ /**
204+ * Converts parsed document tags to the format expected by the create document API.
205+ * Returns the documentTagsData JSON string if there are valid tags.
206+ */
207+ export function formatDocumentTagsForAPI ( tags : DocumentTagEntry [ ] ) : { documentTagsData ?: string } {
208+ if ( tags . length === 0 ) return { }
209+ return {
210+ documentTagsData : JSON . stringify ( tags ) ,
211+ }
212+ }
213+
25214export interface Option {
26215 label : string
27216 value : string
0 commit comments