From ce514708338d80fc4ad020bf2a2bfddaf85d0194 Mon Sep 17 00:00:00 2001 From: Sylvain Perron <1315508+slvnperron@users.noreply.github.com> Date: Fri, 29 May 2026 09:36:19 -0400 Subject: [PATCH 1/8] fix(zai): markdown table rows mangled by doubled leading pipe in patch (#15230) Co-authored-by: Claude Opus 4.7 --- packages/llmz/package.json | 2 +- packages/sdk/package.json | 2 +- packages/vai/package.json | 2 +- packages/zai/e2e/data/cache.jsonl | 2 + packages/zai/e2e/patch.test.ts | 70 +++++++++++++++++++++++++++++++ packages/zai/package.json | 4 +- packages/zai/src/micropatch.ts | 5 ++- pnpm-lock.yaml | 8 ++-- 8 files changed, 85 insertions(+), 10 deletions(-) diff --git a/packages/llmz/package.json b/packages/llmz/package.json index 20984102072..6b71225140a 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -74,7 +74,7 @@ "@botpress/client": "1.46.0", "@botpress/cognitive": "0.5.5", "@bpinternal/thicktoken": "^2.0.0", - "@bpinternal/zui": "^2.1.1" + "@bpinternal/zui": "^2.2.1" }, "dependenciesMeta": { "@bpinternal/zui": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 7e3221c4500..f937b32c8cd 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -31,7 +31,7 @@ "tsup": "^8.0.2" }, "peerDependencies": { - "@bpinternal/zui": "^2.1.1", + "@bpinternal/zui": "^2.2.1", "esbuild": "^0.16.12" }, "peerDependenciesMeta": { diff --git a/packages/vai/package.json b/packages/vai/package.json index 27f538081ce..c4166d0829f 100644 --- a/packages/vai/package.json +++ b/packages/vai/package.json @@ -42,7 +42,7 @@ "peerDependencies": { "@botpress/client": "1.46.0", "@bpinternal/thicktoken": "^1.0.1", - "@bpinternal/zui": "^2.1.1", + "@bpinternal/zui": "^2.2.1", "lodash": "^4.17.21", "vitest": "^2 || ^3 || ^4 || ^5" }, diff --git a/packages/zai/e2e/data/cache.jsonl b/packages/zai/e2e/data/cache.jsonl index 7cab2bc9e33..78c7514dded 100644 --- a/packages/zai/e2e/data/cache.jsonl +++ b/packages/zai/e2e/data/cache.jsonl @@ -2020,3 +2020,5 @@ {"key":"f4dd48a5","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n\\n\\n\\n◼︎<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `◼︎` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `◼︎NNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `◼︎>10|}`\\n\\n3. **Replace single line**: `◼︎=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ◼︎ or end)\\n - Example:\\n ```\\n ◼︎=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `◼︎=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `◼︎=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `◼︎-NNN`\\n - Deletes original line NNN\\n - Example: `◼︎-12`\\n\\n6. **Delete range**: `◼︎-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `◼︎-5-10`\\n\\n### Escaping\\n\\n- To include a literal `◼︎` in your text, use `\\\\◼︎`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `◼︎`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n◼︎=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change database host to \\\"db.prod.internal\\\", port to 5433, and add \\\"caching\\\" to features\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"localhost\\\",\\n004| \\\"port\\\": 5432,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\"\\n013| ]\\n014|}\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\n◼︎=3| \"host\": \"db.prod.internal\",\n◼︎=4| \"port\": 5433,\n◼︎=12| \"logging\",\n◼︎<13| \"caching\"\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1213,"outputTokens":959,"inputCost":0.00042455,"outputCost":0.00071925},"model":"cerebras:gpt-oss-120b","ttft":166,"latency":707,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.0011438,"requestId":"req-a782cd29-593b-4e8c-98df-58f2adf7da86"}}} {"key":"7ad8ac31","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n\\n\\n\\n◼︎<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `◼︎` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `◼︎NNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `◼︎>10|}`\\n\\n3. **Replace single line**: `◼︎=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ◼︎ or end)\\n - Example:\\n ```\\n ◼︎=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `◼︎=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `◼︎=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `◼︎-NNN`\\n - Deletes original line NNN\\n - Example: `◼︎-12`\\n\\n6. **Delete range**: `◼︎-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `◼︎-5-10`\\n\\n### Escaping\\n\\n- To include a literal `◼︎` in your text, use `\\\\◼︎`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `◼︎`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n◼︎=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change database host to \\\"db.prod.internal\\\", port to 5433 or higher, and add \\\"caching\\\" to features\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"localhost\\\",\\n004| \\\"port\\\": 5432,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\"\\n013| ]\\n014|}\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\n◼︎=003| \"host\": \"db.prod.internal\",\n◼︎=004| \"port\": 5433,\n◼︎=012| \"logging\",\n◼︎>012| \"caching\"\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1215,"outputTokens":933,"inputCost":0.00042525,"outputCost":0.00069975},"model":"cerebras:gpt-oss-120b","ttft":259,"latency":742,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.001125,"requestId":"req-8d671d94-4fc3-47af-b66e-f914fdf010a5"}}} {"key":"3b042987","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n\\n\\n\\n◼︎<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `◼︎` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `◼︎NNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `◼︎>10|}`\\n\\n3. **Replace single line**: `◼︎=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ◼︎ or end)\\n - Example:\\n ```\\n ◼︎=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `◼︎=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `◼︎=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `◼︎-NNN`\\n - Deletes original line NNN\\n - Example: `◼︎-12`\\n\\n6. **Delete range**: `◼︎-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `◼︎-5-10`\\n\\n### Escaping\\n\\n- To include a literal `◼︎` in your text, use `\\\\◼︎`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `◼︎`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n◼︎=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"The following JSON file is valid JSON but does not match the expected schema.\\n\\n\\n001|{\\n002| \\\"database\\\": {\\n003| \\\"host\\\": \\\"db.prod.internal\\\",\\n004| \\\"port\\\": 5433,\\n005| \\\"credentials\\\": {\\n006| \\\"username\\\": \\\"admin\\\",\\n007| \\\"password\\\": \\\"secret\\\"\\n008| }\\n009| },\\n010| \\\"features\\\": [\\n011| \\\"auth\\\",\\n012| \\\"logging\\\",\\n013| \\\"caching\\\"\\n014| ]\\n015|}\\n\\n\\nError parsing JSON:\\n\\n---JSON---\\n{\\n \\\"database\\\": {\\n \\\"host\\\": \\\"db.prod.internal\\\",\\n \\\"port\\\": 5433,\\n \\\"credentials\\\": {\\n \\\"username\\\": \\\"admin\\\",\\n \\\"password\\\": \\\"secret\\\"\\n }\\n },\\n \\\"features\\\": [\\n \\\"auth\\\",\\n \\\"logging\\\",\\n \\\"caching\\\"\\n ]\\n}\\n\\n---Validation Errors---\\n\\n1. Field: \\\"database.port\\\"\\n Problem: Number must be at least 5434\\n Message: Number must be greater than or equal to 5434\\n\\n\\nFix the JSON to match the schema. Only change what's needed to satisfy the validation errors above.\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\n◼︎=004| \"port\": 5434,\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1334,"outputTokens":209,"inputCost":0.0004669,"outputCost":0.00015675},"model":"cerebras:gpt-oss-120b","ttft":174,"latency":332,"cached":false,"fallbackPath":[],"stopReason":"stop","cost":0.00062365,"requestId":"req-006f8b68-7fad-42da-9e55-e9a74b88a7a2"}}} +{"key":"98ead616","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n\\n\\n\\n◼︎<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `◼︎` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `◼︎NNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `◼︎>10|}`\\n\\n3. **Replace single line**: `◼︎=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ◼︎ or end)\\n - Example:\\n ```\\n ◼︎=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `◼︎=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `◼︎=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `◼︎-NNN`\\n - Deletes original line NNN\\n - Example: `◼︎-12`\\n\\n6. **Delete range**: `◼︎-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `◼︎-5-10`\\n\\n### Escaping\\n\\n- To include a literal `◼︎` in your text, use `\\\\◼︎`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `◼︎`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n◼︎=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: add a new row to the table for \\\"Grape\\\" with color \\\"Purple\\\" and price \\\"2.00\\\"\\n\\n\\n001|# Fruits\\n002|\\n003|| Name | Color | Price |\\n004|| ------ | ------ | ----- |\\n005|| Apple | Red | 1.00 |\\n006|| Banana | Yellow | 0.50 |\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\n◼︎>006||| Grape | Purple | 2.00 |\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1180,"outputTokens":388,"inputCost":0.000413,"outputCost":0.000291},"model":"cerebras:gpt-oss-120b","ttft":71,"latency":0,"cached":true,"fallbackPath":[],"stopReason":"stop","cost":0.000704,"requestId":"req-a50d96a9-5579-40e4-b0da-f8531c3165db"}}} +{"key":"b99b1256","input":"{\"body\":{\"messages\":[{\"content\":\"You are a code patching assistant. Your task is to generate precise line-based patches using the Micropatch protocol.\\n\\n## Input Format\\n\\nYou will receive files in this XML format:\\n\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n\\n\\n\\n001|export function add(a, b) {\\n002| return a + b\\n003|}\\n\\n```\\n\\nEach file has:\\n- **path**: Full file path\\n- **name**: File name\\n- **Numbered lines**: Format is `NNN|content` where NNN is the ORIGINAL line number (1-based)\\n\\n## Output Format\\n\\nGenerate patches for EACH file that needs modification using this EXACT XML format:\\n\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n\\n\\n\\n◼︎<1|/**\\n * Adds two numbers\\n */\\n\\n```\\n\\n**CRITICAL RULES**:\\n1. Each `` tag MUST include the exact `path` attribute from the input\\n2. Put patch operations for EACH file inside its own `...` block\\n3. If a file doesn't need changes, omit its `` block entirely\\n4. DO NOT mix patches from different files\\n5. DO NOT include line numbers or any text outside the patch operations\\n\\n## Micropatch Protocol\\n\\nThe Micropatch protocol uses line numbers to reference ORIGINAL lines (before any edits).\\n\\n### Operations\\n\\nEach operation starts with the marker `◼︎` at the beginning of a line:\\n\\n1. **Insert BEFORE line**: `◼︎NNN|text`\\n - Inserts `text` as a new line AFTER original line NNN\\n - Example: `◼︎>10|}`\\n\\n3. **Replace single line**: `◼︎=NNN|new text`\\n - Replaces original line NNN with `new text`\\n - Can span multiple lines (continue until next ◼︎ or end)\\n - Example:\\n ```\\n ◼︎=7|function newName() {\\n return 42\\n }\\n ```\\n\\n4. **Replace range**: `◼︎=NNN-MMM|replacement`\\n - Replaces lines NNN through MMM with replacement text\\n - Example: `◼︎=5-8|const combined = a + b + c + d`\\n\\n5. **Delete single line**: `◼︎-NNN`\\n - Deletes original line NNN\\n - Example: `◼︎-12`\\n\\n6. **Delete range**: `◼︎-NNN-MMM`\\n - Deletes lines NNN through MMM inclusive\\n - Example: `◼︎-5-10`\\n\\n### Escaping\\n\\n- To include a literal `◼︎` in your text, use `\\\\◼︎`\\n- No other escape sequences are recognized\\n\\n### Important Rules\\n\\n1. **Use ORIGINAL line numbers**: Always reference the line numbers shown in the input (001, 002, etc.)\\n2. **One operation per line**: Each operation must start on a new line with `◼︎`\\n3. **No explanations**: Output ONLY patch operations inside `` tags\\n4. **Precise operations**: Use the minimal set of operations to achieve the goal\\n5. **Verify line numbers**: Double-check that line numbers match the input\\n\\n## Example\\n\\n**Input:**\\n```\\n\\n001|const x = 1\\n002|const y = 2\\n003|console.log(x + y)\\n004|\\n005|export { x, y }\\n\\n```\\n\\n**Task:** Change variable names from x,y to a,b\\n\\n**Output:**\\n```\\n\\n◼︎=1|const a = 1\\n◼︎=2|const b = 2\\n◼︎=3|console.log(a + b)\\n◼︎=5|export { a, b }\\n\\n```\\n\\n## Your Task\\n\\nGenerate ONLY the `` blocks with patch operations. Do not include explanations, comments, or any other text.\",\"role\":\"system\"},{\"content\":\"Instructions: change the price of Apple from 1.00 to 1.50\\n\\n\\n001|# Fruits\\n002|\\n003|| Name | Color | Price |\\n004|| ------ | ------ | ----- |\\n005|| Apple | Red | 1.00 |\\n006|| Banana | Yellow | 0.50 |\\n\\n\\nGenerate patches for each file that needs modification:\",\"role\":\"user\",\"type\":\"text\"}],\"meta\":{\"integrationName\":\"zai\",\"promptCategory\":\"zai:zai.patch\",\"promptSource\":\"zai:zai.patch:default\"},\"model\":\"fast\",\"signal\":{},\"stopSequences\":[]},\"method\":\"POST\",\"url\":\"https://api.botpress.cloud/v2/cognitive/generate-text\"}","value":{"output":"\n◼︎=005||| Apple | Red | 1.50 |\n","metadata":{"provider":"cerebras","usage":{"inputTokens":1172,"outputTokens":288,"inputCost":0.0004102,"outputCost":0.000216},"model":"cerebras:gpt-oss-120b","ttft":86,"latency":0,"cached":true,"fallbackPath":[],"stopReason":"stop","cost":0.0006262,"requestId":"req-43ade5ec-5264-464a-b24a-a4ac4a9e9ff2"}}} diff --git a/packages/zai/e2e/patch.test.ts b/packages/zai/e2e/patch.test.ts index d844ad7889f..9010c2a43fb 100644 --- a/packages/zai/e2e/patch.test.ts +++ b/packages/zai/e2e/patch.test.ts @@ -236,6 +236,76 @@ describe('zai.patch', { timeout: 60_000 }, () => { expect(result.output[0].patch).toMatchInlineSnapshot(`"◼︎>3|## Installation"`) }) + it('patches a markdown table by adding a row', async () => { + const tableFile: File = { + path: 'fruits.md', + name: 'fruits.md', + content: `# Fruits + +| Name | Color | Price | +| ------ | ------ | ----- | +| Apple | Red | 1.00 | +| Banana | Yellow | 0.50 |`, + } + + const result = await zai + .patch([tableFile], 'add a new row to the table for "Grape" with color "Purple" and price "2.00"') + .result() + + expect(result.output).toHaveLength(1) + const patched = result.output[0].content + + // Grape row exists and is well-formed (no doubled pipes like "||") + expect(patched).toContain('Grape') + expect(patched).toContain('Purple') + expect(patched).toContain('2.00') + expect(patched).not.toMatch(/\|\|/) + + // Existing rows are preserved + expect(patched).toContain('| Apple | Red | 1.00 |') + expect(patched).toContain('| Banana | Yellow | 0.50 |') + + // Every table row starts with a single pipe (not a double pipe) + const tableRows = patched.split('\n').filter((l) => l.trim().startsWith('|')) + for (const row of tableRows) { + expect(row.trimStart().startsWith('||')).toBe(false) + } + }) + + it('patches a markdown table by changing an existing row', async () => { + const tableFile: File = { + path: 'fruits.md', + name: 'fruits.md', + content: `# Fruits + +| Name | Color | Price | +| ------ | ------ | ----- | +| Apple | Red | 1.00 | +| Banana | Yellow | 0.50 |`, + } + + const result = await zai.patch([tableFile], 'change the price of Apple from 1.00 to 1.50').result() + + expect(result.output).toHaveLength(1) + const patched = result.output[0].content + + expect(patched).toContain('1.50') + expect(patched).not.toContain('| Apple | Red | 1.00 |') + + // No doubled pipes — guards against a bug where lines starting with `|` + // get an extra leading pipe added by the patch protocol. + expect(patched).not.toMatch(/\|\|/) + + // Banana row is untouched + expect(patched).toContain('| Banana | Yellow | 0.50 |') + + // Every table row starts with a single pipe + const tableRows = patched.split('\n').filter((l) => l.trim().startsWith('|')) + for (const row of tableRows) { + expect(row.trimStart().startsWith('||')).toBe(false) + } + }) + it('can add multiple sections to different files', async () => { const file1: File = { path: 'file1.txt', name: 'file1.txt', content: 'Line 1\nLine 2' } const file2: File = { path: 'file2.txt', name: 'file2.txt', content: 'Content A\nContent B' } diff --git a/packages/zai/package.json b/packages/zai/package.json index 8ad8144288c..160fc166187 100644 --- a/packages/zai/package.json +++ b/packages/zai/package.json @@ -1,7 +1,7 @@ { "name": "@botpress/zai", "description": "Zui AI (zai) – An LLM utility library written on top of Zui and the Botpress API", - "version": "2.6.21", + "version": "2.6.22", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { @@ -56,7 +56,7 @@ }, "peerDependencies": { "@bpinternal/thicktoken": "^1.0.0", - "@bpinternal/zui": "^2.1.1" + "@bpinternal/zui": "^2.2.1" }, "engines": { "node": ">=18.0.0" diff --git a/packages/zai/src/micropatch.ts b/packages/zai/src/micropatch.ts index f4f5e2d1ad7..f937fda0c70 100644 --- a/packages/zai/src/micropatch.ts +++ b/packages/zai/src/micropatch.ts @@ -153,7 +153,10 @@ export class Micropatch { const op = m[1] as '<' | '>' | '=' | '-' const aNum = parseInt(m[2], 10) const bNum = m[3] ? parseInt(m[3], 10) : undefined - const firstPayload = m[4] ?? '' + // LLMs sometimes "double" the leading `|` of a payload that itself begins with `|` + // (e.g. markdown table rows), as if escaping it. The protocol has no such escape, + // so a payload starting with `||` is treated as a single literal leading `|`. + const firstPayload = (m[4] ?? '').replace(/^\|\|/, '|') if (aNum < 1 || (bNum !== undefined && bNum < aNum)) { throw new Error(`Invalid line/range at line ${i + 1}: ${line}`) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 682b93ddc1d..0ce5146db94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3139,7 +3139,7 @@ importers: specifier: ^2.0.0 version: 2.0.0 '@bpinternal/zui': - specifier: ^2.1.1 + specifier: ^2.2.1 version: link:../zui '@jitl/quickjs-singlefile-browser-release-sync': specifier: ^0.31.0 @@ -3239,7 +3239,7 @@ importers: specifier: 1.46.0 version: link:../client '@bpinternal/zui': - specifier: ^2.1.1 + specifier: ^2.2.1 version: link:../zui browser-or-node: specifier: ^2.1.1 @@ -3279,7 +3279,7 @@ importers: specifier: ^1.0.1 version: 1.0.2 '@bpinternal/zui': - specifier: ^2.1.1 + specifier: ^2.2.1 version: link:../zui json5: specifier: ^2.2.3 @@ -3325,7 +3325,7 @@ importers: specifier: ^1.0.0 version: 1.0.2 '@bpinternal/zui': - specifier: ^2.1.1 + specifier: ^2.2.1 version: link:../zui json5: specifier: ^2.2.3 From a6ef4a59c26681f97207b821a71750747a0fc9a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Levasseur?= Date: Fri, 29 May 2026 10:36:20 -0400 Subject: [PATCH 2/8] chore: add orca as codeowner of llmz zai vai (#15231) --- CODEOWNERS | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index ccd516325f2..c8df29deb03 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -17,9 +17,9 @@ ## AI /packages/cognitive @botpress/dragon -/packages/llmz @slvnperron @botpress/swordfish -/packages/vai @slvnperron @botpress/swordfish -/packages/zai @slvnperron @botpress/swordfish +/packages/llmz @slvnperron @botpress/orca @botpress/swordfish +/packages/vai @slvnperron @botpress/orca @botpress/swordfish +/packages/zai @slvnperron @botpress/orca @botpress/swordfish # Bots /bots @botpress/shell From 064cbb7967edf1bf24984b1f0417ed3410ac3748 Mon Sep 17 00:00:00 2001 From: Augusto Mota Pinheiro Date: Fri, 29 May 2026 10:43:25 -0400 Subject: [PATCH 3/8] feat(llmz): support yielding chat components from tool handlers (#15227) Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/deploy-chat-production.yml | 4 +- .github/workflows/deploy-chat-staging.yml | 4 +- .../21_chat_tool_components/README.md | 38 ++++ .../examples/21_chat_tool_components/demo.svg | 1 + .../examples/21_chat_tool_components/index.ts | 169 ++++++++++++++++++ packages/llmz/examples/record-demo.sh | 2 +- packages/llmz/package.json | 2 +- packages/llmz/src/component.ts | 16 +- packages/llmz/src/llmz.ts | 24 ++- packages/llmz/src/tool.ts | 57 +++++- 10 files changed, 293 insertions(+), 24 deletions(-) create mode 100644 packages/llmz/examples/21_chat_tool_components/README.md create mode 100644 packages/llmz/examples/21_chat_tool_components/demo.svg create mode 100644 packages/llmz/examples/21_chat_tool_components/index.ts diff --git a/.github/workflows/deploy-chat-production.yml b/.github/workflows/deploy-chat-production.yml index 74279467845..0b109b9acce 100644 --- a/.github/workflows/deploy-chat-production.yml +++ b/.github/workflows/deploy-chat-production.yml @@ -65,11 +65,11 @@ jobs: needs: [deploy-chat-production, deploy-chat-lambda] runs-on: ubuntu-latest steps: - - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }} + - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}" ping-failure: needs: [deploy-chat-production, deploy-chat-lambda] if: ${{ failure() }} runs-on: ubuntu-latest steps: - - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}/fail + - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_PRODUCTION_PING_URL }}/fail" diff --git a/.github/workflows/deploy-chat-staging.yml b/.github/workflows/deploy-chat-staging.yml index 8bbd4889ac4..279c5ccc095 100644 --- a/.github/workflows/deploy-chat-staging.yml +++ b/.github/workflows/deploy-chat-staging.yml @@ -69,11 +69,11 @@ jobs: needs: [deploy-chat-staging, deploy-chat-lambda] runs-on: ubuntu-latest steps: - - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }} + - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}" ping-failure: needs: [deploy-chat-staging, deploy-chat-lambda] if: ${{ failure() }} runs-on: ubuntu-latest steps: - - run: curl -m 10 --retry 5 ${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}/fail + - run: curl -m 10 --retry 5 "${{ secrets.CHAT_DEPLOY_STAGING_PING_URL }}/fail" diff --git a/packages/llmz/examples/21_chat_tool_components/README.md b/packages/llmz/examples/21_chat_tool_components/README.md new file mode 100644 index 00000000000..113c9c99cd2 --- /dev/null +++ b/packages/llmz/examples/21_chat_tool_components/README.md @@ -0,0 +1,38 @@ +## Yielding Components from Tool Handlers + +This demo shows how tool handlers can directly yield UI components back to the chat using **async generator functions** (`async function*`). Unlike example 10 where the LLM yields components after calling a tool, here the tool handler itself controls what to display and when. + +The `purchase_ticket` tool handler is an async generator that yields progress updates and the final ticket — the LLM doesn't need to know about components at all. + +### Handler pattern + +```tsx +async *handler({ from, date, to }) { + // Yield progress components step by step + yield ProgressComponent.render({ message: 'Checking flight availability...', step: 1, total: 3 }) + yield ProgressComponent.render({ message: 'Calculating best price...', step: 2, total: 3 }) + + // Yield the final ticket component + yield PlaneTicketComponent.render({ + from, to, date, + price: 299.99, + ticketNumber: 'TICKET-345633', + }) + + // Return the tool result for the LLM + return { price: 299.99, ticketNumber: 'TICKET-345633', confirmation: '...' } +} +``` + +### Contrast with example 10 + +| | Example 10 | Example 21 | +| ------------------------------ | ----------------------------------- | ------------------------------- | +| Who yields components | LLM-generated JSX code | Tool handler itself | +| Tool handler type | `async (input) => output` | `async function* (input)` | +| LLM needs to know components | Yes — included as component aliases | No — tool handles it internally | +| Progress updates mid-execution | Not possible | `yield` between steps | + +## 🎥 Demo + +![Demo](./demo.svg) diff --git a/packages/llmz/examples/21_chat_tool_components/demo.svg b/packages/llmz/examples/21_chat_tool_components/demo.svg new file mode 100644 index 00000000000..c23e34aefdf --- /dev/null +++ b/packages/llmz/examples/21_chat_tool_components/demo.svg @@ -0,0 +1 @@ +🚀Launchingexample:21_chat_tool_components[█░░]Checkingavailability...[█░░]Checkingflightavailability...[██░]Calculatingbestprice...╔═══════════════════════════════════════════════════════════╗✈️FLIGHTTICKET╠═══════════════════════════════════════════════════════════╣TicketNumber:TICKET-345633From:NewYorkTo:LosAngelesDate:2025-10-01Price:$299.99Price:$299.99Haveasafeflight!🛫╚═══════════════════════════════════════════════════════════╝[██░]Finalizingpurchase...[███]Ticketissued.🤖Agent:**Booked!**Yourflightfrom**NewYork**to**LosAngeles**on**2025-10-01**isconfirmed.🤖Agent:Wouldyouliketo**buyanotherticket**ormakechanges(differentdate/cities)?</Message>constresult=awaitpurchase_ticket({from:"NewYork",to:"LosAngeles",date:"2025-10-01"})yield<Message><Progressmessage="Finalizingpurchase..."step={2}total={3}/><Progressmessage="Ticketissued."step={3}total={3}/>yield<Messagetype="success">**Booked!**Yourflightfrom**NewYork**to**LosAngeles**on**2025-10-01**isconfirmed.<PlaneTicketticketNumber={result.ticketNumber}from="NewYork"to="LosAngeles"date="2025-10-01"price={result.price}/>Wouldyouliketo**buyanotherticket**ormakechanges(differentdate/cities)?<Buttonaction="say"label="Buyanotherticket"/><Buttonaction="say"label="Changedate"/>Here'sthecodegeneratedbytheLLMz:return{action:"lreturn{action:"listen"} \ No newline at end of file diff --git a/packages/llmz/examples/21_chat_tool_components/index.ts b/packages/llmz/examples/21_chat_tool_components/index.ts new file mode 100644 index 00000000000..e0031d3122c --- /dev/null +++ b/packages/llmz/examples/21_chat_tool_components/index.ts @@ -0,0 +1,169 @@ +/** + * Example 21: Yielding Components from Tool Handlers + * + * This example demonstrates how tool handlers can directly yield UI components + * back to the chat interface using async generator functions. Unlike example 10 + * where the LLM yielids components, here the tool handler itself controls what + * to display and when. + * + * Key concepts: + * - Tool handlers as async generators (async function*) + * - Yielding components mid-execution for progress updates + * - Component.render() for constructing RenderedComponent objects + * - Tool-side component rendering vs LLM-side component yielding + * + * Contrast with example 10: the LLM no longer needs to know about components — + * the tool handler takes care of rendering directly. + */ + +import { Client } from '@botpress/client' +import { z } from '@bpinternal/zui' + +import chalk from 'chalk' +import { Component, execute, Tool } from 'llmz' +import { box } from '../utils/box' +import { CLIChat } from '../utils/cli-chat' + +// Initialize Botpress client +const client = new Client({ + botId: process.env.BOTPRESS_BOT_ID!, + token: process.env.BOTPRESS_TOKEN!, +}) + +// Define a custom UI component for displaying plane tickets +const PlaneTicketComponent = new Component({ + name: 'PlaneTicket', + description: 'A component to display a plane ticket', + type: 'leaf', + leaf: { + props: z.object({ + ticketNumber: z.string().describe('The unique ticket number for the plane ticket'), + from: z.string().describe('The departure city'), + to: z.string().describe('The destination city'), + date: z.string().describe('The date of the flight (in YYYY-MM-DD format)'), + price: z.number().optional().describe('The price of the ticket'), + }), + }, + examples: [ + { + name: 'PlaneTicket', + description: 'A simple plane ticket example', + code: '', + }, + ], +}) + +// Define a progress component for status updates +const ProgressComponent = new Component({ + name: 'Progress', + description: 'Displays a progress update message', + type: 'leaf', + leaf: { + props: z.object({ + message: z.string().describe('The progress message'), + step: z.number().describe('Current step number'), + total: z.number().describe('Total number of steps'), + }), + }, + examples: [ + { + name: 'Basic Progress', + description: 'Show a progress bar with step and total', + code: '', + }, + { + name: 'Mid-progress', + description: 'Midway progress update', + code: '', + }, + ], +}) + +// Tool for purchasing tickets — uses async generator to yield components +const purchaseTicket = new Tool({ + name: 'purchase_ticket', + description: 'Purchase a plane ticket, showing progress updates along the way', + input: z.object({ + from: z.string().describe('The departure city'), + to: z.string().describe('The destination city'), + date: z.string().describe('The date of the flight (in YYYY-MM-DD format)'), + }), + output: z.object({ + price: z.number().describe('The price of the purchased ticket in USD'), + ticketNumber: z.string().describe('The unique ticket number for the purchased ticket'), + confirmation: z.string().describe('Confirmation message for the ticket purchase'), + }), + // Async generator handler: yields components progress, returns final result + async *handler({ from, date, to }) { + // Step 1: Show progress + yield ProgressComponent.render({ message: 'Checking flight availability...', step: 1, total: 3 }) + await new Promise((resolve) => setTimeout(resolve, 500)) + + // Step 2: Show more progress + yield ProgressComponent.render({ message: 'Calculating best price...', step: 2, total: 3 }) + await new Promise((resolve) => setTimeout(resolve, 500)) + + // Step 3: Yiels the ticket component itself + yield PlaneTicketComponent.render({ + from, + to, + date, + price: 299.99, + ticketNumber: 'TICKET-345633', + }) + + // Final return — the tool result for the LLM + return { + price: 299.99, + ticketNumber: 'TICKET-345633', + confirmation: `Ticket from ${from} to ${to} on ${date} purchased successfully!`, + } + }, +}) + +const chat = new CLIChat() + +chat.transcript.push({ + role: 'user', + content: 'I want to purchase a plane ticket from New York to Los Angeles on 2025-10-01.', +}) + +// Register component renderers — same as example 10 +chat.registerComponent(PlaneTicketComponent, async (message) => { + const { ticketNumber, from, to, date, price } = message.props + + const ticket = box([ + chalk.white.bold(' ✈️ FLIGHT TICKET'), + `${chalk.yellow.bold('Ticket Number:')} ${chalk.white(ticketNumber)}`, + '', + `${chalk.green.bold('From:')} ${chalk.white(from)}`, + `${chalk.red.bold('To:')} ${chalk.white(to)}`, + '', + `${chalk.magenta.bold('Date:')} ${chalk.white(date)}`, + `${chalk.cyan.bold('Price:')} ${chalk.white(`$${price?.toFixed(2) || 'N/A'}`)}`, + '', + chalk.gray(' Have a safe flight! 🛫'), + ]) + + console.log(ticket) +}) + +chat.registerComponent(ProgressComponent, async (message) => { + const { step, total, message: msg } = message.props + const bar = '█'.repeat(step) + '░'.repeat(Math.max(0, total - step)) + console.log(chalk.blue(`[${bar}]`) + ' ' + chalk.white(msg)) +}) + +// Execute the travel agent workflow +// Note: the LLM no longer needs to know about PlaneTicketComponent — +// the tool handler handles rendering directly +const result = await execute({ + instructions: + 'You are a travel agent. Help the user purchase a plane ticket. The ticket will be displayed automatically when the purchase completes.', + tools: [purchaseTicket], + chat, + client, +}) + +console.log("Here's the code generated by the LLMz:") +console.log(result.iteration?.code) diff --git a/packages/llmz/examples/record-demo.sh b/packages/llmz/examples/record-demo.sh index 540eedb17ed..2963e5292dc 100755 --- a/packages/llmz/examples/record-demo.sh +++ b/packages/llmz/examples/record-demo.sh @@ -12,7 +12,7 @@ CAST_PATH="${FOLDER}/demo.cast" SVG_PATH="${FOLDER}/demo.svg" echo "🎬 Recording demo for ${FOLDER}..." -asciinema rec "$CAST_PATH" --command "pnpm start ${FOLDER}" --overwrite +asciinema rec "$CAST_PATH" --command "pnpm start ${FOLDER}" --overwrite -f asciicast-v2 echo "🛠 Appending trailing empty frame..." node -e " diff --git a/packages/llmz/package.json b/packages/llmz/package.json index 6b71225140a..c4fc32ef104 100644 --- a/packages/llmz/package.json +++ b/packages/llmz/package.json @@ -2,7 +2,7 @@ "name": "llmz", "type": "module", "description": "LLMz - An LLM-native Typescript VM built on top of Zui", - "version": "0.0.78", + "version": "0.0.79", "types": "./dist/index.d.ts", "main": "./dist/index.cjs", "module": "./dist/index.js", diff --git a/packages/llmz/src/component.ts b/packages/llmz/src/component.ts index d2bd2e04ea7..732d38b7bae 100644 --- a/packages/llmz/src/component.ts +++ b/packages/llmz/src/component.ts @@ -1,5 +1,5 @@ import { z } from '@bpinternal/zui' -import { isAnyJsxComponent, isJsxComponent } from './jsx.js' +import { createJsxComponent, isAnyJsxComponent, isJsxComponent, JsxComponent } from './jsx.js' export type ExampleUsage = { name: string @@ -206,12 +206,7 @@ type ExtractComponentProps = : never // Rendered component type -export type RenderedComponent> = { - __jsx: true - type: string - children: any[] - props: TProps -} +export type RenderedComponent = JsxComponent // Component Class that infers props from component definition export class Component { @@ -222,6 +217,13 @@ export class Component { assertValidComponent(definition) this.definition = definition } + + public render( + props: Component['propsType'], + children: Array = [] + ): RenderedComponent { + return createJsxComponent({ type: this.definition.name, props, children }) + } } // Type guard function that infers props from component diff --git a/packages/llmz/src/llmz.ts b/packages/llmz/src/llmz.ts index 032fd2b7e92..bf40c91d78c 100644 --- a/packages/llmz/src/llmz.ts +++ b/packages/llmz/src/llmz.ts @@ -590,6 +590,7 @@ const executeIteration = async ({ for (const tool of obj.tools ?? []) { instance[tool.name] = wrapTool({ + chat: ctx.chat, tool, traces, object: obj.name, @@ -607,7 +608,15 @@ const executeIteration = async ({ } for (const tool of iteration.tools) { - const wrapped = wrapTool({ tool, traces, iteration, beforeHook: onBeforeTool, afterHook: onAfterTool, controller }) + const wrapped = wrapTool({ + chat: ctx.chat, + tool, + traces, + iteration, + beforeHook: onBeforeTool, + afterHook: onAfterTool, + controller, + }) for (const key of [tool.name, ...(tool.aliases ?? [])]) { vmContext[key] = wrapped } @@ -784,6 +793,7 @@ const executeIteration = async ({ } type Props = { + chat?: Chat tool: Tool object?: string traces: Trace[] @@ -793,7 +803,7 @@ type Props = { controller: AbortController } -function wrapTool({ tool, traces, object, iteration, beforeHook, afterHook, controller }: Props) { +function wrapTool({ chat, tool, traces, object, iteration, beforeHook, afterHook, controller }: Props) { const getToolInput = (input: any) => (tool.zInput as any).safeParse(input).data ?? input return function (input: any) { @@ -865,9 +875,13 @@ function wrapTool({ tool, traces, object, iteration, beforeHook, afterHook, cont input = beforeRes.input } - let output = await tool.execute(input, { - callId: toolCallId, - }) + let output = await tool.execute( + input, + { + callId: toolCallId, + }, + chat + ) const afterRes = await afterHook?.({ iteration, diff --git a/packages/llmz/src/tool.ts b/packages/llmz/src/tool.ts index ee100d0b0a8..2057e423fd2 100644 --- a/packages/llmz/src/tool.ts +++ b/packages/llmz/src/tool.ts @@ -1,6 +1,8 @@ import { TypeOf, z, transforms, ZodObject, ZodType } from '@bpinternal/zui' import { JSONSchema7 } from 'json-schema' import { isEmpty, uniq } from 'lodash-es' +import { Chat } from './chat.js' +import { RenderedComponent } from './component.js' import { Serializable } from './types.js' import { getTypings as generateTypings } from './typings.js' import { convertObjectToZuiLiterals, isJsonSchema, isValidIdentifier, isZuiSchema } from './utils.js' @@ -438,7 +440,10 @@ export class Tool IX) output: OX | ((original: O | undefined) => OX) staticInputValues?: SmartPartial> - handler: (args: TypeOf, ctx: ToolCallContext) => Promise> + handler: ( + args: TypeOf, + ctx: ToolCallContext + ) => AsyncGenerator> | Promise> retry: ToolRetryFn> }> = {} ): Tool { @@ -478,7 +483,10 @@ export class Tool, ctx: ToolCallContext) => Promise>, + handler: (props.handler ?? this._handler) as ( + args: TypeOf, + ctx: ToolCallContext + ) => AsyncGenerator, void> | Promise>, retry: props.retry ?? this.retry, }).setStaticInputValues((props.staticInputValues as any) ?? (this._staticInputValues as any)) } catch (e) { @@ -486,7 +494,10 @@ export class Tool Promise + private _handler: ( + args: unknown, + ctx: ToolCallContext + ) => AsyncGenerator | Promise /** * Creates a new Tool instance. @@ -572,7 +583,10 @@ export class Tool> - handler: (args: TypeOf, ctx: ToolCallContext) => Promise> + handler: ( + args: TypeOf, + ctx: ToolCallContext + ) => AsyncGenerator> | Promise> retry?: ToolRetryFn> }) { if (!isValidIdentifier(props.name)) { @@ -650,6 +664,34 @@ export class Tool void + ): Promise { + const handler = this._handler(input, ctx) + const isGen = typeof handler === 'object' && handler !== null && 'next' in handler + + if (!isGen) { + return handler + } + + let yieldIndex = 0 + while (true) { + const { value, done } = await handler.next() + if (done) { + return value + } + if (yieldIndex >= yieldedCount) { + setYieldedCount(yieldIndex + 1) + await chat?.handler?.(value) + } + yieldIndex++ + } + } + /** * Executes the tool with the given input and context. * @@ -674,7 +716,7 @@ export class Tool, ctx: ToolCallContext): Promise> { + public async execute(rawInput: TypeOf, ctx: ToolCallContext, chat?: Chat): Promise> { const isZodObject = (this.zInput as any)._def.typeName === 'ZodObject' const input = isZodObject ? (rawInput ?? {}) : rawInput @@ -685,10 +727,13 @@ export class Tool { + yieldedCount = n + }) const pOutput = (this.zOutput as any).safeParse(result) return pOutput.success ? pOutput.data : result } catch (err) { From e01dcef0103c5fa6317c25524486ac359d6a1726 Mon Sep 17 00:00:00 2001 From: DubeSebastien90 <100394271+DubeSebastien90@users.noreply.github.com> Date: Fri, 29 May 2026 10:51:55 -0400 Subject: [PATCH 4/8] feat(zui): Path propagation for zui to typescript schema / type (#15223) --- packages/zui/src/transforms/common/errors.ts | 32 +-- .../zui-to-json-schema/index.test.ts | 123 ++++++++- .../transforms/zui-to-json-schema/index.ts | 3 +- .../zui-to-typescript-schema/index.test.ts | 235 ++++++++++++++++++ .../zui-to-typescript-schema/index.ts | 80 +++--- .../zui-to-typescript-type/index.test.ts | 182 ++++++++++++++ .../zui-to-typescript-type/index.ts | 99 +++++--- .../zui/src/utils/property-path-utils.test.ts | 90 +++---- packages/zui/src/utils/property-path-utils.ts | 31 +-- 9 files changed, 720 insertions(+), 155 deletions(-) diff --git a/packages/zui/src/transforms/common/errors.ts b/packages/zui/src/transforms/common/errors.ts index 4306dab172b..3551001e3db 100644 --- a/packages/zui/src/transforms/common/errors.ts +++ b/packages/zui/src/transforms/common/errors.ts @@ -21,33 +21,33 @@ export abstract class ZuiTransformError extends Error { // json-schema-to-zui-error export class JSONSchemaToZuiError extends ZuiTransformError { - public constructor(message?: string, path?: string) { - super('json-schema-to-zui', message, path) + public constructor(message?: string) { + super('json-schema-to-zui', message) } } export class UnsupportedJSONSchemaToZuiError extends JSONSchemaToZuiError { - public constructor(schema: JSONSchema7, path?: string) { - super(`JSON Schema ${JSON.stringify(schema)} cannot be transformed to ZUI type.`, path) + public constructor(schema: JSONSchema7) { + super(`JSON Schema ${JSON.stringify(schema)} cannot be transformed to ZUI type.`) } } // object-to-zui-error export class ObjectToZuiError extends ZuiTransformError { - public constructor(message?: string, path?: string) { - super('object-to-zui', message, path) + public constructor(message?: string) { + super('object-to-zui', message) } } // zui-to-json-schema-error export class ZuiToJSONSchemaError extends ZuiTransformError { - public constructor(message?: string, path?: string) { + public constructor(message: string, path: string) { super('zui-to-json-schema', message, path) } } export class UnsupportedZuiToJSONSchemaError extends ZuiToJSONSchemaError { public constructor( type: ZodNativeTypeName, - path?: string, + path: string, { suggestedAlternative }: { suggestedAlternative?: string } = {} ) { const msg = suggestedAlternative @@ -57,19 +57,19 @@ export class UnsupportedZuiToJSONSchemaError extends ZuiToJSONSchemaError { } } export class UnsupportedZuiCheckToJSONSchemaError extends ZuiToJSONSchemaError { - public constructor(zodType: ZodNativeTypeName, checkKind: string, path?: string) { + public constructor(zodType: ZodNativeTypeName, checkKind: string, path: string) { super(`Zod check .${checkKind}() of type ${zodType} cannot be transformed to JSON Schema.`, path) } } // zui-to-typescript-schema-error export class ZuiToTypescriptSchemaError extends ZuiTransformError { - public constructor(message?: string, path?: string) { + public constructor(message: string, path: string) { super('zui-to-typescript-schema', message, path) } } export class UnsupportedZuiToTypescriptSchemaError extends ZuiToTypescriptSchemaError { - public constructor(type: ZodNativeTypeName, path?: string) { + public constructor(type: ZodNativeTypeName, path: string) { super(`Zod type ${type} cannot be transformed to TypeScript schema.`, path) } } @@ -81,17 +81,17 @@ export class ZuiToTypescriptTypeError extends ZuiTransformError { } } export class UnsupportedZuiToTypescriptTypeError extends ZuiToTypescriptTypeError { - public constructor(type: ZodNativeTypeName, path?: string) { + public constructor(type: ZodNativeTypeName, path: string) { super(`Zod type ${type} cannot be transformed to TypeScript type.`, path) } } export class UntitledDeclarationError extends ZuiToTypescriptTypeError { - public constructor(path?: string) { - super('Schema must have a title to be transformed to a TypeScript type with a declaration.', path) + public constructor() { + super('Schema must have a title to be transformed to a TypeScript type with a declaration.') } } export class UnrepresentableGenericError extends ZuiToTypescriptTypeError { - public constructor(path?: string) { - super('ZodRef can only be transformed to a TypeScript type with a "type" declaration.', path) + public constructor() { + super('ZodRef can only be transformed to a TypeScript type with a "type" declaration.') } } diff --git a/packages/zui/src/transforms/zui-to-json-schema/index.test.ts b/packages/zui/src/transforms/zui-to-json-schema/index.test.ts index dab73a9497c..0105f4d63ba 100644 --- a/packages/zui/src/transforms/zui-to-json-schema/index.test.ts +++ b/packages/zui/src/transforms/zui-to-json-schema/index.test.ts @@ -415,22 +415,133 @@ describe('zuiToJSONSchemaNext', () => { expect(schema).toEqual({ $ref: 'foo' }) }) - test('should show complete path section in error message', () => { + test('should add object keys to path', () => { try { - toJSONSchema(z.object({ foo: z.object({ bar: z.tuple([z.number(), z.void()]) }) })) + toJSONSchema(z.object({ foo: z.object({ bar: z.void() }) })) expect.fail('should have thrown') } catch (e) { - expect(e instanceof Error && e.message).toContain('#.foo.bar[1]') + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#.foo.bar') + } + }) + + test('should add [number] section to array types', () => { + try { + toJSONSchema(z.array(z.void())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [index] section to tuple types', () => { + try { + toJSONSchema(z.tuple([z.number(), z.void()])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[1]') + } + }) + + test('should add [number] section to tuple rest', () => { + try { + toJSONSchema(z.tuple([z.number(), z.string()]).rest(z.string().refine((v) => v.length > 0))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [number] section to set types', () => { + try { + toJSONSchema(z.set(z.void())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [string] section to record types with string key', () => { + try { + toJSONSchema(z.record(z.string(), z.void())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[string]') + } + }) + + test('should add [number] section to record types with number key', () => { + try { + toJSONSchema(z.record(z.number(), z.void())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [*] section to record types with any key', () => { + try { + toJSONSchema(z.record(z.boolean(), z.void())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[*]') + } + }) + + test('should add [index] section to union types', () => { + try { + toJSONSchema(z.union([z.boolean(), z.void()])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[1]') + } + }) + + test('should add [index] section to discriminated union types', () => { + try { + toJSONSchema( + z.discriminatedUnion('type', [ + z.object({ type: z.literal('a'), foo: z.string() }), + z.object({ type: z.literal('b'), bar: z.string().refine((v) => v.length > 0) }), + ]) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[1].bar') + } + }) + + test('should add [index] section to intersection types', () => { + try { + toJSONSchema( + z.intersection( + z.string().refine((v) => v.length > 0), + z.string() + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errs.ZuiTransformError) + expect((e as errs.ZuiTransformError).path).toBe('#[0]') } }) - test('should expose path as a property on the error', () => { + test('should add [string] section to additional properties', () => { try { - toJSONSchema(z.object({ foo: z.object({ bar: z.tuple([z.number(), z.void()]) }) })) + toJSONSchema(z.object({}).catchall(z.void())) expect.fail('should have thrown') } catch (e) { expect(e).toBeInstanceOf(errs.ZuiTransformError) - expect((e as errs.ZuiTransformError).path).toBe('#.foo.bar[1]') + expect((e as errs.ZuiTransformError).path).toBe('#[string]') } }) }) diff --git a/packages/zui/src/transforms/zui-to-json-schema/index.ts b/packages/zui/src/transforms/zui-to-json-schema/index.ts index 9d266b660b5..d656632292f 100644 --- a/packages/zui/src/transforms/zui-to-json-schema/index.ts +++ b/packages/zui/src/transforms/zui-to-json-schema/index.ts @@ -127,7 +127,8 @@ function _toJSONSchema( const properties = shape .map(([key, value]) => [key, value.mandatory()] satisfies [string, z.ZodType]) .map( - ([key, value]) => [key, _toJSONSchema(value, opts, path.appendSection(key))] satisfies [string, json.Schema] + ([key, value]) => + [key, _toJSONSchema(value, opts, path.withIndexType('key', key))] satisfies [string, json.Schema] ) return { diff --git a/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts b/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts index fce50835cb6..ef8cd1d8b14 100644 --- a/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts +++ b/packages/zui/src/transforms/zui-to-typescript-schema/index.test.ts @@ -692,4 +692,239 @@ describe.concurrent('toTypescriptSchema', () => { expect(evaluated.getMetadata().patate).toBe('pilée') }) }) + + test('should add object keys to path', () => { + try { + toTypescript(z.object({ foo: z.object({ bar: z.string().refine((v) => v.length > 0) }) })) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#.foo.bar') + } + }) + + test('should add [number] section to array types', () => { + try { + toTypescript(z.array(z.string().refine((v) => v.length > 0))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [index] section to tuple types', () => { + try { + toTypescript(z.tuple([z.number(), z.string().refine((v) => v.length > 0)])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1]') + } + }) + + test('should add [number] section to set types', () => { + try { + toTypescript(z.set(z.string().refine((v) => v.length > 0))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add keyOf wrapper to record types with an invalid key', () => { + try { + toTypescript( + z.record( + z.string().refine((v) => v.length > 0), + z.string() + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>') + } + }) + + test('should add [string] section to record types with string key', () => { + try { + toTypescript( + z.record( + z.string(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[string]') + } + }) + + test('should add [number] section to record types with number key', () => { + try { + toTypescript( + z.record( + z.number(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [*] section to record types with any key', () => { + try { + toTypescript( + z.record( + z.boolean(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[*]') + } + }) + + test('should add keyOf wrapper to map types with an invalid key', () => { + try { + toTypescript( + z.map( + z.string().refine((v) => v.length > 0), + z.string() + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>') + } + }) + + test('should add [string] section to map types with string key', () => { + try { + toTypescript( + z.map( + z.string(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[string]') + } + }) + + test('should add [number] section to map types with number key', () => { + try { + toTypescript( + z.map( + z.number(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + test('should add [*] section to map types with any key', () => { + try { + toTypescript( + z.map( + z.boolean(), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[*]') + } + }) + + test('should add [index] section to union types', () => { + try { + toTypescript(z.union([z.boolean(), z.string().refine((v) => v.length > 0)])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1]') + } + }) + + test('should add [index] section to discriminated union types', () => { + try { + toTypescript( + z.discriminatedUnion('type', [ + z.object({ type: z.literal('a'), foo: z.string() }), + z.object({ type: z.literal('b'), bar: z.number().refine((v) => v > 0) }), + ]) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1].bar') + } + }) + + test('should add [index] section to intersection types', () => { + try { + toTypescript( + z.intersection( + z.string().refine((v) => v.length > 0), + z.number() + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[0]') + } + }) + + test('should add parameters wrapper to function type and show index', () => { + try { + toTypescript(z.function(z.tuple([z.boolean(), z.string().refine((v) => v.length > 0)]), z.string())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('Parameters<#>[1]') + } + }) + + test('should add returnType wrapper to function return type invalid', () => { + try { + toTypescript( + z.function( + z.tuple([z.boolean(), z.number()]), + z.string().refine((v) => v.length > 0) + ) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('ReturnType<#>') + } + }) + + test('should add [string] section to additional properties', () => { + try { + toTypescript(z.object({}).catchall(z.string().refine((v) => v.length > 0))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[string]') + } + }) }) diff --git a/packages/zui/src/transforms/zui-to-typescript-schema/index.ts b/packages/zui/src/transforms/zui-to-typescript-schema/index.ts index 446e5cd1b96..32149ba67e5 100644 --- a/packages/zui/src/transforms/zui-to-typescript-schema/index.ts +++ b/packages/zui/src/transforms/zui-to-typescript-schema/index.ts @@ -1,6 +1,7 @@ -import { mapValues, isEqual } from 'lodash-es' +import { isEqual } from 'lodash-es' import * as utils from '../../utils' +import { PropertyPath } from '../../utils/property-path-utils' import * as z from '../../z' import * as errors from '../common/errors' import { @@ -24,12 +25,16 @@ const { zuiKey } = z * @returns a typescript program that would construct the given schema if executed */ export function toTypescriptSchema(schema: z.ZodType): string { + return _toTypescriptSchema(schema, new PropertyPath()) +} + +function _toTypescriptSchema(schema: z.ZodType, path: PropertyPath): string { const wrappedSchema: z.ZodType = schema - const dts = sUnwrapZod(wrappedSchema) + const dts = sUnwrapZod(wrappedSchema, path) return dts } -function sUnwrapZod(schema: z.ZodType): string { +function sUnwrapZod(schema: z.ZodType, path: PropertyPath): string { const s = schema as z.ZodNativeType switch (s.typeName) { case 'ZodString': @@ -69,12 +74,15 @@ function sUnwrapZod(schema: z.ZodType): string { return `z.void()${_addMetadata(s._def)}`.trim() case 'ZodArray': - return `z.array(${sUnwrapZod(s._def.type)})${generateArrayChecks(s._def)}${_addMetadata(s._def, s._def.type)}` + return `z.array(${sUnwrapZod(s._def.type, path.withIndexType('number'))})${generateArrayChecks(s._def)}${_addMetadata(s._def, s._def.type)}` case 'ZodObject': - const props = mapValues(s.shape, sUnwrapZod) + const props: Record = {} + for (const [key, value] of Object.entries(s.shape)) { + props[key] = sUnwrapZod(value, path.withIndexType('key', key)) + } const catchall = s.additionalProperties() - const catchallString = catchall ? `.catchall(${sUnwrapZod(catchall)})` : '' + const catchallString = catchall ? `.catchall(${sUnwrapZod(catchall, path.withIndexType('string'))})` : '' return [ // 'z.object({', @@ -85,44 +93,56 @@ function sUnwrapZod(schema: z.ZodType): string { .trim() case 'ZodUnion': - const options = s._def.options.map(sUnwrapZod) + const options = s._def.options.map((option, index) => sUnwrapZod(option, path.withIndexType('number', index))) return `z.union([${options.join(', ')}])${_addMetadata(s._def)}`.trim() case 'ZodDiscriminatedUnion': - const opts = s._def.options.map(sUnwrapZod) + const opts = s._def.options.map((option, index) => sUnwrapZod(option, path.withIndexType('number', index))) const discriminator = primitiveToTypescriptValue(s._def.discriminator) return `z.discriminatedUnion(${discriminator}, [${opts.join(', ')}])${_addMetadata(s._def)}`.trim() case 'ZodIntersection': - const left: string = sUnwrapZod(s._def.left) - const right: string = sUnwrapZod(s._def.right) + const left: string = sUnwrapZod(s._def.left, path.withIndexType('number', 0)) + const right: string = sUnwrapZod(s._def.right, path.withIndexType('number', 1)) return `z.intersection(${left}, ${right})${_addMetadata(s._def)}`.trim() case 'ZodTuple': - const items = s._def.items.map(sUnwrapZod) + const items = s._def.items.map((item, index) => sUnwrapZod(item, path.withIndexType('number', index))) return `z.tuple([${items.join(', ')}])${_addMetadata(s._def)}`.trim() case 'ZodRecord': - const keyType = sUnwrapZod(s._def.keyType) - const valueType = sUnwrapZod(s._def.valueType) + const keyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf')) + const recordPath = z.is.zuiString(s._def.keyType) + ? path.withIndexType('string') + : z.is.zuiNumber(s._def.keyType) + ? path.withIndexType('number') + : path.withIndexType('any') + const valueType = sUnwrapZod(s._def.valueType, recordPath) return `z.record(${keyType}, ${valueType})${_addMetadata(s._def)}`.trim() case 'ZodMap': - const mapKeyType = sUnwrapZod(s._def.keyType) - const mapValueType = sUnwrapZod(s._def.valueType) + const mapKeyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf')) + const mapPath = z.is.zuiString(s._def.keyType) + ? path.withIndexType('string') + : z.is.zuiNumber(s._def.keyType) + ? path.withIndexType('number') + : path.withIndexType('any') + const mapValueType = sUnwrapZod(s._def.valueType, mapPath) return `z.map(${mapKeyType}, ${mapValueType})${_addMetadata(s._def)}`.trim() case 'ZodSet': - return `z.set(${sUnwrapZod(s._def.valueType)})${generateSetChecks(s._def)}${_addMetadata(s._def)}`.trim() + return `z.set(${sUnwrapZod(s._def.valueType, path.withIndexType('number'))})${generateSetChecks(s._def)}${_addMetadata(s._def)}`.trim() case 'ZodFunction': - const args = s._def.args.items.map(sUnwrapZod) + const args = s._def.args.items.map((arg, index) => + sUnwrapZod(arg, path.withWrapper('Parameters').withIndexType('number', index)) + ) const argsString = args.length ? `.args(${args.join(', ')})` : '' - const returns = sUnwrapZod(s._def.returns) + const returns = sUnwrapZod(s._def.returns, path.withWrapper('ReturnType')) return `z.function()${argsString}.returns(${returns})${_addMetadata(s._def)}`.trim() case 'ZodLazy': - return `z.lazy(() => ${sUnwrapZod(s._def.getter())})${_addMetadata(s._def)}`.trim() + return `z.lazy(() => ${sUnwrapZod(s._def.getter(), path)})${_addMetadata(s._def)}`.trim() case 'ZodLiteral': const value = primitiveToTypescriptValue(s._def.value) @@ -133,38 +153,38 @@ function sUnwrapZod(schema: z.ZodType): string { return `z.enum([${values.join(', ')}])${_addMetadata(s._def)}`.trim() case 'ZodEffects': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodEffects') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodEffects', path.toString()) case 'ZodNativeEnum': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodNativeEnum') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodNativeEnum', path.toString()) case 'ZodOptional': - return `z.optional(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim() + return `z.optional(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim() case 'ZodNullable': - return `z.nullable(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim() + return `z.nullable(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim() case 'ZodDefault': const defaultValue = unknownToTypescriptValue(s._def.defaultValue()) - return `z.default(${sUnwrapZod(s._def.innerType)}, ${defaultValue})${_addMetadata(s._def, s._def.innerType)}`.trim() + return `z.default(${sUnwrapZod(s._def.innerType, path)}, ${defaultValue})${_addMetadata(s._def, s._def.innerType)}`.trim() case 'ZodCatch': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodCatch') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodCatch', path.toString()) case 'ZodPromise': - return `z.promise(${sUnwrapZod(s._def.type)})${_addMetadata(s._def, s._def.type)}`.trim() + return `z.promise(${sUnwrapZod(s._def.type, path)})${_addMetadata(s._def, s._def.type)}`.trim() case 'ZodBranded': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodBranded') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodBranded', path.toString()) case 'ZodPipeline': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodPipeline') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodPipeline', path.toString()) case 'ZodSymbol': - throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodSymbol') + throw new errors.UnsupportedZuiToTypescriptSchemaError('ZodSymbol', path.toString()) case 'ZodReadonly': - return `z.readonly(${sUnwrapZod(s._def.innerType)})${_addMetadata(s._def, s._def.innerType)}`.trim() + return `z.readonly(${sUnwrapZod(s._def.innerType, path)})${_addMetadata(s._def, s._def.innerType)}`.trim() case 'ZodRef': const uri = primitiveToTypescriptValue(s._def.uri) diff --git a/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts b/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts index 37b26a94b23..5583329e032 100644 --- a/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts +++ b/packages/zui/src/transforms/zui-to-typescript-type/index.test.ts @@ -4,6 +4,11 @@ import * as z from '../../z' import * as errors from '../common/errors' import * as assert from '../../assertions.utils.test' +enum TestEnum { + A = 'A', + B = 'B', +} + const toTypescript = (schema: z.ZodType): string => { const hasTitle = 'title' in schema.ui if (!hasTitle) { @@ -1030,4 +1035,181 @@ describe.concurrent('optional', () => { const expected = `declare const MyString: string | undefined;` await assert.expectTypescript(typings).toMatchWithoutFormatting(expected) }) + + describe('error path propagation', () => { + it('should add object keys to path', () => { + try { + toTypescript(z.object({ foo: z.object({ bar: z.nativeEnum(TestEnum) }) })) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#.foo.bar') + } + }) + + it('should add [number] section to array types', () => { + try { + toTypescript(z.array(z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + it('should add [index] section to tuple types', () => { + try { + toTypescript(z.tuple([z.number(), z.nativeEnum(TestEnum)])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1]') + } + }) + + it('should add [number] section to set types', () => { + try { + toTypescript(z.set(z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + it('should add keyOf wrapper to record types with an invalid key', () => { + try { + toTypescript(z.record(z.nativeEnum(TestEnum), z.string())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>') + } + }) + + it('should add [string] section to record types with string key', () => { + try { + toTypescript(z.record(z.string(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[string]') + } + }) + + it('should add [number] section to record types with number key', () => { + try { + toTypescript(z.record(z.number(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + it('should add [*] section to record types with any key', () => { + try { + toTypescript(z.record(z.boolean(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[*]') + } + }) + + it('should add keyOf wrapper to map types with an invalid key', () => { + try { + toTypescript(z.map(z.nativeEnum(TestEnum), z.string())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('KeyOf<#>') + } + }) + + it('should add [string] section to map types with string key', () => { + try { + toTypescript(z.map(z.string(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[string]') + } + }) + + it('should add [number] section to map types with number key', () => { + try { + toTypescript(z.map(z.number(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[number]') + } + }) + + it('should add [*] section to map types with any key', () => { + try { + toTypescript(z.map(z.boolean(), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[*]') + } + }) + + it('should add [index] section to union types', () => { + try { + toTypescript(z.union([z.boolean(), z.nativeEnum(TestEnum)])) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1]') + } + }) + + it('should add [index] section to discriminated union types', () => { + try { + toTypescript( + z.discriminatedUnion('type', [ + z.object({ type: z.literal('a'), foo: z.string() }), + z.object({ type: z.literal('b'), bar: z.nativeEnum(TestEnum) }), + ]) + ) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[1].bar') + } + }) + + it('should add [index] section to intersection types', () => { + try { + toTypescript(z.intersection(z.object({ foo: z.nativeEnum(TestEnum) }), z.object({ bar: z.number() }))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('#[0].foo') + } + }) + + it('should add parameters wrapper to function type and show index', () => { + try { + toTypescript(z.function(z.tuple([z.boolean(), z.nativeEnum(TestEnum)]), z.string())) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('Parameters<#>[1]') + } + }) + + it('should add ReturnType wrapper to function return type', () => { + try { + toTypescript(z.function(z.tuple([z.boolean(), z.number()]), z.nativeEnum(TestEnum))) + expect.fail('should have thrown') + } catch (e) { + expect(e).toBeInstanceOf(errors.ZuiTransformError) + expect((e as errors.ZuiTransformError).path).toBe('ReturnType<#>') + } + }) + }) // error path propagation }) diff --git a/packages/zui/src/transforms/zui-to-typescript-type/index.ts b/packages/zui/src/transforms/zui-to-typescript-type/index.ts index ea7fbd3f254..2a91d0b6027 100644 --- a/packages/zui/src/transforms/zui-to-typescript-type/index.ts +++ b/packages/zui/src/transforms/zui-to-typescript-type/index.ts @@ -1,4 +1,5 @@ import * as utils from '../../utils' +import { PropertyPath } from '../../utils/property-path-utils' import * as z from '../../z' import * as errors from '../common/errors' import { @@ -99,9 +100,13 @@ type InternalOptions = { * @returns a string of the TypeScript **type** representing the schema */ export function toTypescriptType(schema: z.ZodType, options: TypescriptGenerationOptions = {}): string { + return _toTypescriptType(schema, new PropertyPath(), options) +} + +function _toTypescriptType(schema: z.ZodType, path: PropertyPath, options: TypescriptGenerationOptions = {}): string { const wrappedSchema: Declaration = getDeclarationProps(schema, options) - let dts = sUnwrapZod(wrappedSchema, options) + let dts = sUnwrapZod(wrappedSchema, path, options) if (options.formatter) { dts = options.formatter(dts) @@ -112,7 +117,11 @@ export function toTypescriptType(schema: z.ZodType, options: TypescriptGeneratio const _optionalKey = (key: string): string => (key.endsWith('?') ? key : `${key}?`) -function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | null, config: InternalOptions): string { +function sUnwrapZod( + schema: z.ZodType | KeyValue | FnParameters | Declaration | null, + path: PropertyPath, + config: InternalOptions +): string { const newConfig: InternalOptions = { ...config, declaration: false, @@ -124,7 +133,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | } if (schema instanceof Declaration) { - return unwrapDeclaration(schema, newConfig) + return unwrapDeclaration(schema, path, newConfig) } if (schema instanceof KeyValue) { @@ -142,7 +151,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | innerType = innerType?.describe(optionalValue.description) } - return sUnwrapZod(new KeyValue(_optionalKey(schema.key), innerType), newConfig) + return sUnwrapZod(new KeyValue(_optionalKey(schema.key), innerType), path, newConfig) } const description = getMultilineComment(schema.value._def.description || schema.value.description) @@ -151,7 +160,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | const isOptional = z.is.zuiAny(schema.value) // any is treated as optional for backwards compatibility const key = isOptional ? _optionalKey(schema.key) : schema.key - return `${delimiter}${description}${delimiter}${key}: ${sUnwrapZod(withoutDesc, newConfig)}${delimiter}` + return `${delimiter}${description}${delimiter}${key}: ${sUnwrapZod(withoutDesc, path, newConfig)}${delimiter}` } if (schema instanceof FnParameters) { @@ -160,7 +169,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | for (let i = 0; i < schema.schema.items.length; i++) { const argName = (schema.schema.items[i]?.ui?.title as string) ?? `arg${i}` const item = schema.schema.items[i]! - args += `${sUnwrapZod(new KeyValue(toPropertyKey(argName), item), newConfig)}${ + args += `${sUnwrapZod(new KeyValue(toPropertyKey(argName), item), path.withIndexType('number', i), newConfig)}${ i < schema.schema.items.length - 1 ? ', ' : '' } ` } @@ -170,7 +179,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | const isLiteral = z.is.zuiLiteral(schema.schema.naked()) - const typings = sUnwrapZod(schema.schema, newConfig).trim() + const typings = sUnwrapZod(schema.schema, path, newConfig).trim() const startsWithPairs = (typings.startsWith('{') && typings.endsWith('}')) || (typings.startsWith('[') && typings.endsWith(']')) || @@ -188,10 +197,10 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | if (schema instanceof FnReturn) { if (z.is.zuiOptional(schema.schema)) { - return `${sUnwrapZod(schema.schema.unwrap(), newConfig)} | undefined` + return `${sUnwrapZod(schema.schema.unwrap(), path, newConfig)} | undefined` } - return sUnwrapZod(schema.schema, newConfig) + return sUnwrapZod(schema.schema, path, newConfig) } const s = schema as z.ZodFirstPartySchemaTypes @@ -230,7 +239,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | return `${getMultilineComment(s.description)} void`.trim() case 'ZodArray': - const item = sUnwrapZod(s._def.type, newConfig) + const item = sUnwrapZod(s._def.type, path.withIndexType('number'), newConfig) if (isPrimitive(item)) { return `${item}[]` @@ -241,7 +250,7 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | case 'ZodObject': const props = Object.entries(s._def.shape()).map(([key, value]) => { if (z.is.zuiType(value)) { - return sUnwrapZod(new KeyValue(toPropertyKey(key), value), newConfig) + return sUnwrapZod(new KeyValue(toPropertyKey(key), value), path.withIndexType('key', key), newConfig) } return `${key}: unknown` }) @@ -249,44 +258,56 @@ function sUnwrapZod(schema: z.ZodType | KeyValue | FnParameters | Declaration | return `{ ${props.join('; ')} }` case 'ZodUnion': - const options = s._def.options.map((option) => { - return sUnwrapZod(option, newConfig) + const options = s._def.options.map((option, index) => { + return sUnwrapZod(option, path.withIndexType('number', index), newConfig) }) return `${getMultilineComment(s.description)} ${options.join(' | ')}` case 'ZodDiscriminatedUnion': - const opts = s._def.options.map((option) => { - return sUnwrapZod(option, newConfig) + const opts = s._def.options.map((option, index) => { + return sUnwrapZod(option, path.withIndexType('number', index), newConfig) }) return `${getMultilineComment(s.description)} ${opts.join(' | ')}` case 'ZodIntersection': - return `${sUnwrapZod(s._def.left, newConfig)} & ${sUnwrapZod(s._def.right, newConfig)}` + return `${sUnwrapZod(s._def.left, path.withIndexType('number', 0), newConfig)} & ${sUnwrapZod(s._def.right, path.withIndexType('number', 1), newConfig)}` case 'ZodTuple': if (s._def.items.length === 0) { return '[]' } - const items = s._def.items.map((i) => sUnwrapZod(i, newConfig)) + const items = s._def.items.map((i, index) => sUnwrapZod(i, path.withIndexType('number', index), newConfig)) return `[${items.join(', ')}]` - case 'ZodRecord': - const keyType = sUnwrapZod(s._def.keyType, newConfig) - const valueType = sUnwrapZod(s._def.valueType, newConfig) + case 'ZodRecord': { + const keyType = sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'), newConfig) + const recordPath = z.is.zuiString(s._def.keyType) + ? path.withIndexType('string') + : z.is.zuiNumber(s._def.keyType) + ? path.withIndexType('number') + : path.withIndexType('any') + const valueType = sUnwrapZod(s._def.valueType, recordPath, newConfig) return `${getMultilineComment(s.description)} { [key: ${keyType}]: ${valueType} }` + } - case 'ZodMap': - return `Map<${sUnwrapZod(s._def.keyType, newConfig)}, ${sUnwrapZod(s._def.valueType, newConfig)}>` + case 'ZodMap': { + const recordPath = z.is.zuiString(s._def.keyType) + ? path.withIndexType('string') + : z.is.zuiNumber(s._def.keyType) + ? path.withIndexType('number') + : path.withIndexType('any') + return `Map<${sUnwrapZod(s._def.keyType, path.withWrapper('KeyOf'), newConfig)}, ${sUnwrapZod(s._def.valueType, recordPath, newConfig)}>` + } case 'ZodSet': - return `Set<${sUnwrapZod(s._def.valueType, newConfig)}>` + return `Set<${sUnwrapZod(s._def.valueType, path.withIndexType('number'), newConfig)}>` case 'ZodFunction': - const input = sUnwrapZod(new FnParameters(s._def.args), newConfig) - const output = sUnwrapZod(new FnReturn(s._def.returns), newConfig) + const input = sUnwrapZod(new FnParameters(s._def.args), path.withWrapper('Parameters'), newConfig) + const output = sUnwrapZod(new FnReturn(s._def.returns), path.withWrapper('ReturnType'), newConfig) const parentIsType = config?.parent instanceof Declaration && config?.parent.props.type === 'type' if (config?.declaration && !parentIsType) { @@ -298,7 +319,7 @@ ${opts.join(' | ')}` (${input}) => ${output}` case 'ZodLazy': - return sUnwrapZod(s._def.getter(), newConfig) + return sUnwrapZod(s._def.getter(), path, newConfig) case 'ZodLiteral': const value: string = primitiveToTypscriptLiteralType(s._def.value) @@ -310,38 +331,38 @@ ${value}`.trim() return values.join(' | ') case 'ZodEffects': - return sUnwrapZod(s._def.schema, newConfig) + return sUnwrapZod(s._def.schema, path, newConfig) case 'ZodNativeEnum': - throw new errors.UnsupportedZuiToTypescriptTypeError('ZodNativeEnum') + throw new errors.UnsupportedZuiToTypescriptTypeError('ZodNativeEnum', path.toString()) case 'ZodOptional': - return `${sUnwrapZod(s._def.innerType, newConfig)} | undefined` + return `${sUnwrapZod(s._def.innerType, path, newConfig)} | undefined` case 'ZodNullable': - return `${sUnwrapZod(s._def.innerType, newConfig)} | null` + return `${sUnwrapZod(s._def.innerType, path, newConfig)} | null` case 'ZodDefault': const defaultInnerType = config.treatDefaultAsOptional ? s._def.innerType.optional() : s._def.innerType - return sUnwrapZod(defaultInnerType, newConfig) + return sUnwrapZod(defaultInnerType, path, newConfig) case 'ZodCatch': - return sUnwrapZod(s._def.innerType, newConfig) + return sUnwrapZod(s._def.innerType, path, newConfig) case 'ZodPromise': - return `Promise<${sUnwrapZod(s._def.type, newConfig)}>` + return `Promise<${sUnwrapZod(s._def.type, path, newConfig)}>` case 'ZodBranded': - return sUnwrapZod(s._def.type, newConfig) + return sUnwrapZod(s._def.type, path, newConfig) case 'ZodPipeline': - return sUnwrapZod(s._def.in, newConfig) + return sUnwrapZod(s._def.in, path, newConfig) case 'ZodSymbol': return `${getMultilineComment(s.description)} symbol`.trim() case 'ZodReadonly': - return `Readonly<${sUnwrapZod(s._def.innerType, newConfig)}>` + return `Readonly<${sUnwrapZod(s._def.innerType, path, newConfig)}>` case 'ZodRef': return toTypeArgumentName(s._def.uri) @@ -351,15 +372,15 @@ ${value}`.trim() } } -const unwrapDeclaration = (declaration: Declaration, options: InternalOptions): string => { +const unwrapDeclaration = (declaration: Declaration, path: PropertyPath, options: InternalOptions): string => { if (declaration.props.type === 'none') { - return sUnwrapZod(declaration.props.schema, options) + return sUnwrapZod(declaration.props.schema, path, options) } const { schema, identifier } = declaration.props const description = getMultilineComment(schema.description) const withoutDesc = schema.describe('') - const typings = sUnwrapZod(withoutDesc, { ...options, declaration: true }) + const typings = sUnwrapZod(withoutDesc, path, { ...options, declaration: true }) const isLargeDeclaration = typings.split('\n').length >= LARGE_DECLARATION_LINES const closingTag = isLargeDeclaration && options.includeClosingTags ? `// end of ${identifier}` : '' diff --git a/packages/zui/src/utils/property-path-utils.test.ts b/packages/zui/src/utils/property-path-utils.test.ts index 2730c9c3158..a9bbad53f15 100644 --- a/packages/zui/src/utils/property-path-utils.test.ts +++ b/packages/zui/src/utils/property-path-utils.test.ts @@ -6,86 +6,78 @@ describe.concurrent('PropertyPath', () => { expect(new PropertyPath().toString()).toBe('#') }) - it('renders a name-only section', () => { - expect(new PropertyPath().appendSection('foo').toString()).toBe('#.foo') + it('doesnt start with # when starting with wrapper', () => { + expect(new PropertyPath().withIndexType('key', 'foo').withWrapper('KeyOf').toString()).not.toMatch(/^#/) + }) + + it('adds a key index with a dot', () => { + expect(new PropertyPath().withIndexType('key', 'foo').toString()).toBe('#.foo') }) describe.concurrent('withIndexType', () => { it('adds a number index without a value', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('number') - expect(path.toString()).toBe('#.foo[number]') + const path = new PropertyPath().withIndexType('number') + expect(path.toString()).toBe('#[number]') }) it('adds a number index with a value', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('number', 3) - expect(path.toString()).toBe('#.foo[3]') + const path = new PropertyPath().withIndexType('number', 3) + expect(path.toString()).toBe('#[3]') }) it('adds a string index without a value', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('string') - expect(path.toString()).toBe('#.foo[string]') + const path = new PropertyPath().withIndexType('string') + expect(path.toString()).toBe('#[string]') }) it('adds a string index with a value', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('string', 'foo') - expect(path.toString()).toBe('#.foo[foo]') + const path = new PropertyPath().withIndexType('string', 'foo') + expect(path.toString()).toBe('#[foo]') }) it('adds an any index', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('any') - expect(path.toString()).toBe('#.foo[*]') + const path = new PropertyPath().withIndexType('any') + expect(path.toString()).toBe('#[*]') }) - it('stacks multiple indices on the same section', () => { - const path = new PropertyPath().appendSection('foo').withIndexType('number', 2).withIndexType('string', 'col') - expect(path.toString()).toBe('#.foo[2][col]') + it('does not mutate the original', () => { + const original = new PropertyPath() + const next = original.withIndexType('key', 'foo') + expect(original.toString()).toBe('#') + expect(next.toString()).toBe('#.foo') }) - it('applies different index types on consecutive sections', () => { - const path = new PropertyPath() - .appendSection('foo') - .withIndexType('number', 2) - .appendSection('bar') - .withIndexType('string', 'baz') - expect(path.toString()).toBe('#.foo[2].bar[baz]') + it('chains multiple indexType', () => { + const path = new PropertyPath().withIndexType('key', 'foo').withIndexType('key', 'bar') + expect(path.toString()).toBe('#.foo.bar') }) }) - describe.concurrent('withPrefix', () => { - it('prepends the prefix followed by a space before the #', () => { - const path = new PropertyPath().appendSection('foo').withPrefix('keyOf') - expect(path.toString()).toBe('keyOf #.foo') + describe.concurrent('withWrapper', () => { + it('wraps the whole path', () => { + const path = new PropertyPath().withIndexType('key', 'foo').withWrapper('ReturnType') + expect(path.toString()).toBe('ReturnType<#.foo>') }) it('does not mutate the original', () => { - const original = new PropertyPath().appendSection('foo') - const prefixed: PropertyPath = original.withPrefix('keyOf') + const original = new PropertyPath().withIndexType('key', 'foo') + const wrapped: PropertyPath = original.withWrapper('ReturnType') expect(original.toString()).toBe('#.foo') - expect(prefixed.toString()).toBe('keyOf #.foo') + expect(wrapped.toString()).toBe('ReturnType<#.foo>') }) - it('prefix is preserved through appendSection', () => { - const path = new PropertyPath().appendSection('foo').withPrefix('keyOf').appendSection('bar') - expect(path.toString()).toBe('keyOf #.foo.bar') + it('wrapper is preserved through withIndexType', () => { + const path = new PropertyPath().withIndexType('key', 'foo').withWrapper('ReturnType').withIndexType('key', 'bar') + expect(path.toString()).toBe('ReturnType<#.foo>.bar') }) - it('prefix is preserved through withIndexType', () => { - const path = new PropertyPath().appendSection('foo').withPrefix('keyOf').withIndexType('number', 1) - expect(path.toString()).toBe('keyOf #.foo[1]') - }) - }) - - describe.concurrent('appendSection', () => { - it('does not mutate the original', () => { - const original = new PropertyPath() - const next = original.appendSection('foo') - expect(original.toString()).toBe('#') - expect(next.toString()).toBe('#.foo') - }) - - it('chains multiple appends', () => { - const path = new PropertyPath().appendSection('foo').appendSection('bar') - expect(path.toString()).toBe('#.foo.bar') + it('should combine wrappers', () => { + const path = new PropertyPath() + .withIndexType('key', 'foo') + .withWrapper('ReturnType') + .withIndexType('key', 'bar') + .withWrapper('KeyOf') + expect(path.toString()).toBe('KeyOf.bar>') }) }) }) diff --git a/packages/zui/src/utils/property-path-utils.ts b/packages/zui/src/utils/property-path-utils.ts index 26e26352ec0..f0543278129 100644 --- a/packages/zui/src/utils/property-path-utils.ts +++ b/packages/zui/src/utils/property-path-utils.ts @@ -17,39 +17,42 @@ export type AnySectionIndex = { type: 'any' } -export type PathSection = KeySectionIndex | NumberSectionIndex | StringSectionIndex | AnySectionIndex +export type PathSection = KeySectionIndex | NumberSectionIndex | StringSectionIndex | AnySectionIndex | PropertyPath export class PropertyPath { + public readonly type = 'propertyPath' private readonly _sections: PathSection[] - private readonly _prefix: string + private readonly _wrapper: string - constructor(sections: PathSection[] = [], prefix: string = '') { + constructor(sections: PathSection[] = [], wrapper: string = '') { this._sections = sections - this._prefix = prefix - } - - appendSection(name: string): PropertyPath { - return new PropertyPath([...this._sections, { type: 'key', value: name }], this._prefix) + this._wrapper = wrapper } + withIndexType(type: 'key', value: string): PropertyPath withIndexType(type: 'number', value?: number): PropertyPath withIndexType(type: 'string', value?: string): PropertyPath withIndexType(type: 'any'): PropertyPath - withIndexType(type: 'number' | 'string' | 'any', value?: number | string): PropertyPath { - return new PropertyPath([...this._sections, { type, value } as PathSection], this._prefix) + withIndexType(type: 'number' | 'string' | 'any' | 'key', value?: number | string): PropertyPath { + return new PropertyPath([...this._sections, { type, value } as PathSection], this._wrapper) } - withPrefix(prefix: string): PropertyPath { - return new PropertyPath(this._sections, prefix + ' ') + withWrapper(wrapper: string): PropertyPath { + return new PropertyPath([new PropertyPath([...this._sections], wrapper)]) } toString(): string { - return `${this._prefix}#${this._sections + const wrapperBegin = this._wrapper ? `${this._wrapper}<` : '' + const wrapperEnd = this._wrapper ? '>' : '' + const rootCarAtBeginning = this._sections.length === 0 || this._sections[0]!.type !== 'propertyPath' ? '#' : '' + + return `${wrapperBegin}${rootCarAtBeginning}${this._sections .map((section) => { + if (section.type === 'propertyPath') return section.toString() if (section.type === 'key') return `.${section.value}` if (section.type === 'any') return '[*]' return `[${section.value ?? section.type}]` }) - .join('')}` + .join('')}${wrapperEnd}` } } From a545a11ed876d115b2a57a192acb7e7c184b978a Mon Sep 17 00:00:00 2001 From: mikee-b <112516913+mikee-b@users.noreply.github.com> Date: Fri, 29 May 2026 11:24:03 -0400 Subject: [PATCH 5/8] chore(integrations/zendesk): Re-enabled zendesk deploy in production (#15232) --- .github/workflows/deploy-integrations-production.yml | 2 +- .github/workflows/prod-master-version-verification.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-integrations-production.yml b/.github/workflows/deploy-integrations-production.yml index a49a74805c4..71b885a51e0 100644 --- a/.github/workflows/deploy-integrations-production.yml +++ b/.github/workflows/deploy-integrations-production.yml @@ -31,7 +31,7 @@ jobs: uses: ./.github/actions/deploy-integrations with: environment: 'production' - extra_filter: "-F '!docusign' -F '!zendesk' -F '!chat' -F '!grafana'" + extra_filter: "-F '!docusign' -F '!chat' -F '!grafana'" force: ${{ github.event.inputs.force == 'true' }} sentry_auth_token: ${{ secrets.SENTRY_AUTH_TOKEN }} token_cloud_ops_account: ${{ secrets.PRODUCTION_TOKEN_CLOUD_OPS_ACCOUNT }} diff --git a/.github/workflows/prod-master-version-verification.yml b/.github/workflows/prod-master-version-verification.yml index 2d9c29a04ee..f789d77b344 100644 --- a/.github/workflows/prod-master-version-verification.yml +++ b/.github/workflows/prod-master-version-verification.yml @@ -24,7 +24,7 @@ jobs: REPO: ${{ github.repository }} SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WORKFLOW_SEAGULL_WEBHOOK_URL }} run: | - SKIP_INTEGRATIONS=("chat" "docusign" "zendesk" "zendesk-messaging-hitl") + SKIP_INTEGRATIONS=("chat" "docusign" "zendesk-messaging-hitl") integrations=$(find integrations -mindepth 1 -maxdepth 1 -type d -print0 | xargs -0 -n1 basename | sort -u) should_fail=0 From 48781007478de46a6a9450d3da11050fe18ac06c Mon Sep 17 00:00:00 2001 From: Mak <98408710+makhlouf1102@users.noreply.github.com> Date: Fri, 29 May 2026 11:35:20 -0400 Subject: [PATCH 6/8] fix(monday): update vrl Link (#15233) Co-authored-by: Makhlouf Hennine --- integrations/monday/linkTemplate.vrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/monday/linkTemplate.vrl b/integrations/monday/linkTemplate.vrl index 06be15fb414..23372049f7a 100644 --- a/integrations/monday/linkTemplate.vrl +++ b/integrations/monday/linkTemplate.vrl @@ -1,4 +1,4 @@ webhookId = to_string!(.webhookId) webhookUrl = to_string!(.webhookUrl) -"{{ webhookUrl }}/oauth/wizard/oauth-redirect?state={{ webhookId }}" +"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}" From 811980f533cbe8ba1006aab0625edfc374b4bb63 Mon Sep 17 00:00:00 2001 From: Mak <98408710+makhlouf1102@users.noreply.github.com> Date: Fri, 29 May 2026 12:03:46 -0400 Subject: [PATCH 7/8] fix(monday): bump the version (#15234) Co-authored-by: Makhlouf Hennine --- integrations/monday/integration.definition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrations/monday/integration.definition.ts b/integrations/monday/integration.definition.ts index d147f0ad981..743555652f9 100644 --- a/integrations/monday/integration.definition.ts +++ b/integrations/monday/integration.definition.ts @@ -5,7 +5,7 @@ export default new IntegrationDefinition({ name: 'monday', title: 'Monday', description: 'Manage items in Monday boards.', - version: '1.1.3', + version: '1.1.4', readme: 'hub.md', icon: 'icon.svg', states: { From 9174cdec35ea69252b0abc2ccd30c444673572a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Levasseur?= Date: Fri, 29 May 2026 13:44:15 -0400 Subject: [PATCH 8/8] chore(client): replace tsup by rollup because it's no longer maintained (#15224) --- packages/client/package.json | 5 +- packages/client/rollup.dts.config.mjs | 15 ++ pnpm-lock.yaml | 344 ++++++++++++++++++++++---- 3 files changed, 312 insertions(+), 52 deletions(-) create mode 100644 packages/client/rollup.dts.config.mjs diff --git a/packages/client/package.json b/packages/client/package.json index 0bf140dfdd8..89e98f8401a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -16,7 +16,7 @@ }, "scripts": { "check:type": "tsc --noEmit", - "build:type": "tsup --tsconfig tsconfig.build.json ./src/index.ts --dts-resolve --dts-only", + "build:type": "rollup -c rollup.dts.config.mjs", "build:browser": "ts-node -T ./build.ts --browser", "build:node": "ts-node -T ./build.ts --node", "build:bundle": "ts-node -T ./build.ts --bundle", @@ -34,7 +34,8 @@ "@types/qs": "^6.9.7", "esbuild": "^0.25.10", "lodash": "^4.17.21", - "tsup": "^8.0.2" + "rollup": "^4.60.4", + "rollup-plugin-dts": "^6.4.1" }, "engines": { "node": ">=18.0.0" diff --git a/packages/client/rollup.dts.config.mjs b/packages/client/rollup.dts.config.mjs new file mode 100644 index 00000000000..13e8fc6fbab --- /dev/null +++ b/packages/client/rollup.dts.config.mjs @@ -0,0 +1,15 @@ +import dts from 'rollup-plugin-dts' + +export default { + input: './src/index.ts', + external: [/node_modules/], + output: { + file: './dist/index.d.ts', + }, + plugins: [ + dts({ + tsconfig: './tsconfig.build.json', + respectExternal: true, + }), + ], +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ce5146db94..c6964bf8458 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3016,9 +3016,12 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 - tsup: - specifier: ^8.0.2 - version: 8.0.2(@microsoft/api-extractor@7.49.0(@types/node@22.16.4))(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.3))(typescript@5.9.3) + rollup: + specifier: ^4.60.4 + version: 4.60.4 + rollup-plugin-dts: + specifier: ^6.4.1 + version: 6.4.1(rollup@4.60.4)(typescript@5.9.3) packages/cognitive: dependencies: @@ -3994,10 +3997,6 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} - engines: {node: '>=6.9.0'} - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -5256,6 +5255,9 @@ packages: resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -5264,21 +5266,12 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/sourcemap-codec@1.5.4': - resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.29': - resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - '@jridgewell/trace-mapping@0.3.30': resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} @@ -6006,91 +5999,216 @@ packages: cpu: [arm] os: [android] + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} + cpu: [arm] + os: [android] + '@rollup/rollup-android-arm64@4.24.2': resolution: {integrity: sha512-iZoYCiJz3Uek4NI0J06/ZxUgwAfNzqltK0MptPDO4OR0a88R4h0DSELMsflS6ibMCJ4PnLvq8f7O1d7WexUvIA==} cpu: [arm64] os: [android] + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} + cpu: [arm64] + os: [android] + '@rollup/rollup-darwin-arm64@4.24.2': resolution: {integrity: sha512-/UhrIxobHYCBfhi5paTkUDQ0w+jckjRZDZ1kcBL132WeHZQ6+S5v9jQPVGLVrLbNUebdIRpIt00lQ+4Z7ys4Rg==} cpu: [arm64] os: [darwin] + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} + cpu: [arm64] + os: [darwin] + '@rollup/rollup-darwin-x64@4.24.2': resolution: {integrity: sha512-1F/jrfhxJtWILusgx63WeTvGTwE4vmsT9+e/z7cZLKU8sBMddwqw3UV5ERfOV+H1FuRK3YREZ46J4Gy0aP3qDA==} cpu: [x64] os: [darwin] + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} + cpu: [x64] + os: [darwin] + '@rollup/rollup-freebsd-arm64@4.24.2': resolution: {integrity: sha512-1YWOpFcGuC6iGAS4EI+o3BV2/6S0H+m9kFOIlyFtp4xIX5rjSnL3AwbTBxROX0c8yWtiWM7ZI6mEPTI7VkSpZw==} cpu: [arm64] os: [freebsd] + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} + cpu: [arm64] + os: [freebsd] + '@rollup/rollup-freebsd-x64@4.24.2': resolution: {integrity: sha512-3qAqTewYrCdnOD9Gl9yvPoAoFAVmPJsBvleabvx4bnu1Kt6DrB2OALeRVag7BdWGWLhP1yooeMLEi6r2nYSOjg==} cpu: [x64] os: [freebsd] + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} + cpu: [x64] + os: [freebsd] + '@rollup/rollup-linux-arm-gnueabihf@4.24.2': resolution: {integrity: sha512-ArdGtPHjLqWkqQuoVQ6a5UC5ebdX8INPuJuJNWRe0RGa/YNhVvxeWmCTFQ7LdmNCSUzVZzxAvUznKaYx645Rig==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.24.2': resolution: {integrity: sha512-B6UHHeNnnih8xH6wRKB0mOcJGvjZTww1FV59HqJoTJ5da9LCG6R4SEBt6uPqzlawv1LoEXSS0d4fBlHNWl6iYw==} cpu: [arm] os: [linux] + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} + cpu: [arm] + os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.24.2': resolution: {integrity: sha512-kr3gqzczJjSAncwOS6i7fpb4dlqcvLidqrX5hpGBIM1wtt0QEVtf4wFaAwVv8QygFU8iWUMYEoJZWuWxyua4GQ==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} + cpu: [arm64] + os: [linux] + '@rollup/rollup-linux-arm64-musl@4.24.2': resolution: {integrity: sha512-TDdHLKCWgPuq9vQcmyLrhg/bgbOvIQ8rtWQK7MRxJ9nvaxKx38NvY7/Lo6cYuEnNHqf6rMqnivOIPIQt6H2AoA==} cpu: [arm64] os: [linux] + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} + cpu: [loong64] + os: [linux] + '@rollup/rollup-linux-powerpc64le-gnu@4.24.2': resolution: {integrity: sha512-xv9vS648T3X4AxFFZGWeB5Dou8ilsv4VVqJ0+loOIgDO20zIhYfDLkk5xoQiej2RiSQkld9ijF/fhLeonrz2mw==} cpu: [ppc64] os: [linux] + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} + cpu: [ppc64] + os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.24.2': resolution: {integrity: sha512-tbtXwnofRoTt223WUZYiUnbxhGAOVul/3StZ947U4A5NNjnQJV5irKMm76G0LGItWs6y+SCjUn/Q0WaMLkEskg==} cpu: [riscv64] os: [linux] + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} + cpu: [riscv64] + os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.24.2': resolution: {integrity: sha512-gc97UebApwdsSNT3q79glOSPdfwgwj5ELuiyuiMY3pEWMxeVqLGKfpDFoum4ujivzxn6veUPzkGuSYoh5deQ2Q==} cpu: [s390x] os: [linux] + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} + cpu: [s390x] + os: [linux] + '@rollup/rollup-linux-x64-gnu@4.24.2': resolution: {integrity: sha512-jOG/0nXb3z+EM6SioY8RofqqmZ+9NKYvJ6QQaa9Mvd3RQxlH68/jcB/lpyVt4lCiqr04IyaC34NzhUqcXbB5FQ==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} + cpu: [x64] + os: [linux] + '@rollup/rollup-linux-x64-musl@4.24.2': resolution: {integrity: sha512-XAo7cJec80NWx9LlZFEJQxqKOMz/lX3geWs2iNT5CHIERLFfd90f3RYLLjiCBm1IMaQ4VOX/lTC9lWfzzQm14Q==} cpu: [x64] os: [linux] + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} + cpu: [arm64] + os: [openharmony] + '@rollup/rollup-win32-arm64-msvc@4.24.2': resolution: {integrity: sha512-A+JAs4+EhsTjnPQvo9XY/DC0ztaws3vfqzrMNMKlwQXuniBKOIIvAAI8M0fBYiTCxQnElYu7mLk7JrhlQ+HeOw==} cpu: [arm64] os: [win32] + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} + cpu: [arm64] + os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.24.2': resolution: {integrity: sha512-ZhcrakbqA1SCiJRMKSU64AZcYzlZ/9M5LaYil9QWxx9vLnkQ9Vnkve17Qn4SjlipqIIBFKjBES6Zxhnvh0EAEw==} cpu: [ia32] os: [win32] + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} + cpu: [x64] + os: [win32] + '@rollup/rollup-win32-x64-msvc@4.24.2': resolution: {integrity: sha512-2mLH46K1u3r6uwc95hU+OR9q/ggYMpnS7pSp83Ece1HUQgF9Nh/QwTK5rcgbFnV9j+08yBrU5sA/P0RK2MSBNA==} cpu: [x64] os: [win32] + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} + cpu: [x64] + os: [win32] + '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} @@ -10149,6 +10267,9 @@ packages: magic-string@0.30.12: resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-dir@1.3.0: resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} engines: {node: '>=4'} @@ -11444,11 +11565,23 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rollup-plugin-dts@6.4.1: + resolution: {integrity: sha512-l//F3Zf7ID5GoOfLfD8kroBjQKEKpy1qfhtAdnpibFZMffPaylrg1CoDC2vGkPeTeyxUe4bVFCln2EFuL7IGGg==} + engines: {node: '>=20'} + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 || ^6.0 + rollup@4.24.2: resolution: {integrity: sha512-do/DFGq5g6rdDhdpPq5qb2ecoczeK6y+2UAjdJ5trjQJj5f1AiVdLRWRc9A9/fFukfvJRgM0UXzxBIYMovm5ww==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -13823,12 +13956,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/code-frame@7.27.1': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -13891,8 +14018,8 @@ snapshots: dependencies: '@babel/parser': 7.27.2 '@babel/types': 7.27.1 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/generator@7.29.1': @@ -13979,7 +14106,7 @@ snapshots: dependencies: '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.27.1 transitivePeerDependencies: - supports-color @@ -14170,13 +14297,13 @@ snapshots: '@babel/template@7.26.9': dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@babel/parser': 7.26.9 '@babel/types': 7.26.9 '@babel/template@7.27.2': dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@babel/parser': 7.27.2 '@babel/types': 7.27.1 @@ -14200,7 +14327,7 @@ snapshots: '@babel/traverse@7.27.1': dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@babel/generator': 7.27.1 '@babel/parser': 7.27.2 '@babel/template': 7.27.2 @@ -14230,7 +14357,7 @@ snapshots: '@babel/types@7.27.1': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@babel/types@7.29.0': dependencies: @@ -15024,7 +15151,7 @@ snapshots: '@jest/test-result': 29.5.0 '@jest/transform': 29.5.0 '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 '@types/node': 22.16.4 chalk: 4.1.2 collect-v8-coverage: 1.0.1 @@ -15052,7 +15179,7 @@ snapshots: '@jest/source-map@29.4.3': dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 callsites: 3.1.0 graceful-fs: 4.2.11 @@ -15074,7 +15201,7 @@ snapshots: dependencies: '@babel/core': 7.26.9 '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.29 + '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -15113,28 +15240,24 @@ snapshots: '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/sourcemap-codec@1.5.4': {} - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@jridgewell/trace-mapping@0.3.29': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping@0.3.30': dependencies: @@ -15844,7 +15967,7 @@ snapshots: '@readme/better-ajv-errors@1.6.0(ajv@8.17.1)': dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@babel/runtime': 7.24.7 '@humanwhocodes/momoa': 2.0.4 ajv: 8.17.1 @@ -15903,57 +16026,132 @@ snapshots: '@rollup/rollup-android-arm-eabi@4.24.2': optional: true + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + '@rollup/rollup-android-arm64@4.24.2': optional: true + '@rollup/rollup-android-arm64@4.60.4': + optional: true + '@rollup/rollup-darwin-arm64@4.24.2': optional: true + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + '@rollup/rollup-darwin-x64@4.24.2': optional: true + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + '@rollup/rollup-freebsd-arm64@4.24.2': optional: true + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + '@rollup/rollup-freebsd-x64@4.24.2': optional: true + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.24.2': optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + '@rollup/rollup-linux-arm-musleabihf@4.24.2': optional: true + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + '@rollup/rollup-linux-arm64-gnu@4.24.2': optional: true + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + '@rollup/rollup-linux-arm64-musl@4.24.2': optional: true + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + '@rollup/rollup-linux-powerpc64le-gnu@4.24.2': optional: true + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + '@rollup/rollup-linux-riscv64-gnu@4.24.2': optional: true + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + '@rollup/rollup-linux-s390x-gnu@4.24.2': optional: true + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + '@rollup/rollup-linux-x64-gnu@4.24.2': optional: true + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + '@rollup/rollup-linux-x64-musl@4.24.2': optional: true + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + '@rollup/rollup-win32-arm64-msvc@4.24.2': optional: true + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + '@rollup/rollup-win32-ia32-msvc@4.24.2': optional: true + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + '@rollup/rollup-win32-x64-msvc@4.24.2': optional: true + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + '@rtsao/scc@1.1.0': {} '@rushstack/node-core-library@5.10.1(@types/node@22.16.4)': @@ -19214,7 +19412,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.8 esutils@2.0.3: {} @@ -20666,7 +20864,7 @@ snapshots: jest-message-util@29.5.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@jest/types': 29.5.0 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -21291,7 +21489,11 @@ snapshots: magic-string@0.30.12: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 make-dir@1.3.0: dependencies: @@ -22304,7 +22506,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -22917,6 +23119,17 @@ snapshots: dependencies: glob: 7.2.3 + rollup-plugin-dts@6.4.1(rollup@4.60.4)(typescript@5.9.3): + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + convert-source-map: 2.0.0 + magic-string: 0.30.21 + rollup: 4.60.4 + typescript: 5.9.3 + optionalDependencies: + '@babel/code-frame': 7.29.0 + rollup@4.24.2: dependencies: '@types/estree': 1.0.6 @@ -22941,6 +23154,37 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.2 fsevents: 2.3.3 + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.3 @@ -23757,7 +24001,7 @@ snapshots: joycon: 3.1.1 postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@22.16.4)(typescript@5.9.3)) resolve-from: 5.0.0 - rollup: 4.24.2 + rollup: 4.60.4 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tree-kill: 1.2.2 @@ -24070,7 +24314,7 @@ snapshots: v8-to-istanbul@9.1.0: dependencies: - '@jridgewell/trace-mapping': 0.3.30 + '@jridgewell/trace-mapping': 0.3.31 '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0