fix(docs): resolve missing tool outputs for spread-inherited V2 tools (#4020)

* fix(docs): resolve missing tool outputs for spread-inherited V2 tools

* fix(docs): add word boundary to baseToolRegex to prevent false matches

* fix(docs): remove unnecessary case-insensitive flag from baseToolRegex
This commit is contained in:
Waleed
2026-04-07 12:41:23 -07:00
committed by GitHub
parent c89a95d606
commit 762fbbd3e2
8 changed files with 320 additions and 283 deletions

View File

@@ -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 |

View File

@@ -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\) |

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -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<Record<string, string>> {
const startIndex = match.index + match[0].length - 1
// Extract the block content
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 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
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 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<string, any> {
const openBracePos = content.indexOf('{', outputsStart)
if (openBracePos === -1) return {}
let braceCount = 1
let pos = openBracePos + 1
const pos = findMatchingClose(content, openBracePos)
if (pos === -1) return {}
while (pos < content.length && braceCount > 0) {
if (content[pos] === '{') braceCount++
else if (content[pos] === '}') braceCount--
pos++
const outputsContent = content.substring(openBracePos + 1, pos - 1).trim()
const outputs: Record<string, any> = {}
const fieldRegex = /(\w+)\s*:\s*{/g
let match
const fieldPositions: Array<{ name: string; start: number }> = []
while ((match = fieldRegex.exec(outputsContent)) !== null) {
fieldPositions.push({
name: match[1],
start: match.index + match[0].length - 1,
})
}
if (braceCount === 0) {
const outputsContent = content.substring(openBracePos + 1, pos - 1).trim()
const outputs: Record<string, any> = {}
fieldPositions.forEach((field) => {
const endPos = findMatchingClose(outputsContent, field.start)
const fieldRegex = /(\w+)\s*:\s*{/g
let match
const fieldPositions: Array<{ name: string; start: number }> = []
if (endPos !== -1) {
const fieldContent = outputsContent.substring(field.start + 1, endPos - 1).trim()
while ((match = fieldRegex.exec(outputsContent)) !== null) {
fieldPositions.push({
name: match[1],
start: match.index + match[0].length - 1,
})
}
const typeMatch = fieldContent.match(/type\s*:\s*['"](.*?)['"]/)
const description = extractDescription(fieldContent)
fieldPositions.forEach((field) => {
const startPos = field.start
let braceCount = 1
let endPos = startPos + 1
while (endPos < outputsContent.length && braceCount > 0) {
if (outputsContent[endPos] === '{') braceCount++
else if (outputsContent[endPos] === '}') braceCount--
endPos++
}
if (braceCount === 0) {
const fieldContent = outputsContent.substring(startPos + 1, endPos - 1).trim()
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
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 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<string, any> {
const constMatch = content.match(
/(?<![a-zA-Z_])outputs\s*:\s*([A-Z][A-Z_0-9]+)\s*(?:,|\}|$)/
)
if (constMatch) {
const resolved = resolveConstReference(constMatch[1], toolPrefix)
if (resolved && typeof resolved === 'object') {
return resolved
}
}
const outputsStart = content.search(/(?<![a-zA-Z_])outputs\s*:\s*{/)
if (outputsStart !== -1) {
const openBracePos = content.indexOf('{', outputsStart)
if (openBracePos !== -1) {
const closePos = findMatchingClose(content, openBracePos)
if (closePos !== -1) {
const outputsContent = content.substring(openBracePos + 1, closePos - 1).trim()
return parseToolOutputsField(outputsContent, toolPrefix)
}
}
}
return {}
}
function extractToolInfo(
toolName: string,
fileContent: string
@@ -1539,16 +1486,9 @@ function extractToolInfo(
if (exportMatch && exportMatch.index !== undefined) {
const startIndex = exportMatch.index + exportMatch[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) {
toolContent = fileContent.substring(startIndex, endIndex)
}
}
@@ -1650,19 +1590,9 @@ function extractToolInfo(
continue
}
let braceCount = 1
let endPos = startPos + 1
const endPos = findMatchingClose(paramsContent, startPos)
while (endPos < paramsContent.length && braceCount > 0) {
if (paramsContent[endPos] === '{') {
braceCount++
} else if (paramsContent[endPos] === '}') {
braceCount--
}
endPos++
}
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<string, any> = {}
let outputs = extractOutputsFromToolContent(toolContent, toolPrefix)
// Pattern 1: outputs directly assigned to a const (e.g., "outputs: GIT_REF_OUTPUT_PROPERTIES,")
const directConstMatch = toolContent.match(
/(?<![a-zA-Z_])outputs\s*:\s*([A-Z][A-Z_0-9]+)\s*(?:,|\}|$)/
)
if (directConstMatch) {
const constName = directConstMatch[1]
const resolvedConst = resolveConstReference(constName, toolPrefix)
if (resolvedConst && typeof resolvedConst === 'object') {
outputs = resolvedConst
}
}
// Pattern 2: outputs is an object with properties (e.g., "outputs: { ... }")
// If no outputs found, check for spread inheritance (e.g., "...extendParserTool")
// toolContent may be narrowed past the spread line, so reconstruct the full block
if (Object.keys(outputs).length === 0) {
const outputsStart = toolContent.search(/(?<![a-zA-Z_])outputs\s*:\s*{/)
if (outputsStart !== -1) {
const openBracePos = toolContent.indexOf('{', outputsStart)
if (openBracePos !== -1) {
let braceCount = 1
let pos = openBracePos + 1
while (pos < toolContent.length && braceCount > 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
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 = 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
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 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
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 blockContent = fileContent.substring(startIndex, endIndex)
const blockType = extractStringPropertyFromContent(blockContent, 'type', true)