{"version":3,"file":"chain-serializer.d.ts","sourceRoot":"","sources":["../../../src/agents/chain-serializer.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAmB,MAAM,aAAa,CAAC;AAkG7E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAuC9F;AAOD,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CA4FlG;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAa9D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAqC1D","sourcesContent":["import { validateAcceptanceInput } from \"../runs/shared/acceptance.ts\";\nimport { ChainOutputValidationError, validateChainOutputBindings } from \"../runs/shared/chain-outputs.ts\";\nimport { validateToolBudgetConfig } from \"../runs/shared/tool-budget.ts\";\nimport type { ChainStep } from \"../shared/settings.ts\";\nimport type { AgentSource, ChainConfig, ChainStepConfig } from \"./agents.ts\";\nimport { parseFrontmatter } from \"./frontmatter.ts\";\nimport { buildRuntimeName, frontmatterNameForConfig, parsePackageName } from \"./identity.ts\";\n\nfunction parseStepBody(agent: string, sectionBody: string): ChainStepConfig {\n\tconst lines = sectionBody.split(\"\\n\");\n\tconst blankIndex = lines.findIndex((line) => line.trim() === \"\");\n\tconst configLines = blankIndex === -1 ? lines : lines.slice(0, blankIndex);\n\tconst task = (blankIndex === -1 ? \"\" : lines.slice(blankIndex + 1).join(\"\\n\")).trim();\n\n\tconst step: ChainStepConfig = { agent, task };\n\tfor (const line of configLines) {\n\t\tconst match = line.match(/^([\\w-]+):\\s*(.*)$/);\n\t\tif (!match) continue;\n\t\tconst [keyValue, rawValueValue] = match.slice(1);\n\t\tif (keyValue === undefined || rawValueValue === undefined) continue;\n\t\tconst key = keyValue.trim().toLowerCase();\n\t\tconst rawValue = rawValueValue.trim();\n\n\t\tif (key === \"output\") {\n\t\t\tif (rawValue === \"false\") step.output = false;\n\t\t\telse if (rawValue) step.output = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"phase\") {\n\t\t\tif (rawValue) step.phase = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"label\") {\n\t\t\tif (rawValue) step.label = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"as\") {\n\t\t\tif (rawValue) step.as = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"outputschema\") {\n\t\t\tif (rawValue.startsWith(\"{\") || rawValue.startsWith(\"[\")) {\n\t\t\t\tthrow new Error(\"Inline outputSchema values are not supported in .chain.md files; use a schema file path.\");\n\t\t\t}\n\t\t\tif (rawValue) step.outputSchema = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"outputmode\") {\n\t\t\tif (rawValue === \"inline\" || rawValue === \"file-only\") step.outputMode = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"reads\") {\n\t\t\tif (rawValue === \"false\") {\n\t\t\t\tstep.reads = false;\n\t\t\t} else {\n\t\t\t\tconst reads = rawValue\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((v) => v.trim())\n\t\t\t\t\t.filter(Boolean);\n\t\t\t\tstep.reads = reads.length > 0 ? reads : false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"model\") {\n\t\t\tif (rawValue) step.model = rawValue;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"skills\") {\n\t\t\tif (rawValue === \"false\") {\n\t\t\t\tstep.skills = false;\n\t\t\t} else {\n\t\t\t\tconst skills = rawValue\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((v) => v.trim())\n\t\t\t\t\t.filter(Boolean);\n\t\t\t\tstep.skills = skills.length > 0 ? skills : false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"progress\") {\n\t\t\tif (rawValue === \"true\") step.progress = true;\n\t\t\telse if (rawValue === \"false\") step.progress = false;\n\t\t\tcontinue;\n\t\t}\n\t\tif (key === \"toolbudget\") {\n\t\t\tlet parsed: unknown;\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(rawValue);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tthrow new Error(`Invalid toolBudget in .chain.md step '${agent}': ${message}`);\n\t\t\t}\n\t\t\tconst validation = validateToolBudgetConfig(parsed, `toolBudget for step '${agent}'`);\n\t\t\tif (validation.error) throw new Error(validation.error);\n\t\t\tconst toolBudget = parsed as ChainStepConfig[\"toolBudget\"];\n\t\t\tif (toolBudget !== undefined) step.toolBudget = toolBudget;\n\t\t}\n\t}\n\n\treturn step;\n}\n\nexport function parseChain(content: string, source: AgentSource, filePath: string): ChainConfig {\n\tconst { frontmatter, body } = parseFrontmatter(content);\n\tif (!frontmatter.name || !frontmatter.description) {\n\t\tthrow new Error(\"Chain frontmatter must include name and description\");\n\t}\n\n\tconst matches = [...body.matchAll(/^##\\s+(.+)[^\\S\\n]*$/gm)];\n\tconst steps: ChainStepConfig[] = [];\n\n\tfor (let i = 0; i < matches.length; i++) {\n\t\tconst match = matches[i]!;\n\t\tconst agent = match[1]!.trim();\n\t\tconst lineEndOffset = body[match.index! + match[0].length] === \"\\n\" ? 1 : 0;\n\t\tconst sectionStart = match.index! + match[0].length + lineEndOffset;\n\t\tconst sectionEnd = i + 1 < matches.length ? matches[i + 1]!.index! : body.length;\n\t\tconst sectionBody = body.slice(sectionStart, sectionEnd).trimEnd();\n\t\tsteps.push(parseStepBody(agent, sectionBody));\n\t}\n\n\tconst localName = frontmatter.name;\n\tconst parsedPackage = parsePackageName(frontmatter.package, `Chain '${localName}' package`);\n\tif (parsedPackage.error) throw new Error(parsedPackage.error);\n\tconst packageName = parsedPackage.packageName;\n\tconst extraFields: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(frontmatter)) {\n\t\tif (key === \"name\" || key === \"package\" || key === \"description\") continue;\n\t\textraFields[key] = value;\n\t}\n\n\treturn {\n\t\tname: buildRuntimeName(localName, packageName),\n\t\tlocalName,\n\t\t...(packageName !== undefined ? { packageName } : {}),\n\t\tdescription: frontmatter.description,\n\t\tsource,\n\t\tfilePath,\n\t\tsteps,\n\t\t...(Object.keys(extraFields).length > 0 ? { extraFields } : {}),\n\t};\n}\n\nfunction validateJsonChainToolBudget(value: unknown, label: string): void {\n\tconst validation = validateToolBudgetConfig(value, label);\n\tif (validation.error) throw new Error(validation.error);\n}\n\nexport function parseJsonChain(content: string, source: AgentSource, filePath: string): ChainConfig {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tthrow new Error(`Invalid JSON chain '${filePath}': ${message}`);\n\t}\n\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n\t\tthrow new Error(`JSON chain '${filePath}' must contain an object root.`);\n\t}\n\tconst input = parsed as Record<string, unknown>;\n\tif (typeof input.name !== \"string\" || !input.name.trim()) {\n\t\tthrow new Error(`JSON chain '${filePath}' must include string name.`);\n\t}\n\tif (typeof input.description !== \"string\" || !input.description.trim()) {\n\t\tthrow new Error(`JSON chain '${filePath}' must include string description.`);\n\t}\n\tif (!Array.isArray(input.chain)) {\n\t\tthrow new Error(`JSON chain '${filePath}' must include array chain.`);\n\t}\n\tfor (let i = 0; i < input.chain.length; i++) {\n\t\tconst step = input.chain[i];\n\t\tif (!step || typeof step !== \"object\" || Array.isArray(step)) {\n\t\t\tthrow new Error(`JSON chain '${filePath}' step ${i + 1} must be an object.`);\n\t\t}\n\t\tconst stepRecord = step as Record<string, unknown>;\n\t\tif (stepRecord.toolBudget !== undefined)\n\t\t\tvalidateJsonChainToolBudget(stepRecord.toolBudget, `step ${i + 1} toolBudget`);\n\t\tconst acceptanceErrors = validateAcceptanceInput(stepRecord.acceptance, `step ${i + 1} acceptance`);\n\t\tif (acceptanceErrors.length > 0) {\n\t\t\tthrow new Error(`Invalid JSON chain '${filePath}': ${acceptanceErrors.join(\" \")}`);\n\t\t}\n\t\tconst parallel = stepRecord.parallel;\n\t\tif (Array.isArray(parallel)) {\n\t\t\tfor (let taskIndex = 0; taskIndex < parallel.length; taskIndex++) {\n\t\t\t\tconst task = parallel[taskIndex];\n\t\t\t\tif (!task || typeof task !== \"object\" || Array.isArray(task)) continue;\n\t\t\t\tconst taskRecord = task as Record<string, unknown>;\n\t\t\t\tif (taskRecord.toolBudget !== undefined)\n\t\t\t\t\tvalidateJsonChainToolBudget(\n\t\t\t\t\t\ttaskRecord.toolBudget,\n\t\t\t\t\t\t`step ${i + 1} parallel task ${taskIndex + 1} toolBudget`,\n\t\t\t\t\t);\n\t\t\t\tconst taskErrors = validateAcceptanceInput(\n\t\t\t\t\ttaskRecord.acceptance,\n\t\t\t\t\t`step ${i + 1} parallel task ${taskIndex + 1} acceptance`,\n\t\t\t\t);\n\t\t\t\tif (taskErrors.length > 0) {\n\t\t\t\t\tthrow new Error(`Invalid JSON chain '${filePath}': ${taskErrors.join(\" \")}`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (parallel && typeof parallel === \"object\") {\n\t\t\tconst parallelRecord = parallel as Record<string, unknown>;\n\t\t\tif (parallelRecord.toolBudget !== undefined)\n\t\t\t\tvalidateJsonChainToolBudget(parallelRecord.toolBudget, `step ${i + 1} dynamic template toolBudget`);\n\t\t\tconst templateErrors = validateAcceptanceInput(\n\t\t\t\tparallelRecord.acceptance,\n\t\t\t\t`step ${i + 1} dynamic template acceptance`,\n\t\t\t);\n\t\t\tif (templateErrors.length > 0) {\n\t\t\t\tthrow new Error(`Invalid JSON chain '${filePath}': ${templateErrors.join(\" \")}`);\n\t\t\t}\n\t\t}\n\t}\n\ttry {\n\t\tvalidateChainOutputBindings(input.chain as ChainStep[], { maxItems: Number.MAX_SAFE_INTEGER });\n\t} catch (error) {\n\t\tif (error instanceof ChainOutputValidationError)\n\t\t\tthrow new Error(`Invalid JSON chain '${filePath}': ${error.message}`);\n\t\tthrow error;\n\t}\n\tconst parsedPackage = parsePackageName(\n\t\ttypeof input.package === \"string\" ? input.package : undefined,\n\t\t`Chain '${input.name}' package`,\n\t);\n\tif (parsedPackage.error) throw new Error(parsedPackage.error);\n\tconst extraFields: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(input)) {\n\t\tif (key === \"name\" || key === \"package\" || key === \"description\" || key === \"chain\") continue;\n\t\tif (typeof value === \"string\") extraFields[key] = value;\n\t}\n\treturn {\n\t\tname: buildRuntimeName(input.name.trim(), parsedPackage.packageName),\n\t\tlocalName: input.name.trim(),\n\t\t...(parsedPackage.packageName !== undefined ? { packageName: parsedPackage.packageName } : {}),\n\t\tdescription: input.description.trim(),\n\t\tsource,\n\t\tfilePath,\n\t\tsteps: input.chain as ChainStepConfig[],\n\t\t...(Object.keys(extraFields).length > 0 ? { extraFields } : {}),\n\t};\n}\n\nexport function serializeJsonChain(config: ChainConfig): string {\n\tconst root: Record<string, unknown> = {\n\t\tname: frontmatterNameForConfig(config),\n\t\tdescription: config.description,\n\t\tchain: config.steps,\n\t};\n\tif (config.packageName) root.package = config.packageName;\n\tif (config.extraFields) {\n\t\tfor (const [key, value] of Object.entries(config.extraFields)) {\n\t\t\tif (key !== \"name\" && key !== \"description\" && key !== \"package\" && key !== \"chain\") root[key] = value;\n\t\t}\n\t}\n\treturn `${JSON.stringify(root, null, 2)}\\n`;\n}\n\nexport function serializeChain(config: ChainConfig): string {\n\tconst lines: string[] = [];\n\tlines.push(\"---\");\n\tlines.push(`name: ${frontmatterNameForConfig(config)}`);\n\tif (config.packageName) lines.push(`package: ${config.packageName}`);\n\tlines.push(`description: ${config.description}`);\n\tif (config.extraFields) {\n\t\tfor (const [key, value] of Object.entries(config.extraFields)) {\n\t\t\tlines.push(`${key}: ${value}`);\n\t\t}\n\t}\n\tlines.push(\"---\");\n\tlines.push(\"\");\n\n\tfor (let i = 0; i < config.steps.length; i++) {\n\t\tconst step = config.steps[i]!;\n\t\tlines.push(`## ${step.agent}`);\n\t\tif (step.output === false) lines.push(\"output: false\");\n\t\telse if (step.output) lines.push(`output: ${step.output}`);\n\t\tif (step.phase) lines.push(`phase: ${step.phase}`);\n\t\tif (step.label) lines.push(`label: ${step.label}`);\n\t\tif (step.as) lines.push(`as: ${step.as}`);\n\t\tif (step.outputSchema) lines.push(`outputSchema: ${step.outputSchema}`);\n\t\tif (step.outputMode) lines.push(`outputMode: ${step.outputMode}`);\n\t\tif (step.reads === false) lines.push(\"reads: false\");\n\t\telse if (Array.isArray(step.reads) && step.reads.length > 0) lines.push(`reads: ${step.reads.join(\", \")}`);\n\t\tif (step.model) lines.push(`model: ${step.model}`);\n\t\tif (step.skills === false) lines.push(\"skills: false\");\n\t\telse if (Array.isArray(step.skills) && step.skills.length > 0) lines.push(`skills: ${step.skills.join(\", \")}`);\n\t\tif (step.progress !== undefined) lines.push(`progress: ${step.progress ? \"true\" : \"false\"}`);\n\t\tif (step.toolBudget !== undefined) lines.push(`toolBudget: ${JSON.stringify(step.toolBudget)}`);\n\t\tlines.push(\"\");\n\t\tlines.push(step.task ?? \"\");\n\t\tif (i < config.steps.length - 1) lines.push(\"\");\n\t}\n\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n"]}