diff --git a/apps/docs/content/docs/en/tools/extend.mdx b/apps/docs/content/docs/en/tools/extend.mdx index 8cecfca278..a92d445f1b 100644 --- a/apps/docs/content/docs/en/tools/extend.mdx +++ b/apps/docs/content/docs/en/tools/extend.mdx @@ -34,6 +34,13 @@ Integrate Extend AI into the workflow. Parse and extract structured content from #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Unique identifier for the parser run | +| `status` | string | Processing status | +| `chunks` | json | Parsed document content chunks | +| `blocks` | json | Block-level document elements with type and content | +| `pageCount` | number | Number of pages processed | +| `creditsUsed` | number | API credits consumed | diff --git a/apps/docs/content/docs/en/tools/mistral_parse.mdx b/apps/docs/content/docs/en/tools/mistral_parse.mdx index 046620f39a..9a74a864fd 100644 --- a/apps/docs/content/docs/en/tools/mistral_parse.mdx +++ b/apps/docs/content/docs/en/tools/mistral_parse.mdx @@ -54,8 +54,27 @@ Integrate Mistral Parse into the workflow. Can extract text from uploaded PDF do | Parameter | Type | Description | | --------- | ---- | ----------- | | `pages` | array | Array of page objects from Mistral OCR | -| `model` | string | Mistral OCR model identifier | -| `usage_info` | json | Usage statistics from the API | -| `document_annotation` | string | Structured annotation data | +| ↳ `index` | number | Page index \(zero-based\) | +| ↳ `markdown` | string | Extracted markdown content | +| ↳ `images` | array | Images extracted from this page with bounding boxes | +| ↳ `id` | string | Image identifier \(e.g., img-0.jpeg\) | +| ↳ `top_left_x` | number | Top-left X coordinate in pixels | +| ↳ `top_left_y` | number | Top-left Y coordinate in pixels | +| ↳ `bottom_right_x` | number | Bottom-right X coordinate in pixels | +| ↳ `bottom_right_y` | number | Bottom-right Y coordinate in pixels | +| ↳ `image_base64` | string | Base64-encoded image data \(when include_image_base64=true\) | +| ↳ `dimensions` | object | Page dimensions | +| ↳ `dpi` | number | Dots per inch | +| ↳ `height` | number | Page height in pixels | +| ↳ `width` | number | Page width in pixels | +| ↳ `tables` | array | Extracted tables as HTML/markdown \(when table_format is set\). Referenced via placeholders like \[tbl-0.html\] | +| ↳ `hyperlinks` | array | Array of URL strings detected in the page \(e.g., \["https://...", "mailto:..."\]\) | +| ↳ `header` | string | Page header content \(when extract_header=true\) | +| ↳ `footer` | string | Page footer content \(when extract_footer=true\) | +| `model` | string | Mistral OCR model identifier \(e.g., mistral-ocr-latest\) | +| `usage_info` | object | Usage and processing statistics | +| ↳ `pages_processed` | number | Total number of pages processed | +| ↳ `doc_size_bytes` | number | Document file size in bytes | +| `document_annotation` | string | Structured annotation data as JSON string \(when applicable\) | diff --git a/apps/docs/content/docs/en/tools/pulse.mdx b/apps/docs/content/docs/en/tools/pulse.mdx index 19fd85f4a9..46b275cb59 100644 --- a/apps/docs/content/docs/en/tools/pulse.mdx +++ b/apps/docs/content/docs/en/tools/pulse.mdx @@ -56,6 +56,16 @@ Integrate Pulse into the workflow. Extract text from PDF documents, images, and #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `markdown` | string | Extracted content in markdown format | +| `page_count` | number | Number of pages in the document | +| `job_id` | string | Unique job identifier | +| `bounding_boxes` | json | Bounding box layout information | +| `extraction_url` | string | URL for extraction results \(for large documents\) | +| `html` | string | HTML content if requested | +| `structured_output` | json | Structured output if schema was provided | +| `chunks` | json | Chunked content if chunking was enabled | +| `figures` | json | Extracted figures if figure extraction was enabled | diff --git a/apps/docs/content/docs/en/tools/reducto.mdx b/apps/docs/content/docs/en/tools/reducto.mdx index 11af5add13..cce140b86e 100644 --- a/apps/docs/content/docs/en/tools/reducto.mdx +++ b/apps/docs/content/docs/en/tools/reducto.mdx @@ -50,6 +50,13 @@ Integrate Reducto Parse into the workflow. Can extract text from uploaded PDF do #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `job_id` | string | Unique identifier for the processing job | +| `duration` | number | Processing time in seconds | +| `usage` | json | Resource consumption data | +| `result` | json | Parsed document content with chunks and blocks | +| `pdf_url` | string | Storage URL of converted PDF | +| `studio_link` | string | Link to Reducto studio interface | diff --git a/apps/docs/content/docs/en/tools/stt.mdx b/apps/docs/content/docs/en/tools/stt.mdx index 9567243298..6920544178 100644 --- a/apps/docs/content/docs/en/tools/stt.mdx +++ b/apps/docs/content/docs/en/tools/stt.mdx @@ -69,7 +69,17 @@ Transcribe audio and video files to text using leading AI providers. Supports mu #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transcript` | string | Full transcribed text | +| `segments` | array | Timestamped segments | +| ↳ `text` | string | Transcribed text for this segment | +| ↳ `start` | number | Start time in seconds | +| ↳ `end` | number | End time in seconds | +| ↳ `speaker` | string | Speaker identifier \(if diarization enabled\) | +| ↳ `confidence` | number | Confidence score \(0-1\) | +| `language` | string | Detected or specified language | +| `duration` | number | Audio duration in seconds | ### `stt_deepgram` @@ -89,7 +99,18 @@ This tool does not produce any outputs. #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transcript` | string | Full transcribed text | +| `segments` | array | Timestamped segments with speaker labels | +| ↳ `text` | string | Transcribed text for this segment | +| ↳ `start` | number | Start time in seconds | +| ↳ `end` | number | End time in seconds | +| ↳ `speaker` | string | Speaker identifier \(if diarization enabled\) | +| ↳ `confidence` | number | Confidence score \(0-1\) | +| `language` | string | Detected or specified language | +| `duration` | number | Audio duration in seconds | +| `confidence` | number | Overall confidence score | ### `stt_elevenlabs` @@ -108,7 +129,13 @@ This tool does not produce any outputs. #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transcript` | string | Full transcribed text | +| `segments` | array | Timestamped segments | +| `language` | string | Detected or specified language | +| `duration` | number | Audio duration in seconds | +| `confidence` | number | Overall confidence score | ### `stt_assemblyai` @@ -132,7 +159,30 @@ This tool does not produce any outputs. #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transcript` | string | Full transcribed text | +| `segments` | array | Timestamped segments with speaker labels | +| ↳ `text` | string | Transcribed text for this segment | +| ↳ `start` | number | Start time in seconds | +| ↳ `end` | number | End time in seconds | +| ↳ `speaker` | string | Speaker identifier \(if diarization enabled\) | +| ↳ `confidence` | number | Confidence score \(0-1\) | +| `language` | string | Detected or specified language | +| `duration` | number | Audio duration in seconds | +| `confidence` | number | Overall confidence score | +| `sentiment` | array | Sentiment analysis results | +| ↳ `text` | string | Text that was analyzed | +| ↳ `sentiment` | string | Sentiment \(POSITIVE, NEGATIVE, NEUTRAL\) | +| ↳ `confidence` | number | Confidence score | +| ↳ `start` | number | Start time in milliseconds | +| ↳ `end` | number | End time in milliseconds | +| `entities` | array | Detected entities | +| ↳ `entity_type` | string | Entity type \(e.g., person_name, location, organization\) | +| ↳ `text` | string | Entity text | +| ↳ `start` | number | Start time in milliseconds | +| ↳ `end` | number | End time in milliseconds | +| `summary` | string | Auto-generated summary | ### `stt_gemini` @@ -151,6 +201,12 @@ This tool does not produce any outputs. #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `transcript` | string | Full transcribed text | +| `segments` | array | Timestamped segments | +| `language` | string | Detected or specified language | +| `duration` | number | Audio duration in seconds | +| `confidence` | number | Overall confidence score | diff --git a/apps/docs/content/docs/en/tools/textract.mdx b/apps/docs/content/docs/en/tools/textract.mdx index 41a14abfd8..c654e281ff 100644 --- a/apps/docs/content/docs/en/tools/textract.mdx +++ b/apps/docs/content/docs/en/tools/textract.mdx @@ -56,6 +56,39 @@ Integrate AWS Textract into your workflow to extract text, tables, forms, and ke #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `blocks` | array | Array of Block objects containing detected text, tables, forms, and other elements | +| ↳ `BlockType` | string | Type of block \(PAGE, LINE, WORD, TABLE, CELL, KEY_VALUE_SET, etc.\) | +| ↳ `Id` | string | Unique identifier for the block | +| ↳ `Text` | string | The text content \(for LINE and WORD blocks\) | +| ↳ `TextType` | string | Type of text \(PRINTED or HANDWRITING\) | +| ↳ `Confidence` | number | Confidence score \(0-100\) | +| ↳ `Page` | number | Page number | +| ↳ `Geometry` | object | Location and bounding box information | +| ↳ `BoundingBox` | object | Height as ratio of document height | +| ↳ `Height` | number | Height as ratio of document height | +| ↳ `Left` | number | Left position as ratio of document width | +| ↳ `Top` | number | Top position as ratio of document height | +| ↳ `Width` | number | Width as ratio of document width | +| ↳ `Polygon` | array | Polygon coordinates | +| ↳ `X` | number | X coordinate | +| ↳ `Y` | number | Y coordinate | +| ↳ `Relationships` | array | Relationships to other blocks | +| ↳ `Type` | string | Relationship type \(CHILD, VALUE, ANSWER, etc.\) | +| ↳ `Ids` | array | IDs of related blocks | +| ↳ `EntityTypes` | array | Entity types for KEY_VALUE_SET \(KEY or VALUE\) | +| ↳ `SelectionStatus` | string | For checkboxes: SELECTED or NOT_SELECTED | +| ↳ `RowIndex` | number | Row index for table cells | +| ↳ `ColumnIndex` | number | Column index for table cells | +| ↳ `RowSpan` | number | Row span for merged cells | +| ↳ `ColumnSpan` | number | Column span for merged cells | +| ↳ `Query` | object | Query information for QUERY blocks | +| ↳ `Text` | string | Query text | +| ↳ `Alias` | string | Query alias | +| ↳ `Pages` | array | Pages to search | +| `documentMetadata` | object | Metadata about the analyzed document | +| ↳ `pages` | number | Number of pages in the document | +| `modelVersion` | string | Version of the Textract model used for processing | diff --git a/apps/docs/content/docs/en/tools/vision.mdx b/apps/docs/content/docs/en/tools/vision.mdx index af3b052f41..1ab0cee507 100644 --- a/apps/docs/content/docs/en/tools/vision.mdx +++ b/apps/docs/content/docs/en/tools/vision.mdx @@ -47,6 +47,14 @@ Integrate Vision into the workflow. Can analyze images with vision models. #### Output -This tool does not produce any outputs. +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | The analyzed content and description of the image | +| `model` | string | The vision model that was used for analysis | +| `tokens` | number | Total tokens used for the analysis | +| `usage` | object | Detailed token usage breakdown | +| ↳ `input_tokens` | number | Tokens used for input processing | +| ↳ `output_tokens` | number | Tokens used for response generation | +| ↳ `total_tokens` | number | Total tokens consumed | diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 49f2dcf8f1..079bf0da8f 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -53,6 +53,27 @@ interface BlockConfig { [key: string]: any } +/** + * Find the position after the matching close delimiter for an opening delimiter. + * Assumes `content[openPos]` is the opening char (e.g. `{` or `[`). + * Returns the index one past the matching close char, or -1 if unbalanced. + */ +function findMatchingClose( + content: string, + openPos: number, + openChar = '{', + closeChar = '}' +): number { + let count = 1 + let pos = openPos + 1 + while (pos < content.length && count > 0) { + if (content[pos] === openChar) count++ + else if (content[pos] === closeChar) count-- + pos++ + } + return count === 0 ? pos : -1 +} + interface TriggerInfo { id: string name: string @@ -134,16 +155,9 @@ async function generateIconMapping(): Promise> { const startIndex = match.index + match[0].length - 1 // Extract the block content - let braceCount = 1 - let endIndex = startIndex + 1 - - while (endIndex < fileContent.length && braceCount > 0) { - if (fileContent[endIndex] === '{') braceCount++ - else if (fileContent[endIndex] === '}') braceCount-- - endIndex++ - } + const endIndex = findMatchingClose(fileContent, startIndex) - if (braceCount === 0) { + if (endIndex !== -1) { const blockContent = fileContent.substring(startIndex, endIndex) // Check hideFromToolbar - skip hidden blocks for docs but NOT for icon mapping @@ -272,26 +286,16 @@ function extractOperationsFromContent(blockContent: string): { label: string; id // Locate the opening '[' of the subBlocks array const arrayStart = subBlocksMatch.index + subBlocksMatch[0].length - 1 - let bracketCount = 1 - let pos = arrayStart + 1 - while (pos < blockContent.length && bracketCount > 0) { - if (blockContent[pos] === '[') bracketCount++ - else if (blockContent[pos] === ']') bracketCount-- - pos++ - } - const subBlocksContent = blockContent.substring(arrayStart + 1, pos - 1) + const arrayEnd = findMatchingClose(blockContent, arrayStart, '[', ']') + if (arrayEnd === -1) return [] + const subBlocksContent = blockContent.substring(arrayStart + 1, arrayEnd - 1) // Iterate over top-level objects in the subBlocks array, looking for id: 'operation' let i = 0 while (i < subBlocksContent.length) { if (subBlocksContent[i] === '{') { - let braceCount = 1 - let j = i + 1 - while (j < subBlocksContent.length && braceCount > 0) { - if (subBlocksContent[j] === '{') braceCount++ - else if (subBlocksContent[j] === '}') braceCount-- - j++ - } + const j = findMatchingClose(subBlocksContent, i) + if (j === -1) break const objContent = subBlocksContent.substring(i, j) if (/\bid\s*:\s*['"]operation['"]/.test(objContent)) { @@ -299,14 +303,9 @@ function extractOperationsFromContent(blockContent: string): { label: string; id if (!optionsMatch) return [] const optArrayStart = optionsMatch.index + optionsMatch[0].length - 1 - let bc = 1 - let op = optArrayStart + 1 - while (op < objContent.length && bc > 0) { - if (objContent[op] === '[') bc++ - else if (objContent[op] === ']') bc-- - op++ - } - const optionsContent = objContent.substring(optArrayStart + 1, op - 1) + const optArrayEnd = findMatchingClose(objContent, optArrayStart, '[', ']') + if (optArrayEnd === -1) return [] + const optionsContent = objContent.substring(optArrayStart + 1, optArrayEnd - 1) // Extract { label, id } pairs from each option object const pairs: { label: string; id: string }[] = [] @@ -442,14 +441,9 @@ function extractTriggersAvailable(blockContent: string): string[] { if (!triggersMatch) return [] const start = triggersMatch.index + triggersMatch[0].length - 1 - let braceCount = 1 - let pos = start + 1 - while (pos < blockContent.length && braceCount > 0) { - if (blockContent[pos] === '{') braceCount++ - else if (blockContent[pos] === '}') braceCount-- - pos++ - } - const triggersContent = blockContent.substring(start, pos) + const trigEnd = findMatchingClose(blockContent, start) + if (trigEnd === -1) return [] + const triggersContent = blockContent.substring(start, trigEnd) if (!/enabled\s*:\s*true/.test(triggersContent)) return [] @@ -457,14 +451,9 @@ function extractTriggersAvailable(blockContent: string): string[] { if (!availableMatch) return [] const arrayStart = availableMatch.index + availableMatch[0].length - 1 - let bracketCount = 1 - let ap = arrayStart + 1 - while (ap < triggersContent.length && bracketCount > 0) { - if (triggersContent[ap] === '[') bracketCount++ - else if (triggersContent[ap] === ']') bracketCount-- - ap++ - } - const arrayContent = triggersContent.substring(arrayStart + 1, ap - 1) + const arrayEnd = findMatchingClose(triggersContent, arrayStart, '[', ']') + if (arrayEnd === -1) return [] + const arrayContent = triggersContent.substring(arrayStart + 1, arrayEnd - 1) const ids: string[] = [] const idRegex = /['"]([^'"]+)['"]/g @@ -731,16 +720,9 @@ function extractAllBlockConfigs(fileContent: string): BlockConfig[] { const startIndex = match.index + match[0].length - 1 // Position of opening brace // Extract the block content by matching braces - let braceCount = 1 - let endIndex = startIndex + 1 - - while (endIndex < fileContent.length && braceCount > 0) { - if (fileContent[endIndex] === '{') braceCount++ - else if (fileContent[endIndex] === '}') braceCount-- - endIndex++ - } + const endIndex = findMatchingClose(fileContent, startIndex) - if (braceCount === 0) { + if (endIndex !== -1) { const blockContent = fileContent.substring(startIndex, endIndex) // Check if this block has hideFromToolbar: true @@ -798,16 +780,9 @@ function extractBlockConfigFromContent( if (baseMatch) { const startIndex = baseMatch.index + baseMatch[0].length - 1 - let braceCount = 1 - let endIndex = startIndex + 1 + const endIndex = findMatchingClose(fileContent, startIndex) - while (endIndex < fileContent.length && braceCount > 0) { - if (fileContent[endIndex] === '{') braceCount++ - else if (fileContent[endIndex] === '}') braceCount-- - endIndex++ - } - - if (braceCount === 0) { + if (endIndex !== -1) { const baseBlockContent = fileContent.substring(startIndex, endIndex) // Recursively extract base config (but don't pass fileContent to avoid infinite loops) baseConfig = extractBlockConfigFromContent( @@ -1015,62 +990,42 @@ function extractOutputsFromContent(content: string): Record { const openBracePos = content.indexOf('{', outputsStart) if (openBracePos === -1) return {} - let braceCount = 1 - let pos = openBracePos + 1 - - while (pos < content.length && braceCount > 0) { - if (content[pos] === '{') braceCount++ - else if (content[pos] === '}') braceCount-- - pos++ - } - - if (braceCount === 0) { - const outputsContent = content.substring(openBracePos + 1, pos - 1).trim() - const outputs: Record = {} + const pos = findMatchingClose(content, openBracePos) + if (pos === -1) return {} - const fieldRegex = /(\w+)\s*:\s*{/g - let match - const fieldPositions: Array<{ name: string; start: number }> = [] + const outputsContent = content.substring(openBracePos + 1, pos - 1).trim() + const outputs: Record = {} - while ((match = fieldRegex.exec(outputsContent)) !== null) { - fieldPositions.push({ - name: match[1], - start: match.index + match[0].length - 1, - }) - } + const fieldRegex = /(\w+)\s*:\s*{/g + let match + const fieldPositions: Array<{ name: string; start: number }> = [] - fieldPositions.forEach((field) => { - const startPos = field.start - let braceCount = 1 - let endPos = startPos + 1 + while ((match = fieldRegex.exec(outputsContent)) !== null) { + fieldPositions.push({ + name: match[1], + start: match.index + match[0].length - 1, + }) + } - while (endPos < outputsContent.length && braceCount > 0) { - if (outputsContent[endPos] === '{') braceCount++ - else if (outputsContent[endPos] === '}') braceCount-- - endPos++ - } + fieldPositions.forEach((field) => { + const endPos = findMatchingClose(outputsContent, field.start) - if (braceCount === 0) { - const fieldContent = outputsContent.substring(startPos + 1, endPos - 1).trim() + if (endPos !== -1) { + const fieldContent = outputsContent.substring(field.start + 1, endPos - 1).trim() - const typeMatch = fieldContent.match(/type\s*:\s*['"](.*?)['"]/) - const description = extractDescription(fieldContent) + const typeMatch = fieldContent.match(/type\s*:\s*['"](.*?)['"]/) + const description = extractDescription(fieldContent) - if (typeMatch) { - outputs[field.name] = { - type: typeMatch[1], - description: description || `${field.name} output from the block`, - } + if (typeMatch) { + outputs[field.name] = { + type: typeMatch[1], + description: description || `${field.name} output from the block`, } } - }) - - if (Object.keys(outputs).length > 0) { - return outputs } - } + }) - return {} + return outputs } function extractToolsAccessFromContent(content: string): string[] { @@ -1148,16 +1103,9 @@ function resolveConstReference( // Extract the const content const startIndex = constMatch.index + constMatch[0].length - 1 - let braceCount = 1 - let endIndex = startIndex + 1 + const endIndex = findMatchingClose(typesContent, startIndex) - while (endIndex < typesContent.length && braceCount > 0) { - if (typesContent[endIndex] === '{') braceCount++ - else if (typesContent[endIndex] === '}') braceCount-- - endIndex++ - } - - if (braceCount !== 0) { + if (endIndex === -1) { return null } @@ -1242,14 +1190,8 @@ function parseConstProperties( if ((propName === 'properties' || propName === 'type') && !constRef) { // Peek at what's inside the braces const startPos = match.index + match[0].length - 1 - let braceCount = 1 - let endPos = startPos + 1 - while (endPos < content.length && braceCount > 0) { - if (content[endPos] === '{') braceCount++ - else if (content[endPos] === '}') braceCount-- - endPos++ - } - if (braceCount === 0) { + const endPos = findMatchingClose(content, startPos) + if (endPos !== -1) { const propContent = content.substring(startPos + 1, endPos - 1).trim() // If it starts with 'type:', it's an output field definition - process it if (propContent.match(/^\s*type\s*:/)) { @@ -1272,17 +1214,9 @@ function parseConstProperties( } else { // This property has inline definition const startPos = match.index + match[0].length - 1 + const endPos = findMatchingClose(content, startPos) - let braceCount = 1 - let endPos = startPos + 1 - - while (endPos < content.length && braceCount > 0) { - if (content[endPos] === '{') braceCount++ - else if (content[endPos] === '}') braceCount-- - endPos++ - } - - if (braceCount === 0) { + if (endPos !== -1) { const propContent = content.substring(startPos + 1, endPos - 1).trim() const parsedProp = parseConstFieldContent(propContent, toolPrefix, typesContent, depth) if (parsedProp) { @@ -1324,16 +1258,9 @@ function resolveConstFromTypesContent( } const startIndex = constMatch.index + constMatch[0].length - 1 - let braceCount = 1 - let endIndex = startIndex + 1 + const endIndex = findMatchingClose(typesContent, startIndex) - while (endIndex < typesContent.length && braceCount > 0) { - if (typesContent[endIndex] === '{') braceCount++ - else if (typesContent[endIndex] === '}') braceCount-- - endIndex++ - } - - if (braceCount !== 0) return null + if (endIndex === -1) return null const constContent = typesContent.substring(startIndex + 1, endIndex - 1).trim() @@ -1414,16 +1341,9 @@ function parseConstFieldContent( const propertiesStart = fieldContent.search(/properties\s*:\s*\{/) if (propertiesStart !== -1) { const braceStart = fieldContent.indexOf('{', propertiesStart) - let braceCount = 1 - let braceEnd = braceStart + 1 + const braceEnd = findMatchingClose(fieldContent, braceStart) - while (braceEnd < fieldContent.length && braceCount > 0) { - if (fieldContent[braceEnd] === '{') braceCount++ - else if (fieldContent[braceEnd] === '}') braceCount-- - braceEnd++ - } - - if (braceCount === 0) { + if (braceEnd !== -1) { const propertiesContent = fieldContent.substring(braceStart + 1, braceEnd - 1).trim() result.properties = parseConstProperties( propertiesContent, @@ -1452,16 +1372,9 @@ function parseConstFieldContent( const itemsStart = fieldContent.search(/items\s*:\s*\{/) if (itemsStart !== -1) { const braceStart = fieldContent.indexOf('{', itemsStart) - let braceCount = 1 - let braceEnd = braceStart + 1 - - while (braceEnd < fieldContent.length && braceCount > 0) { - if (fieldContent[braceEnd] === '{') braceCount++ - else if (fieldContent[braceEnd] === '}') braceCount-- - braceEnd++ - } + const braceEnd = findMatchingClose(fieldContent, braceStart) - if (braceCount === 0) { + if (braceEnd !== -1) { const itemsContent = fieldContent.substring(braceStart + 1, braceEnd - 1).trim() const itemsType = itemsContent.match(/type\s*:\s*['"]([^'"]+)['"]/) const itemsDesc = extractDescription(itemsContent) @@ -1516,6 +1429,40 @@ function parseConstFieldContent( return result } +/** + * Extract outputs from a tool content block by trying: + * 1. Const reference (e.g., `outputs: GIT_REF_OUTPUT_PROPERTIES,`) + * 2. Inline object (e.g., `outputs: { id: { type: 'string', ... } }`) + */ +function extractOutputsFromToolContent( + content: string, + toolPrefix: string +): Record { + const constMatch = content.match( + /(? 0) { - if (fileContent[endIndex] === '{') braceCount++ - else if (fileContent[endIndex] === '}') braceCount-- - endIndex++ - } - - if (braceCount === 0) { + if (endIndex !== -1) { toolContent = fileContent.substring(startIndex, endIndex) } } @@ -1650,19 +1590,9 @@ function extractToolInfo( continue } - let braceCount = 1 - let endPos = startPos + 1 - - while (endPos < paramsContent.length && braceCount > 0) { - if (paramsContent[endPos] === '{') { - braceCount++ - } else if (paramsContent[endPos] === '}') { - braceCount-- - } - endPos++ - } + const endPos = findMatchingClose(paramsContent, startPos) - if (braceCount === 0) { + if (endPos !== -1) { const paramBlock = paramsContent.substring(startPos + 1, endPos - 1).trim() paramPositions.push({ name: paramName, start: startPos, content: paramBlock }) } @@ -1704,36 +1634,43 @@ function extractToolInfo( // Get the tool prefix for resolving const references const toolPrefix = getToolPrefixFromName(toolName) - let outputs: Record = {} + let outputs = extractOutputsFromToolContent(toolContent, toolPrefix) - // Pattern 1: outputs directly assigned to a const (e.g., "outputs: GIT_REF_OUTPUT_PROPERTIES,") - const directConstMatch = toolContent.match( - /(? 0) { - if (toolContent[pos] === '{') braceCount++ - else if (toolContent[pos] === '}') braceCount-- - pos++ + let fullToolBlock = toolContent + if (toolIdMatch && toolIdMatch.index !== undefined) { + const beforeId = fileContent.substring(0, toolIdMatch.index) + const exportRegex = /export\s+const\s+\w+[^=]*=\s*\{/g + let lastExportMatch: RegExpExecArray | null = null + let m: RegExpExecArray | null = null + while ((m = exportRegex.exec(beforeId)) !== null) { + lastExportMatch = m + } + if (lastExportMatch && lastExportMatch.index !== undefined) { + const bracePos = + lastExportMatch.index + lastExportMatch[0].length - 1 + const ep = findMatchingClose(fileContent, bracePos) + if (ep !== -1) { + fullToolBlock = fileContent.substring(bracePos, ep) } - if (braceCount === 0) { - const outputsContent = toolContent.substring(openBracePos + 1, pos - 1).trim() - outputs = parseToolOutputsField(outputsContent, toolPrefix) + } + } + const spreadMatch = fullToolBlock.match(/\.\.\.(\w+(?:Tool|Base)\w*)/) + if (spreadMatch) { + const baseVarName = spreadMatch[1] + const baseToolRegex = new RegExp( + `export\\s+const\\s+${baseVarName}(?=[^a-zA-Z0-9_]|$)[^=]*=\\s*\\{` + ) + const baseToolMatch = fileContent.match(baseToolRegex) + if (baseToolMatch && baseToolMatch.index !== undefined) { + const baseStart = + baseToolMatch.index + baseToolMatch[0].length - 1 + const endIdx = findMatchingClose(fileContent, baseStart) + if (endIdx !== -1) { + const baseToolContent = fileContent.substring(baseStart, endIdx) + outputs = extractOutputsFromToolContent(baseToolContent, toolPrefix) } } } @@ -1921,24 +1858,15 @@ function parseToolOutputsField(outputsContent: string, toolPrefix?: string): Rec const openBrace = braces.find((b) => b.type === 'open' && b.pos === bracePos) if (openBrace) { - let braceCount = 1 - let endPos = bracePos + 1 - - while (endPos < outputsContent.length && braceCount > 0) { - if (outputsContent[endPos] === '{') { - braceCount++ - } else if (outputsContent[endPos] === '}') { - braceCount-- - } - endPos++ + const endPos = findMatchingClose(outputsContent, bracePos) + if (endPos !== -1) { + fieldPositions.push({ + name: fieldName, + start: bracePos, + end: endPos, + level: openBrace.level, + }) } - - fieldPositions.push({ - name: fieldName, - start: bracePos, - end: endPos, - level: openBrace.level, - }) } } @@ -2001,16 +1929,9 @@ function parseFieldContent(fieldContent: string, toolPrefix?: string): any { if (propertiesStart !== -1) { const braceStart = fieldContent.indexOf('{', propertiesStart) - let braceCount = 1 - let braceEnd = braceStart + 1 - - while (braceEnd < fieldContent.length && braceCount > 0) { - if (fieldContent[braceEnd] === '{') braceCount++ - else if (fieldContent[braceEnd] === '}') braceCount-- - braceEnd++ - } + const braceEnd = findMatchingClose(fieldContent, braceStart) - if (braceCount === 0) { + if (braceEnd !== -1) { const propertiesContent = fieldContent.substring(braceStart + 1, braceEnd - 1).trim() result.properties = parsePropertiesContent(propertiesContent, toolPrefix) } @@ -2031,16 +1952,9 @@ function parseFieldContent(fieldContent: string, toolPrefix?: string): any { if (itemsStart !== -1) { const braceStart = fieldContent.indexOf('{', itemsStart) - let braceCount = 1 - let braceEnd = braceStart + 1 - - while (braceEnd < fieldContent.length && braceCount > 0) { - if (fieldContent[braceEnd] === '{') braceCount++ - else if (fieldContent[braceEnd] === '}') braceCount-- - braceEnd++ - } + const braceEnd = findMatchingClose(fieldContent, braceStart) - if (braceCount === 0) { + if (braceEnd !== -1) { const itemsContent = fieldContent.substring(braceStart + 1, braceEnd - 1).trim() const itemsType = itemsContent.match(/type\s*:\s*['"]([^'"]+)['"]/) @@ -2213,19 +2127,9 @@ function parsePropertiesContent( const startPos = match.index + match[0].length - 1 - let braceCount = 1 - let endPos = startPos + 1 + const endPos = findMatchingClose(propertiesContent, startPos) - while (endPos < propertiesContent.length && braceCount > 0) { - if (propertiesContent[endPos] === '{') { - braceCount++ - } else if (propertiesContent[endPos] === '}') { - braceCount-- - } - endPos++ - } - - if (braceCount === 0) { + if (endPos !== -1) { const propContent = propertiesContent.substring(startPos + 1, endPos - 1).trim() const hasDescription = /description\s*:\s*/.test(propContent) @@ -2729,16 +2633,9 @@ async function getHiddenAndVisibleBlockTypes(): Promise<{ const startIndex = match.index + match[0].length - 1 // Extract the block content - let braceCount = 1 - let endIndex = startIndex + 1 - - while (endIndex < fileContent.length && braceCount > 0) { - if (fileContent[endIndex] === '{') braceCount++ - else if (fileContent[endIndex] === '}') braceCount-- - endIndex++ - } + const endIndex = findMatchingClose(fileContent, startIndex) - if (braceCount === 0) { + if (endIndex !== -1) { const blockContent = fileContent.substring(startIndex, endIndex) const blockType = extractStringPropertyFromContent(blockContent, 'type', true)