pomljs
Version:
Prompt Orchestration Markup Language
1 lines • 99.6 kB
Source Map (JSON)
{"version":3,"file":"file.cjs","sources":["../.build/file.js"],"sourcesContent":["/**\n * Handles the files (with .poml extension).\n */\nimport { parse as parseXML } from '@xml-tools/parser';\nimport { buildAst } from '@xml-tools/ast';\nimport * as React from 'react';\nimport { ReadError, SourceProvider, findComponentByAlias, findComponentByAliasOrUndefined, listComponents } from './base';\nimport { deepMerge, parseText, readSource } from './util';\nimport { StyleSheetProvider, ErrorCollection } from './base';\nimport { getSuggestions } from './util/xmlContentAssist';\nimport { existsSync, readFileSync } from './util/fs';\nimport path from 'path';\nimport { POML_VERSION } from './version';\nimport { Schema, ToolsSchema } from './util/schema';\nimport { z } from 'zod';\nexport class PomlFile {\n text;\n sourcePath;\n config;\n ast;\n cst;\n tokenVector;\n documentRange;\n disabledComponents = new Set();\n expressionTokens = [];\n expressionEvaluations = new Map();\n responseSchema;\n toolsSchema;\n runtimeParameters;\n constructor(text, options, sourcePath) {\n this.config = {\n trim: options?.trim ?? true,\n autoAddPoml: options?.autoAddPoml ?? true,\n crlfToLf: options?.crlfToLf ?? true\n };\n this.text = this.config.crlfToLf ? text.replace(/\\r\\n/g, '\\n') : text;\n this.sourcePath = sourcePath;\n if (this.sourcePath) {\n const envFile = this.sourcePath.replace(/(source)?\\.poml$/i, '.env');\n if (existsSync(envFile)) {\n try {\n const envText = readFileSync(envFile, 'utf8');\n const match = envText.match(/^SOURCE_PATH=(.*)$/m);\n if (match) {\n // The real source path is specified in the .env file.\n this.sourcePath = match[1];\n }\n }\n catch {\n /* ignore */\n }\n }\n }\n this.documentRange = { start: 0, end: text.length - 1 };\n let { ast, cst, tokenVector, errors } = this.readXml(text);\n let addPoml = undefined;\n if (this.config.autoAddPoml && text.slice(5).toLowerCase() !== '<poml') {\n if (!ast || !ast.rootElement) {\n // Invalid XML. Treating it as a free text.\n addPoml = '<poml syntax=\"text\" whiteSpace=\"pre\">';\n }\n else if (\n // Valid XML, but contains e.g., multiple root elements.\n (ast.rootElement.position.startOffset > 0 &&\n !this.testAllCommentsAndSpace(0, ast.rootElement.position.startOffset - 1, tokenVector)) ||\n (ast.rootElement.position.endOffset + 1 < text.length &&\n !this.testAllCommentsAndSpace(ast.rootElement.position.endOffset + 1, text.length - 1, tokenVector))) {\n addPoml = '<poml syntax=\"markdown\">';\n }\n }\n if (addPoml) {\n this.documentRange = { start: addPoml.length, end: text.length - 1 + addPoml.length };\n this.config.trim = options?.trim ?? false; // TODO: this is an ad-hoc fix.\n let { ast, cst, tokenVector, errors } = this.readXml(addPoml + text + '</poml>');\n this.ast = ast;\n this.cst = cst;\n this.tokenVector = tokenVector;\n // Report errors\n for (const error of errors) {\n ErrorCollection.add(error);\n }\n }\n else {\n this.ast = ast;\n this.cst = cst;\n this.tokenVector = tokenVector;\n for (const error of errors) {\n ErrorCollection.add(error);\n }\n }\n }\n readXml(text) {\n const { cst, tokenVector, lexErrors, parseErrors } = parseXML(text);\n const errors = [];\n for (const lexError of lexErrors) {\n errors.push(this.formatError(lexError.message, {\n start: this.ensureRange(lexError.offset),\n end: this.ensureRange(lexError.offset)\n }, lexErrors));\n }\n for (const parseError of parseErrors) {\n let startOffset = parseError.token.startOffset;\n let endOffset = parseError.token.endOffset;\n if (isNaN(startOffset) &&\n (endOffset === undefined || isNaN(endOffset)) &&\n parseError.previousToken) {\n startOffset = parseError.previousToken.endOffset;\n endOffset = parseError.previousToken.endOffset;\n }\n errors.push(this.formatError(parseError.message, {\n start: this.ensureRange(startOffset),\n end: endOffset ? this.ensureRange(endOffset) : this.ensureRange(startOffset)\n }, parseError));\n }\n let ast = undefined;\n try {\n ast = buildAst(cst, tokenVector);\n }\n catch (e) {\n errors.push(this.formatError('Error building AST', {\n start: this.ensureRange(0),\n end: this.ensureRange(text.length)\n }, e));\n }\n return { ast, cst, tokenVector, errors };\n }\n testAllCommentsAndSpace(startOffset, endOffset, tokens) {\n // start to end, inclusive. It must not be in the middle of a token.\n const tokensFiltered = tokens.filter((token) => token.startOffset >= startOffset && (token.endOffset ?? token.startOffset) <= endOffset);\n return tokensFiltered.every((token) => {\n if (token.tokenType.name === 'SEA_WS' || token.tokenType.name === 'Comment') {\n return true;\n }\n else if (/^\\s*$/.test(token.image)) {\n return true;\n }\n return false;\n });\n }\n readJsonElement(parent, tagName) {\n const element = xmlElementContents(parent).filter(i => i.type === 'XMLElement' && i.name?.toLowerCase() === tagName.toLowerCase());\n if (element.length === 0) {\n return undefined;\n }\n if (element.length > 1) {\n this.reportError(`Multiple ${tagName} element found.`, {\n start: this.ensureRange(element[0].position.startOffset),\n end: this.ensureRange(element[element.length - 1].position.endOffset)\n });\n return undefined;\n }\n const text = xmlElementText(element[0]);\n try {\n return JSON.parse(text);\n }\n catch (e) {\n this.reportError(e !== undefined && e.message\n ? e.message\n : `Error parsing JSON: ${text}`, this.xmlElementRange(element[0]), e);\n return undefined;\n }\n }\n getResponseSchema() {\n return this.responseSchema;\n }\n getToolsSchema() {\n return this.toolsSchema;\n }\n getRuntimeParameters() {\n return this.runtimeParameters;\n }\n xmlRootElement() {\n if (!this.ast || !this.ast.rootElement) {\n this.reportError('Root element is invalid.', {\n start: this.ensureRange(this.documentRange.start),\n end: this.ensureRange(this.documentRange.end)\n });\n return undefined;\n }\n else {\n return this.ast.rootElement;\n }\n }\n react(context) {\n this.expressionTokens = [];\n this.expressionEvaluations.clear();\n const rootElement = this.xmlRootElement();\n if (rootElement) {\n // See whether stylesheet and context is available\n const stylesheet = this.readJsonElement(rootElement, 'stylesheet');\n context = deepMerge(context || {}, this.readJsonElement(rootElement, 'context') || {});\n let parsedElement = this.parseXmlElement(rootElement, context || {}, {});\n if (stylesheet) {\n parsedElement = React.createElement(StyleSheetProvider, { stylesheet }, parsedElement);\n }\n if (this.sourcePath) {\n parsedElement = React.createElement(SourceProvider, { source: this.sourcePath }, parsedElement);\n }\n return parsedElement;\n }\n else {\n return React.createElement(React.Fragment, null);\n }\n }\n getHoverToken(offset) {\n const realOffset = this.recoverPosition(offset);\n if (!this.ast || !this.ast.rootElement) {\n return undefined;\n }\n return this.findTokenInElement(this.ast.rootElement, realOffset);\n }\n getCompletions(offset) {\n const realOffset = this.recoverPosition(offset);\n if (!this.ast || !this.ast.rootElement) {\n return [];\n }\n return getSuggestions({\n ast: this.ast,\n cst: this.cst,\n tokenVector: this.tokenVector,\n offset: realOffset,\n providers: {\n // 1. There are more types(scenarios) of suggestions providers (see api.d.ts)\n // 2. Multiple providers may be supplied for a single scenario.\n elementName: [this.handleElementNameCompletion(realOffset)],\n elementNameClose: [this.handleElementNameCloseCompletion(realOffset)],\n attributeName: [this.handleAttributeNameCompletion(realOffset)],\n attributeValue: [this.handleAttributeValueCompletion(realOffset)]\n }\n });\n }\n getExpressionTokens() {\n if (this.expressionTokens.length > 0) {\n return this.expressionTokens;\n }\n if (!this.ast || !this.ast.rootElement) {\n return [];\n }\n const tokens = [];\n const regex = /{{\\s*(.+?)\\s*}}(?!})/gm;\n const visit = (element) => {\n // Special handling for schema and tool definition elements with parser=\"eval\"\n const elementName = element.name?.toLowerCase();\n if (elementName === 'output-schema' || elementName === 'outputschema' || elementName === 'tool-definition' || elementName === 'tool-def' || elementName === 'tooldef' || elementName === 'tool') {\n const parserAttr = xmlAttribute(element, 'parser');\n const text = xmlElementText(element).trim();\n // Check if it's an expression (either explicit parser=\"eval\" or auto-detected)\n if (parserAttr?.value === 'eval' || (!parserAttr && !text.trim().startsWith('{'))) {\n const position = this.xmlElementRange(element.textContents[0]);\n tokens.push({\n type: 'expression',\n range: position,\n expression: text.trim(),\n });\n }\n }\n // attributes\n for (const attr of element.attributes) {\n if (!attr.value) {\n continue;\n }\n if (attr.key?.toLowerCase() === 'if' || attr.key?.toLowerCase() === 'for') {\n tokens.push({\n type: 'expression',\n range: this.xmlAttributeValueRange(attr),\n expression: attr.value\n });\n continue;\n }\n if (element.name?.toLowerCase() === 'let' && attr.key?.toLowerCase() === 'value') {\n tokens.push({\n type: 'expression',\n range: this.xmlAttributeValueRange(attr),\n expression: attr.value\n });\n continue;\n }\n const range = this.xmlAttributeValueRange(attr);\n regex.lastIndex = 0;\n let match;\n while ((match = regex.exec(attr.value))) {\n tokens.push({\n type: 'expression',\n range: {\n start: range.start + match.index,\n end: range.start + match.index + match[0].length - 1,\n },\n expression: match[1],\n });\n }\n }\n // text contents\n for (const tc of element.textContents) {\n const text = tc.text || '';\n const pos = this.xmlElementRange(tc);\n // Regular template expression handling for other elements and JSON\n regex.lastIndex = 0;\n let match;\n while ((match = regex.exec(text))) {\n tokens.push({\n type: 'expression',\n range: {\n start: pos.start + match.index,\n end: pos.start + match.index + match[0].length - 1,\n },\n expression: match[1],\n });\n }\n }\n for (const child of element.subElements) {\n visit(child);\n }\n };\n visit(this.ast.rootElement);\n return tokens;\n }\n getExpressionEvaluations(range) {\n const key = `${range.start}:${range.end}`;\n return this.expressionEvaluations.get(key) ?? [];\n }\n recordEvaluation(range, output) {\n const key = `${range.start}:${range.end}`;\n const evaluationList = this.expressionEvaluations.get(key) ?? [];\n evaluationList.push(output);\n this.expressionEvaluations.set(key, evaluationList);\n }\n formatError(msg, range, cause) {\n return ReadError.fromProps(msg, {\n originalStartIndex: range?.start,\n originalEndIndex: range?.end,\n sourcePath: this.sourcePath\n }, { cause: cause });\n }\n reportError(msg, range, cause) {\n ErrorCollection.add(this.formatError(msg, range, cause));\n }\n /**\n * Template related functions only usable in standalone poml files.\n * It's not available for POML expressed with JSX or Python SDK.\n */\n handleForLoop(element, context) {\n const forLoop = element.attributes.find(attr => attr.key?.toLowerCase() === 'for');\n if (!forLoop) {\n // No for loop found.\n return undefined;\n }\n const forLoopValue = forLoop.value;\n if (!forLoopValue) {\n this.reportError('for attribute value is expected.', this.xmlElementRange(element));\n return undefined;\n }\n const [itemName, listName] = forLoopValue.match(/(.+)\\s+in\\s+(.+)/)?.slice(1) || [null, null];\n if (!itemName || !listName) {\n this.reportError('item in list syntax is expected in for attribute.', this.xmlAttributeValueRange(forLoop));\n return undefined;\n }\n const list = this.evaluateExpression(listName, context, this.xmlAttributeValueRange(forLoop));\n if (!Array.isArray(list)) {\n this.reportError('List is expected in for attribute.', this.xmlAttributeValueRange(forLoop));\n return undefined;\n }\n return list.map((item, index) => {\n const loop = {\n index: index,\n length: list.length,\n first: index === 0,\n last: index === list.length - 1\n };\n return { loop: loop, [itemName]: item };\n });\n }\n handleIfCondition = (element, context) => {\n const ifCondition = element.attributes.find(attr => attr.key?.toLowerCase() === 'if');\n if (!ifCondition) {\n // No if condition found.\n return true;\n }\n const ifConditionValue = ifCondition.value;\n if (!ifConditionValue) {\n this.reportError('if attribute value is expected.', this.xmlAttributeValueRange(ifCondition));\n return false;\n }\n const condition = this.evaluateExpression(ifConditionValue, context, this.xmlAttributeValueRange(ifCondition), true);\n if (condition) {\n return true;\n }\n else {\n return false;\n }\n };\n handleLet = (element, contextIn, contextOut) => {\n if (element.name?.toLowerCase() !== 'let') {\n return false;\n }\n const source = xmlAttribute(element, 'src')?.value;\n const type = xmlAttribute(element, 'type')?.value;\n const name = xmlAttribute(element, 'name')?.value;\n const value = xmlAttribute(element, 'value')?.value;\n // Case 1: <let name=\"var1\" src=\"/path/to/file\" />, case insensitive\n // or <let src=\"/path/to/file\" />, case insensitive\n if (source) {\n let content;\n try {\n content = readSource(source, this.sourcePath ? path.dirname(this.sourcePath) : undefined, type);\n }\n catch (e) {\n this.reportError(e !== undefined && e.message\n ? e.message\n : `Error reading source: ${source}`, this.xmlAttributeValueRange(xmlAttribute(element, 'src')), e);\n return true;\n }\n if (!name) {\n if (content && typeof content === 'object') {\n Object.assign(contextOut, content);\n }\n else {\n this.reportError('name attribute is expected when the source is not an object.', this.xmlElementRange(element));\n }\n }\n else {\n contextOut[name] = content;\n }\n return true;\n }\n // Case 2: <let name=\"var1\" value=\"{{ expression }}\" />, case insensitive\n if (value) {\n if (!name) {\n this.reportError('name attribute is expected when <let> contains two attributes.', this.xmlElementRange(element));\n return true;\n }\n const evaluated = this.evaluateExpression(value, contextIn, this.xmlAttributeValueRange(xmlAttribute(element, 'value')), true);\n contextOut[name] = evaluated;\n return true;\n }\n // Case 3: <let>{ JSON }</let>\n // or <let name=\"var1\" type=\"number\">{ JSON }</let>\n if (element.textContents.length > 0) {\n const text = xmlElementText(element);\n let content;\n try {\n content = parseText(text, type);\n }\n catch (e) {\n this.reportError(e !== undefined && e.message\n ? e.message\n : `Error parsing text as type ${type}: ${text}`, this.xmlElementRange(element), e);\n return true;\n }\n if (!name) {\n if (content && typeof content === 'object') {\n Object.assign(contextOut, content);\n }\n else {\n this.reportError('name attribute is expected when the source is not an object.', this.xmlElementRange(element));\n }\n }\n else {\n contextOut[name] = content;\n }\n return true;\n }\n this.reportError('Invalid <let> element.', this.xmlElementRange(element));\n return true;\n };\n handleAttribute = (attribute, context) => {\n if (!attribute.key || !attribute.value) {\n return;\n }\n if (attribute.key.toLowerCase() === 'for' || attribute.key.toLowerCase() === 'if') {\n return;\n }\n const key = hyphenToCamelCase(attribute.key);\n const value = this.handleText(attribute.value, context, this.xmlAttributeValueRange(attribute));\n if (value.length === 1) {\n return [key, value[0]];\n }\n else {\n return [key, value];\n }\n };\n handleInclude = (element, context) => {\n if (element.name?.toLowerCase() !== 'include') {\n return undefined;\n }\n const src = xmlAttribute(element, 'src');\n if (!src || !src.value) {\n this.reportError('src attribute is expected.', this.xmlElementRange(element));\n return React.createElement(React.Fragment, null);\n }\n const source = src.value;\n let text;\n try {\n text = readSource(source, this.sourcePath ? path.dirname(this.sourcePath) : undefined, 'string');\n }\n catch (e) {\n this.reportError(e !== undefined && e.message\n ? e.message\n : `Error reading source: ${source}`, this.xmlAttributeValueRange(src), e);\n return React.createElement(React.Fragment, null);\n }\n const includePath = this.sourcePath && !path.isAbsolute(source)\n ? path.join(path.dirname(this.sourcePath), source)\n : source;\n const included = new PomlFile(text, this.config, includePath);\n const root = included.xmlRootElement();\n if (!root) {\n return React.createElement(React.Fragment, null);\n }\n let contents = [];\n if (root.name?.toLowerCase() === 'poml') {\n contents = xmlElementContents(root);\n }\n else {\n contents = [root];\n }\n const resultNodes = [];\n contents.forEach((el, idx) => {\n if (el.type === 'XMLTextContent') {\n resultNodes.push(...included\n .handleText(el.text ?? '', context, included.xmlElementRange(el))\n .map(v => typeof v === 'object' && v !== null && !React.isValidElement(v)\n ? JSON.stringify(v)\n : v));\n }\n else if (el.type === 'XMLElement') {\n const child = included.parseXmlElement(el, context, {});\n resultNodes.push(React.isValidElement(child) ? React.cloneElement(child, { key: `child-${idx}` }) : child);\n }\n });\n if (resultNodes.length === 1) {\n return React.createElement(React.Fragment, null, resultNodes[0]);\n }\n return React.createElement(React.Fragment, null, resultNodes);\n };\n handleSchema = (element, context) => {\n const parserAttr = xmlAttribute(element, 'parser');\n let parser = parserAttr?.value\n ? this.handleTextAsString(parserAttr.value, context || {}, this.xmlAttributeValueRange(parserAttr))\n : undefined;\n const text = xmlElementText(element).trim();\n // Get the range for the text content (if available)\n const textRange = element.textContents.length > 0\n ? this.xmlElementRange(element.textContents[0])\n : this.xmlElementRange(element);\n // Auto-detect parser if not specified\n if (!parser) {\n if (text.startsWith('{')) {\n parser = 'json';\n }\n else {\n parser = 'eval';\n }\n }\n else if (parser !== 'json' && parser !== 'eval') {\n this.reportError(`Invalid parser attribute: ${parser}. Expected \"json\" or \"eval\"`, this.xmlAttributeValueRange(xmlAttribute(element, 'parser')));\n return undefined;\n }\n try {\n if (parser === 'json') {\n // Process template expressions in JSON text\n const processedText = this.handleText(text, context || {}, textRange);\n // handleText returns an array, join if all strings\n const jsonText = processedText.length === 1 && typeof processedText[0] === 'string'\n ? processedText[0]\n : processedText.map(p => typeof p === 'string' ? p : JSON.stringify(p)).join('');\n const jsonSchema = JSON.parse(jsonText);\n return Schema.fromOpenAPI(jsonSchema);\n }\n else if (parser === 'eval') {\n // Evaluate expression directly with z in context\n const contextWithZ = { z, ...context };\n const result = this.evaluateExpression(text, contextWithZ, textRange, true);\n // If evaluation failed, result will be empty string\n if (!result) {\n return undefined;\n }\n // Determine if result is a Zod schema or JSON schema\n if (result && typeof result === 'object' && result._def) {\n // It's a Zod schema\n return Schema.fromZod(result);\n }\n else {\n // Treat as JSON schema\n return Schema.fromOpenAPI(result);\n }\n }\n }\n catch (e) {\n this.reportError(e instanceof Error ? e.message : 'Error parsing schema', this.xmlElementRange(element), e);\n }\n return undefined;\n };\n handleOutputSchema = (element, context) => {\n const elementName = element.name?.toLowerCase();\n if (elementName !== 'output-schema' && elementName !== 'outputschema') {\n return false;\n }\n if (this.responseSchema) {\n this.reportError('Multiple output-schema elements found. Only one is allowed.', this.xmlElementRange(element));\n return true;\n }\n const schema = this.handleSchema(element, context);\n if (schema) {\n this.responseSchema = schema;\n }\n return true;\n };\n handleToolDefinition = (element, context) => {\n const elementName = element.name?.toLowerCase();\n if (elementName !== 'tool-definition' && elementName !== 'tool-def' && elementName !== 'tooldef' && elementName !== 'tool') {\n return false;\n }\n const nameAttr = xmlAttribute(element, 'name');\n if (!nameAttr?.value) {\n this.reportError('name attribute is required for tool definition', this.xmlElementRange(element));\n return true;\n }\n // Process template expressions in name attribute\n const name = this.handleTextAsString(nameAttr.value, context || {}, this.xmlAttributeValueRange(nameAttr));\n const descriptionAttr = xmlAttribute(element, 'description');\n // Process template expressions in description attribute if present\n const description = descriptionAttr?.value\n ? this.handleTextAsString(descriptionAttr.value, context || {}, this.xmlAttributeValueRange(descriptionAttr))\n : undefined;\n const inputSchema = this.handleSchema(element, context);\n if (inputSchema) {\n if (!this.toolsSchema) {\n this.toolsSchema = new ToolsSchema();\n }\n try {\n this.toolsSchema.addTool(name, description || undefined, inputSchema);\n }\n catch (e) {\n this.reportError(e instanceof Error ? e.message : 'Error adding tool to tools schema', this.xmlElementRange(element), e);\n }\n }\n return true;\n };\n handleRuntime = (element, context) => {\n const elementName = element.name?.toLowerCase();\n if (elementName !== 'runtime') {\n return false;\n }\n // Extract runtime parameters from all attributes\n const runtimeParams = {};\n for (const attribute of element.attributes) {\n if (attribute.key && attribute.value) {\n // Process template expressions and convert to string\n const stringValue = this.handleTextAsString(attribute.value, context || {}, this.xmlAttributeValueRange(attribute));\n // Convert key to camelCase (kebab-case to camelCase)\n const camelKey = hyphenToCamelCase(attribute.key);\n // Convert value (auto-convert booleans, numbers, JSON)\n const convertedValue = this.convertRuntimeValue(stringValue);\n runtimeParams[camelKey] = convertedValue;\n }\n }\n this.runtimeParameters = runtimeParams;\n return true;\n };\n convertRuntimeValue = (value) => {\n // Convert boolean-like values\n if (value === 'true') {\n return true;\n }\n if (value === 'false') {\n return false;\n }\n // Convert number-like values\n if (/^-?\\d*\\.?\\d+$/.test(value)) {\n const num = parseFloat(value);\n if (!isNaN(num)) {\n return num;\n }\n }\n // Convert JSON-like values (arrays and objects)\n if ((value.startsWith('[') && value.endsWith(']')) ||\n (value.startsWith('{') && value.endsWith('}'))) {\n try {\n return JSON.parse(value);\n }\n catch {\n // If JSON parsing fails, return as string\n return value;\n }\n }\n // Return as string for everything else\n return value;\n };\n handleMeta = (element, context) => {\n if (element.name?.toLowerCase() !== 'meta') {\n return false;\n }\n // Check if this is an old-style meta with type attribute\n const metaType = xmlAttribute(element, 'type')?.value;\n if (metaType) {\n this.reportError(`Meta elements with type attribute have been removed. Use <${metaType === 'schema' ? 'output-schema' : metaType === 'tool' ? 'tool-definition' : metaType === 'runtime' ? 'runtime' : metaType}> instead of <meta type=\"${metaType}\">`, this.xmlElementRange(element));\n return true;\n }\n // Handle version control\n const minVersion = xmlAttribute(element, 'minVersion')?.value;\n if (minVersion && compareVersions(POML_VERSION, minVersion) < 0) {\n this.reportError(`POML version ${minVersion} or higher is required`, this.xmlAttributeValueRange(xmlAttribute(element, 'minVersion')));\n }\n const maxVersion = xmlAttribute(element, 'maxVersion')?.value;\n if (maxVersion && compareVersions(POML_VERSION, maxVersion) > 0) {\n this.reportError(`POML version ${maxVersion} or lower is required`, this.xmlAttributeValueRange(xmlAttribute(element, 'maxVersion')));\n }\n const comps = xmlAttribute(element, 'components')?.value;\n if (comps) {\n comps.split(/[,\\s]+/).forEach(token => {\n token = token.trim();\n if (!token) {\n return;\n }\n const op = token[0];\n const name = token.slice(1).toLowerCase().trim();\n if (!name) {\n return;\n }\n if (op === '+') {\n this.disabledComponents.delete(name);\n }\n else if (op === '-') {\n this.disabledComponents.add(name);\n }\n else {\n this.reportError(`Invalid component operation: ${op}. Use + to enable or - to disable.`, this.xmlAttributeValueRange(xmlAttribute(element, 'components')));\n }\n });\n }\n return true;\n };\n unescapeText = (text) => {\n return text\n .replace(/#lt;/g, '<')\n .replace(/#gt;/g, '>')\n .replace(/#amp;/g, '&')\n .replace(/#quot;/g, '\"')\n .replace(/#apos;/g, \"'\")\n .replace(/#hash;/g, '#')\n .replace(/#lbrace;/g, '{')\n .replace(/#rbrace;/g, '}');\n };\n handleText = (text, context, position) => {\n let curlyMatch;\n let replacedPrefixLength = 0;\n let results = [];\n const regex = /{{\\s*(.+?)\\s*}}(?!})/gm;\n while ((curlyMatch = regex.exec(text))) {\n const curlyExpression = curlyMatch[1];\n const value = this.evaluateExpression(curlyExpression, context, position\n ? {\n start: position.start + curlyMatch.index,\n end: position.start + curlyMatch.index + curlyMatch[0].length - 1\n }\n : undefined);\n if (this.config.trim && curlyMatch[0] === text.trim()) {\n return [value];\n }\n if (curlyMatch.index > replacedPrefixLength) {\n results.push(this.unescapeText(text.slice(replacedPrefixLength, curlyMatch.index)));\n }\n results.push(value);\n replacedPrefixLength = curlyMatch.index + curlyMatch[0].length;\n }\n if (text.length > replacedPrefixLength) {\n results.push(this.unescapeText(text.slice(replacedPrefixLength)));\n }\n if (results.length > 0 && results.every(r => typeof r === 'string' || typeof r === 'number')) {\n return [results.map(r => r.toString()).join('')];\n }\n return results;\n };\n handleTextAsString = (text, context, position) => {\n const results = this.handleText(text, context, position);\n if (results.length === 1) {\n if (typeof results[0] === 'string') {\n return results[0];\n }\n else {\n return results[0].toString();\n }\n }\n else {\n return JSON.stringify(results);\n }\n };\n evaluateExpression(expression, context, range, stripCurlyBrackets = false) {\n try {\n if (stripCurlyBrackets) {\n const curlyMatch = expression.match(/^\\s*{{\\s*(.+?)\\s*}}\\s*$/m);\n if (curlyMatch) {\n expression = curlyMatch[1];\n }\n }\n const result = evalWithVariables(expression, context || {});\n if (range) {\n this.recordEvaluation(range, result);\n }\n return result;\n }\n catch (e) {\n const errMessage = e !== undefined && e.message\n ? e.message\n : `Error evaluating expression: ${expression}`;\n if (range) {\n this.recordEvaluation(range, errMessage);\n }\n this.reportError(errMessage, range, e);\n return '';\n }\n }\n /**\n * Parse the XML element and return the corresponding React element.\n *\n * @param element The element to be converted.\n * @param globalContext The context can be carried over when the function returns.\n * @param localContext The context that is only available in the current element and its children.\n */\n parseXmlElement(element, globalContext, localContext) {\n // Let. Always set the global.\n if (this.handleLet(element, { ...globalContext, ...localContext }, globalContext)) {\n return React.createElement(React.Fragment, null);\n }\n const tagName = element.name;\n if (!tagName) {\n // Probably already had an invalid syntax error.\n return React.createElement(React.Fragment, null);\n }\n const tagNameLower = tagName.toLowerCase();\n const isMeta = tagNameLower === 'meta';\n const isInclude = tagNameLower === 'include';\n const isOutputSchema = tagNameLower === 'output-schema' || tagNameLower === 'outputschema';\n const isToolDefinition = tagNameLower === 'tool-definition' || tagNameLower === 'tool-def' || tagNameLower === 'tooldef' || tagNameLower === 'tool';\n const isRuntime = tagNameLower === 'runtime';\n // Common logic for handling for-loops\n const forLoops = this.handleForLoop(element, { ...globalContext, ...localContext });\n const forLoopedContext = forLoops === undefined ? [{}] : forLoops;\n const resultElements = [];\n for (let i = 0; i < forLoopedContext.length; i++) {\n const currentLocal = { ...localContext, ...forLoopedContext[i] };\n const context = { ...globalContext, ...currentLocal };\n // Common logic for handling if-conditions\n if (!this.handleIfCondition(element, context)) {\n continue;\n }\n // Common logic for handling meta elements and new schema/tool elements\n if (isMeta && this.handleMeta(element, context)) {\n // If it's a meta element, we don't render anything.\n continue;\n }\n if (isOutputSchema && this.handleOutputSchema(element, context)) {\n // If it's an output-schema element, we don't render anything.\n continue;\n }\n if (isToolDefinition && this.handleToolDefinition(element, context)) {\n // If it's a tool-definition element, we don't render anything.\n continue;\n }\n if (isRuntime && this.handleRuntime(element, context)) {\n // If it's a runtime element, we don't render anything.\n continue;\n }\n let elementToAdd = null;\n if (isInclude) {\n // Logic for <include> tags\n const included = this.handleInclude(element, context);\n if (included) {\n // Add a key if we are in a loop with multiple items\n if (forLoopedContext.length > 1) {\n elementToAdd = React.createElement(React.Fragment, { key: `include-${i}` }, included);\n }\n else {\n elementToAdd = included;\n }\n }\n }\n else {\n // Logic for all other components\n const component = findComponentByAlias(tagName, this.disabledComponents);\n if (typeof component === 'string') {\n // Add a read error\n this.reportError(component, this.xmlOpenNameRange(element));\n // Return empty fragment to prevent rendering this element\n // You might want to 'continue' the loop as well.\n return React.createElement(React.Fragment, null);\n }\n const attrib = element.attributes.reduce((acc, attribute) => {\n const [key, value] = this.handleAttribute(attribute, context) || [null, null];\n if (key && value !== null) {\n acc[key] = value;\n }\n return acc;\n }, {});\n // Retain the position of current element for future diagnostics\n const range = this.xmlElementRange(element);\n attrib.originalStartIndex = range.start;\n attrib.originalEndIndex = range.end;\n // Add key attribute for react\n if (!attrib.key && forLoopedContext.length > 1) {\n attrib.key = `key-${i}`;\n }\n const contents = xmlElementContents(element).filter(el => {\n // Filter out stylesheet and context element in the root poml element\n if (tagName === 'poml' &&\n el.type === 'XMLElement' &&\n ['context', 'stylesheet'].includes(el.name?.toLowerCase() ?? '')) {\n return false;\n }\n else {\n return true;\n }\n });\n const avoidObject = (el) => {\n if (typeof el === 'object' && el !== null && !React.isValidElement(el)) {\n return JSON.stringify(el);\n }\n return el;\n };\n const processedContents = contents.reduce((acc, el, i) => {\n if (el.type === 'XMLTextContent') {\n // const isFirst = i === 0,\n // isLast = i === contents.length - 1;\n // const text = this.config.trim ? trimText(el.text || '', isFirst, isLast) : el.text || '';\n acc.push(...this.handleText(el.text ?? '', { ...globalContext, ...currentLocal }, this.xmlElementRange(el)).map(avoidObject));\n }\n else if (el.type === 'XMLElement') {\n acc.push(this.parseXmlElement(el, globalContext, currentLocal));\n }\n return acc;\n }, []);\n elementToAdd = React.createElement(component.render.bind(component), attrib, ...processedContents);\n }\n if (elementToAdd) {\n // If we have an element to add, push it to the result elements.\n resultElements.push(elementToAdd);\n }\n }\n // Common logic for returning the final result\n if (resultElements.length === 1) {\n return resultElements[0];\n }\n else {\n // Cases where there are multiple elements or zero elements.\n return React.createElement(React.Fragment, null, resultElements);\n }\n }\n recoverPosition(position) {\n return position + this.documentRange.start;\n }\n ensureRange(position) {\n return Math.max(Math.min(position, this.documentRange.end) - this.documentRange.start, 0);\n }\n xmlElementRange(element) {\n return {\n start: this.ensureRange(element.position.startOffset),\n end: this.ensureRange(element.position.endOffset)\n };\n }\n xmlOpenNameRange(element) {\n if (element.syntax.openName) {\n return {\n start: this.ensureRange(element.syntax.openName.startOffset),\n end: this.ensureRange(element.syntax.openName.endOffset)\n };\n }\n else {\n return this.xmlElementRange(element);\n }\n }\n xmlCloseNameRange(element) {\n if (element.syntax.closeName) {\n return {\n start: this.ensureRange(element.syntax.closeName.startOffset),\n end: this.ensureRange(element.syntax.closeName.endOffset)\n };\n }\n else {\n return this.xmlElementRange(element);\n }\n }\n xmlAttributeKeyRange(element) {\n if (element.syntax.key) {\n return {\n start: this.ensureRange(element.syntax.key.startOffset),\n end: this.ensureRange(element.syntax.key.endOffset)\n };\n }\n else {\n return this.xmlElementRange(element);\n }\n }\n xmlAttributeValueRange(element) {\n if (element.syntax.value) {\n return {\n start: this.ensureRange(element.syntax.value.startOffset),\n end: this.ensureRange(element.syntax.value.endOffset)\n };\n }\n else {\n return this.xmlElementRange(element);\n }\n }\n findTokenInElement(element, offset) {\n if (element.name) {\n if (element.syntax.openName &&\n element.syntax.openName.startOffset <= offset &&\n offset <= element.syntax.openName.endOffset) {\n return {\n type: 'element',\n range: this.xmlOpenNameRange(element),\n element: element.name\n };\n }\n if (element.syntax.closeName &&\n element.syntax.closeName.startOffset <= offset &&\n offset <= element.syntax.closeName.endOffset) {\n return {\n type: 'element',\n range: this.xmlCloseNameRange(element),\n element: element.name\n };\n }\n for (const attrib of element.attributes) {\n if (attrib.key &&\n attrib.syntax.key &&\n attrib.syntax.key.startOffset <= offset &&\n offset <= attrib.syntax.key.endOffset) {\n return {\n type: 'attribute',\n range: this.xmlAttributeKeyRange(attrib),\n element: element.name,\n attribute: attrib.key\n };\n }\n }\n }\n for (const child of element.subElements) {\n const result = this.findTokenInElement(child, offset);\n if (result) {\n return result;\n }\n }\n }\n handleElementNameCompletion(offset) {\n return ({ element, prefix }) => {\n const candidates = this.findComponentWithPrefix(prefix, true);\n return candidates.map(candidate => {\n return {\n type: 'element',\n range: {\n start: this.ensureRange(offset - (prefix ? prefix.length : 0)),\n end: this.ensureRange(offset - 1)\n },\n element: candidate\n };\n });\n };\n }\n handleElementNameCloseCompletion(offset) {\n return ({ element, prefix }) => {\n const candidates = [];\n const excludedComponents = [];\n if (element.name) {\n candidates.push(element.name);\n const component = findComponentByAliasOrUndefined(element.name, this.disabledComponents);\n if (component !== undefined) {\n excludedComponents.push(component.name);\n }\n }\n if (prefix) {\n candidates.push(...this.findComponentWithPrefix(prefix, true, excludedComponents));\n }\n return candidates.map(candidate => {\n return {\n type: 'element',\n range: {\n start: this.ensureRange(offset - (prefix ? prefix.length : 0)),\n end: this.ensureRange(offset - 1)\n },\n element: candidate\n };\n });\n };\n }\n handleAttributeNameCompletion(offset) {\n return ({ element, prefix }) => {\n if (!element.name) {\n return [];\n }\n const component = findComponentByAliasOrUndefined(element.name, this.disabledComponents);\n const parameters = component?.parameters();\n if (!component || !parameters) {\n return [];\n }\n const candidates = [];\n for (const parameter of parameters) {\n if (parameter.name.toLowerCase().startsWith(prefix?.toLowerCase() ?? '')) {\n candidates.push({\n type: 'attribute',\n range: {\n start: this.ensureRange(offset - (prefix ? prefix.length : 0)),\n end: this.ensureRange(offset - 1)\n },\n element: component.name,\n attribute: parameter.name\n });\n }\n }\n return candidates;\n };\n }\n handleAttributeValueCompletion(offset) {\n return ({ element, attribute, prefix }) => {\n if (!element.name) {\n return [];\n }\n const component = findComponentByAliasOrUndefined(element.name, this.disabledComponents);\n const parameters = component?.parameters();\n if (!component || !parameters) {\n return [];\n }\n const candidates = [];\n for (const parameter of parameters) {\n if (parameter.name.toLowerCase() === attribute.key?.toLowerCase()) {\n for (const choice of parameter.choices) {\n if (choice.toLowerCase().startsWith(prefix?.toLowerCase() ?? '')) {\n candidates.push({\n type: 'attributeValue',\n range: {\n start: this.ensureRange(offset - (prefix ? prefix.length : 0)),\n end: this.ensureRange(offset - 1)\n },\n element: component.name,\n attribute: parameter.name,\n value: choice\n });\n }\n }\n }\n }\n return candidates;\n };\n }\n findComponentWithPrefix(prefix, publicOnly, excludedComponents) {\n const candidates = [];\n for (const component of listComponents()) {\n if (publicOnly && !component.isPublic()) {\n continue;\n }\n if (excludedComponents && excludedComponents.includes(component.name)) {\n continue;\n }\n let nameMatch = undefined;\n if (!prefix || component.name.toLowerCase().startsWith(prefix.toLowerCase()))