From 9011269894544e005429d9a2d3ecfa7a7d57a725 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Wed, 29 Jul 2026 17:32:53 +0200 Subject: [PATCH 1/2] Fixed round trip `blocksToHTMLLossy` -> `tryParseHTMLToBlocks` ignoring empty blocks --- .../html/util/serializeBlocksExternalHTML.ts | 35 ++++++++--- .../blocknoteHTML/paragraph/emptyBetween.html | 25 ++++++++ .../__snapshots__/html/complex/misc.html | 2 +- .../__snapshots__/html/paragraph/empty.html | 2 +- .../html/paragraph/emptyBetween.html | 3 + .../markdown/paragraph/emptyBetween.md | 5 ++ .../nodes/paragraph/emptyBetween.json | 62 +++++++++++++++++++ .../export/exportTestInstances.ts | 19 ++++++ 8 files changed, 143 insertions(+), 10 deletions(-) create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/emptyBetween.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md create mode 100644 tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/emptyBetween.json diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 064a9af6b5..0425af0c81 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -265,15 +265,34 @@ function serializeBlock< } } - if (ret.contentDOM && block.content) { - const ic = serializeInlineContentExternalHTML( - editor, - block.content as any, // TODO - serializer, - { ...options, blockType: block.type }, - ); + if (ret.contentDOM) { + if (block.content) { + const ic = serializeInlineContentExternalHTML( + editor, + block.content as any, // TODO + serializer, + { ...options, blockType: block.type }, + ); - ret.contentDOM.appendChild(ic); + ret.contentDOM.appendChild(ic); + } + + // Blocks with empty inline content (e.g. an empty paragraph) serialize to + // an element with no children (e.g. `

`), which is ignored when the + // HTML is parsed back into blocks. To make these blocks survive such a + // round trip, we fill their content with a non-breaking space. + // + // Only applies to blocks that hold inline content: containers (columns, + // tables) fill their `contentDOM` with child blocks later on, and code + // blocks would turn the placeholder into literal content. + const blockNodeType = editor.pmSchema.nodes[block.type as any]; + if ( + blockNodeType?.inlineContent && + !blockNodeType.spec.code && + ret.contentDOM.childNodes.length === 0 + ) { + ret.contentDOM.appendChild(doc.createTextNode(" ")); + } } let listType = undefined; diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/emptyBetween.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/emptyBetween.html new file mode 100644 index 0000000000..65c75517ae --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/emptyBetween.html @@ -0,0 +1,25 @@ +
+
+
+
+

Before

+
+
+
+
+
+
+

+ +

+
+
+
+
+
+
+

After

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html index 5dd72c369b..c3ddccdd39 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html @@ -13,6 +13,6 @@

-

+

 

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html index 540135ad6a..8b4abd40de 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html @@ -1 +1 @@ -

\ No newline at end of file +

 

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html new file mode 100644 index 0000000000..59a36b2883 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html @@ -0,0 +1,3 @@ +

Before

+

 

+

After

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md new file mode 100644 index 0000000000..5ab341feb0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md @@ -0,0 +1,5 @@ +Before + +  + +After diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/emptyBetween.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/emptyBetween.json new file mode 100644 index 0000000000..5807d53144 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/emptyBetween.json @@ -0,0 +1,62 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Before", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "After", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts index bd8cb77493..1d901c81a9 100644 --- a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts +++ b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts @@ -29,6 +29,25 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "paragraph/emptyBetween", + content: [ + { + type: "paragraph", + content: "Before", + }, + { + type: "paragraph", + }, + { + type: "paragraph", + content: "After", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "paragraph/basic", From 1711bddd6f66da35afafdf4662edbe72b685fc55 Mon Sep 17 00:00:00 2001 From: Matthew Lipski Date: Tue, 4 Aug 2026 11:35:49 +0200 Subject: [PATCH 2/2] Replaced non-breaking white space with Unicode object replacement and fixed tests --- .../html/util/serializeBlocksExternalHTML.ts | 19 +++++++- .../exporters/markdown/markdownExporter.ts | 11 ++++- .../core/src/api/parsers/html/parseHTML.ts | 43 +++++++++++++++++++ ...)_1_2263f232809497c4f6c0a975957dff6b.json} | 27 ++++++++++-- ...)_1_965ae67e6f11e3d04bb0403636e829f7.json} | 19 ++++++-- ...)_1_f197d9625d431cd2edeab6e1461b332f.json} | 19 ++++++-- .../__snapshots__/html/complex/misc.html | 2 +- .../__snapshots__/html/paragraph/empty.html | 2 +- .../html/paragraph/emptyBetween.html | 2 +- .../markdown/paragraph/emptyBetween.md | 2 +- 10 files changed, 130 insertions(+), 16 deletions(-) rename packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/{add a new paragraph (empty doc)_1_509f796014e0dae8a4a50e1aaab8a378.json => add a new paragraph (empty doc)_1_2263f232809497c4f6c0a975957dff6b.json} (70%) rename packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/{add a new paragraph (empty doc)_1_3f2fffcfa733575ca2a6c4f9d1a8f615.json => add a new paragraph (empty doc)_1_965ae67e6f11e3d04bb0403636e829f7.json} (63%) rename packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/{add a new paragraph (empty doc)_1_100f09592ece8a32c33dff2dc612c649.json => add a new paragraph (empty doc)_1_f197d9625d431cd2edeab6e1461b332f.json} (89%) diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index 0425af0c81..e2274140f7 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -15,6 +15,20 @@ import { } from "../../../nodeConversions/blockToNode.js"; import { nodeToCustomInlineContent } from "../../../nodeConversions/nodeToBlock.js"; +/** + * Placeholder character inserted into empty inline-content blocks when + * exporting to external HTML. An empty block serializes to an element with no + * children (e.g. `

`), which is dropped when the HTML is parsed back into + * blocks. Filling it with this character keeps the block alive through the + * round trip, and the parser strips the character again so the block ends up + * empty (see `HTMLToBlocks`). + * + * The Unicode object replacement character (U+FFFC) is used because it's a + * reserved placeholder that users don't type, so it can be safely removed on + * import without discarding legitimate content (unlike a non-breaking space). + */ +export const EMPTY_BLOCK_PLACEHOLDER = ""; + function addAttributesAndRemoveClasses(element: HTMLElement) { // Removes all BlockNote specific class names. const className = @@ -280,7 +294,8 @@ function serializeBlock< // Blocks with empty inline content (e.g. an empty paragraph) serialize to // an element with no children (e.g. `

`), which is ignored when the // HTML is parsed back into blocks. To make these blocks survive such a - // round trip, we fill their content with a non-breaking space. + // round trip, we fill their content with a placeholder character that the + // parser strips out again (see `EMPTY_BLOCK_PLACEHOLDER`). // // Only applies to blocks that hold inline content: containers (columns, // tables) fill their `contentDOM` with child blocks later on, and code @@ -291,7 +306,7 @@ function serializeBlock< !blockNodeType.spec.code && ret.contentDOM.childNodes.length === 0 ) { - ret.contentDOM.appendChild(doc.createTextNode(" ")); + ret.contentDOM.appendChild(doc.createTextNode(EMPTY_BLOCK_PLACEHOLDER)); } } diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index 2f73616dc0..98be8b38c6 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -8,11 +8,20 @@ import { StyleSchema, } from "../../../schema/index.js"; import { createExternalHTMLExporter } from "../html/externalHTMLExporter.js"; +import { EMPTY_BLOCK_PLACEHOLDER } from "../html/util/serializeBlocksExternalHTML.js"; import { htmlToMarkdown } from "./htmlToMarkdown.js"; // Needs to be sync because it's used in drag handler event (SideMenuPlugin) export function cleanHTMLToMarkdown(cleanHTMLString: string) { - return htmlToMarkdown(cleanHTMLString); + // The external HTML exporter fills empty inline-content blocks with a + // placeholder character so they survive an HTML round trip (see + // `EMPTY_BLOCK_PLACEHOLDER`). Markdown has no need for that placeholder, so we + // remove it to avoid it showing up as a stray character in the output. + const withoutPlaceholder = cleanHTMLString + .split(EMPTY_BLOCK_PLACEHOLDER) + .join(""); + + return htmlToMarkdown(withoutPlaceholder); } export function blocksToMarkdown< diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index 808dd20137..86243d5be9 100644 --- a/packages/core/src/api/parsers/html/parseHTML.ts +++ b/packages/core/src/api/parsers/html/parseHTML.ts @@ -6,10 +6,51 @@ import { } from "../../../schema/index.js"; import { Block } from "../../../blocks/defaultBlocks.js"; +import { EMPTY_BLOCK_PLACEHOLDER } from "../../exporters/html/util/serializeBlocksExternalHTML.js"; import { nodeToBlock } from "../../nodeConversions/nodeToBlock.js"; import { nestedListsToBlockNoteStructure } from "./util/nestedLists.js"; import { preprocessHTMLWhitespace } from "./util/normalizeWhitespace.js"; +/** + * Removes the placeholder character that the external HTML exporter inserts + * into empty inline-content blocks (see `EMPTY_BLOCK_PLACEHOLDER`). The + * placeholder keeps such blocks from being dropped while the HTML is parsed; + * stripping it here lets the block round trip back to genuinely empty content. + */ +function stripEmptyBlockPlaceholder(content: any[]): any[] { + const stripped: any[] = []; + + for (const item of content) { + if (item.type === "text") { + const text = item.text.split(EMPTY_BLOCK_PLACEHOLDER).join(""); + if (text.length > 0) { + stripped.push({ ...item, text }); + } + } else if (Array.isArray(item.content)) { + // Links and custom inline content that hold nested styled text. + stripped.push({ + ...item, + content: stripEmptyBlockPlaceholder(item.content), + }); + } else { + stripped.push(item); + } + } + + return stripped; +} + +function stripEmptyBlockPlaceholderFromBlocks(blocks: Block[]) { + for (const block of blocks) { + if (Array.isArray(block.content)) { + (block as any).content = stripEmptyBlockPlaceholder(block.content); + } + if (block.children.length > 0) { + stripEmptyBlockPlaceholderFromBlocks(block.children); + } + } +} + export function HTMLToBlocks< BSchema extends BlockSchema, I extends InlineContentSchema, @@ -33,5 +74,7 @@ export function HTMLToBlocks< blocks.push(nodeToBlock(parentNode.child(i), parentNode)); } + stripEmptyBlockPlaceholderFromBlocks(blocks); + return blocks; } diff --git a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_509f796014e0dae8a4a50e1aaab8a378.json b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_2263f232809497c4f6c0a975957dff6b.json similarity index 70% rename from packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_509f796014e0dae8a4a50e1aaab8a378.json rename to packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_2263f232809497c4f6c0a975957dff6b.json index aeede5f3b7..04e3f0f905 100644 --- a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_509f796014e0dae8a4a50e1aaab8a378.json +++ b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/anthropic.messages/claude-3-7-sonnet-latest (streaming)/add a new paragraph (empty doc)_1_2263f232809497c4f6c0a975957dff6b.json @@ -2,8 +2,29 @@ "request": { "method": "POST", "url": "https://api.anthropic.com/v1/messages", - "body": "{\"model\":\"claude-3-7-sonnet-latest\",\"max_tokens\":64000,\"system\":[{\"type\":\"text\",\"text\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"}],\"messages\":[{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.\"},{\"type\":\"text\",\"text\":\"[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\\\"},{\\\"cursor\\\":true}]\"}]},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"write a new paragraph with the text 'You look great today!'\"}]}],\"tools\":[{\"name\":\"applyDocumentOperations\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}],\"tool_choice\":{\"type\":\"any\"},\"stream\":true}", - "headers": [], + "body": "{\"model\":\"claude-3-7-sonnet-latest\",\"max_tokens\":64000,\"system\":[{\"type\":\"text\",\"text\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"}],\"messages\":[{\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.\"},{\"type\":\"text\",\"text\":\"[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\ufffc

\\\"},{\\\"cursor\\\":true}]\"}]},{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"write a new paragraph with the text 'You look great today!'\"}]}],\"tools\":[{\"name\":\"applyDocumentOperations\",\"input_schema\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}],\"tool_choice\":{\"type\":\"any\"},\"stream\":true}", + "headers": [ + [ + "anthropic-beta", + "fine-grained-tool-streaming-2025-05-14" + ], + [ + "anthropic-version", + "2023-06-01" + ], + [ + "content-type", + "application/json" + ], + [ + "user-agent", + "ai-sdk/anthropic/3.0.2 ai-sdk/provider-utils/4.0.2 runtime/browser" + ], + [ + "x-api-key", + "not-available-in-ci" + ] + ], "cookies": [] }, "response": { @@ -12,4 +33,4 @@ "body": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-3-7-sonnet-20250219\",\"id\":\"msg_016r1xY1xw9dBXbuUikPJbe6\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":1148,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":11,\"service_tier\":\"standard\"}}}\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_019x69kHjsPjYsLZ1suXT9QU\",\"name\":\"applyDocumentOperations\",\"input\":{}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"operations\\\": [\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\n {\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\n \\\"type\\\":\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\" \\\"update\\\",\\n \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"id\\\": \\\"ref\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"1$\\\",\\n \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"block\\\": \\\"You look great today\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"!

\\\"\\n \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"}\\n]\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":1148,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":82} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", "headers": [] } -} \ No newline at end of file +} diff --git a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_3f2fffcfa733575ca2a6c4f9d1a8f615.json b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_965ae67e6f11e3d04bb0403636e829f7.json similarity index 63% rename from packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_3f2fffcfa733575ca2a6c4f9d1a8f615.json rename to packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_965ae67e6f11e3d04bb0403636e829f7.json index cb1534cb08..64dcbd017f 100644 --- a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_3f2fffcfa733575ca2a6c4f9d1a8f615.json +++ b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/groq.chat/llama-3.3-70b-versatile (streaming)/add a new paragraph (empty doc)_1_965ae67e6f11e3d04bb0403636e829f7.json @@ -2,8 +2,21 @@ "request": { "method": "POST", "url": "https://api.groq.com/openai/v1/chat/completions", - "body": "{\"model\":\"llama-3.3-70b-versatile\",\"messages\":[{\"role\":\"system\",\"content\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"},{\"role\":\"assistant\",\"content\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\\\"},{\\\"cursor\\\":true}]\"},{\"role\":\"user\",\"content\":\"write a new paragraph with the text 'You look great today!'\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}}],\"tool_choice\":\"required\",\"stream\":true}", - "headers": [], + "body": "{\"model\":\"llama-3.3-70b-versatile\",\"messages\":[{\"role\":\"system\",\"content\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"},{\"role\":\"assistant\",\"content\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\ufffc

\\\"},{\\\"cursor\\\":true}]\"},{\"role\":\"user\",\"content\":\"write a new paragraph with the text 'You look great today!'\"}],\"tools\":[{\"type\":\"function\",\"function\":{\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}}],\"tool_choice\":\"required\",\"stream\":true}", + "headers": [ + [ + "authorization", + "Bearer not-available-in-ci" + ], + [ + "content-type", + "application/json" + ], + [ + "user-agent", + "ai-sdk/groq/3.0.2 ai-sdk/provider-utils/4.0.2 runtime/browser" + ] + ], "cookies": [] }, "response": { @@ -12,4 +25,4 @@ "body": "data: {\"id\":\"chatcmpl-82f7ef27-ca01-433d-a94c-90d790674b93\",\"object\":\"chat.completion.chunk\",\"created\":1767361375,\"model\":\"llama-3.3-70b-versatile\",\"system_fingerprint\":\"fp_f8b414701e\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":null},\"logprobs\":null,\"finish_reason\":null}],\"x_groq\":{\"id\":\"req_01kdzf4v5rfnsav2kd19rcrfz6\",\"seed\":2140344246}}\n\ndata: {\"id\":\"chatcmpl-82f7ef27-ca01-433d-a94c-90d790674b93\",\"object\":\"chat.completion.chunk\",\"created\":1767361375,\"model\":\"llama-3.3-70b-versatile\",\"system_fingerprint\":\"fp_f8b414701e\",\"choices\":[{\"index\":0,\"delta\":{\"tool_calls\":[{\"id\":\"khb558s13\",\"type\":\"function\",\"function\":{\"name\":\"applyDocumentOperations\",\"arguments\":\"{\\\"operations\\\":[{\\\"block\\\":\\\"\\\\u003cp\\\\u003eYou look great today!\\\\u003c/p\\\\u003e\\\",\\\"id\\\":\\\"ref1$\\\",\\\"type\\\":\\\"update\\\"}]}\"},\"index\":0}]},\"logprobs\":null,\"finish_reason\":null}]}\n\ndata: {\"id\":\"chatcmpl-82f7ef27-ca01-433d-a94c-90d790674b93\",\"object\":\"chat.completion.chunk\",\"created\":1767361375,\"model\":\"llama-3.3-70b-versatile\",\"system_fingerprint\":\"fp_f8b414701e\",\"choices\":[{\"index\":0,\"delta\":{},\"logprobs\":null,\"finish_reason\":\"tool_calls\"}],\"x_groq\":{\"id\":\"req_01kdzf4v5rfnsav2kd19rcrfz6\",\"usage\":{\"queue_time\":0.047394491,\"prompt_tokens\":850,\"prompt_time\":0.046081609,\"completion_tokens\":32,\"completion_time\":0.09129475,\"total_tokens\":882,\"total_time\":0.137376359}},\"usage\":{\"queue_time\":0.047394491,\"prompt_tokens\":850,\"prompt_time\":0.046081609,\"completion_tokens\":32,\"completion_time\":0.09129475,\"total_tokens\":882,\"total_time\":0.137376359}}\n\ndata: [DONE]\n\n", "headers": [] } -} \ No newline at end of file +} diff --git a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_100f09592ece8a32c33dff2dc612c649.json b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_f197d9625d431cd2edeab6e1461b332f.json similarity index 89% rename from packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_100f09592ece8a32c33dff2dc612c649.json rename to packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_f197d9625d431cd2edeab6e1461b332f.json index 58b410cc97..68bbf21dff 100644 --- a/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_100f09592ece8a32c33dff2dc612c649.json +++ b/packages/xl-ai/src/api/formats/html-blocks/__snapshots__/htmlBlocks.test.ts/Add/__msw_snapshots__/openai.responses/gpt-4o-2024-08-06 (streaming)/add a new paragraph (empty doc)_1_f197d9625d431cd2edeab6e1461b332f.json @@ -2,8 +2,21 @@ "request": { "method": "POST", "url": "https://api.openai.com/v1/responses", - "body": "{\"model\":\"gpt-4o-2024-08-06\",\"input\":[{\"role\":\"system\",\"content\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"},{\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\\\"},{\\\"cursor\\\":true}]\"}]},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"write a new paragraph with the text 'You look great today!'\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}],\"tool_choice\":\"required\",\"stream\":true}", - "headers": [], + "body": "{\"model\":\"gpt-4o-2024-08-06\",\"input\":[{\"role\":\"system\",\"content\":\"You're manipulating a text document using HTML blocks. \\nMake sure to follow the json schema provided. When referencing ids they MUST be EXACTLY the same (including the trailing $). \\nList items are 1 block with 1 list item each, so block content `
  • item1
` is valid, but `
  • item1
  • item2
` is invalid. We'll merge them automatically.\\nFor code blocks, you can use the `data-language` attribute on a block (wrapped with
) to specify the language.\\n\\nIf the user requests updates to the document, use the \\\"applyDocumentOperations\\\" tool to update the document.\\n---\\nIF there is no selection active in the latest state, first, determine what part of the document the user is talking about. You SHOULD probably take cursor info into account if needed.\\n  EXAMPLE: if user says \\\"below\\\" (without pointing to a specific part of the document) he / she probably indicates the block(s) after the cursor. \\n  EXAMPLE: If you want to insert content AT the cursor position (UNLESS indicated otherwise by the user), then you need `referenceId` to point to the block before the cursor with position `after` (or block below and `before`\\n---\\n \"},{\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"There is no active selection. This is the latest state of the document (ignore previous documents, you MUST issue operations against this latest version of the document). \\nThe cursor is BETWEEN two blocks as indicated by cursor: true.\\nBecause the document is empty, YOU MUST first update the empty block before adding new blocks.\"}]},{\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"[{\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

\ufffc

\\\"},{\\\"cursor\\\":true}]\"}]},{\"role\":\"user\",\"content\":[{\"type\":\"input_text\",\"text\":\"write a new paragraph with the text 'You look great today!'\"}]}],\"tools\":[{\"type\":\"function\",\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]}}],\"tool_choice\":\"required\",\"stream\":true}", + "headers": [ + [ + "authorization", + "Bearer not-available-in-ci" + ], + [ + "content-type", + "application/json" + ], + [ + "user-agent", + "ai-sdk/openai/3.0.2 ai-sdk/provider-utils/4.0.2 runtime/browser" + ] + ], "cookies": [] }, "response": { @@ -12,4 +25,4 @@ "body": "event: response.created\ndata: {\"type\":\"response.created\",\"sequence_number\":0,\"response\":{\"id\":\"resp_09fb538de8ef257d006957cb43a23c8197a0af82b58ef83d2c\",\"object\":\"response\",\"created_at\":1767361347,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-2024-08-06\",\"output\":[],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":null,\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}}}\n\nevent: response.in_progress\ndata: {\"type\":\"response.in_progress\",\"sequence_number\":1,\"response\":{\"id\":\"resp_09fb538de8ef257d006957cb43a23c8197a0af82b58ef83d2c\",\"object\":\"response\",\"created_at\":1767361347,\"status\":\"in_progress\",\"background\":false,\"completed_at\":null,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-2024-08-06\",\"output\":[],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"auto\",\"store\":true,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":null,\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\":{}}}\n\nevent: response.output_item.added\ndata: {\"type\":\"response.output_item.added\",\"sequence_number\":2,\"output_index\":0,\"item\":{\"id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"type\":\"function_call\",\"status\":\"in_progress\",\"arguments\":\"\",\"call_id\":\"call_QUjPke6rjKsntZkyRUmUcnA3\",\"name\":\"applyDocumentOperations\"}}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":3,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"{\\\"\",\"obfuscation\":\"aS3xdKHRBUOc9N\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":4,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"operations\",\"obfuscation\":\"WcqSbl\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":5,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\":[\",\"obfuscation\":\"DuRzGywwHgber\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":6,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"{\\\"\",\"obfuscation\":\"DbwB6sDiazPOsP\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":7,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"type\",\"obfuscation\":\"1gC70wKL4sCr\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":8,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\":\\\"\",\"obfuscation\":\"hcmK0SFqLkXe6\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":9,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"update\",\"obfuscation\":\"9J8qcUZ4rX\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":10,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\",\\\"\",\"obfuscation\":\"wqO7moJuHT7a2\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":11,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"id\",\"obfuscation\":\"qiNOSv7SF54UIG\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":12,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\":\\\"\",\"obfuscation\":\"Y7cglR5UZBuYk\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":13,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"ref\",\"obfuscation\":\"uxgZ5xLNEN5ON\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":14,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"1\",\"obfuscation\":\"5RGKDjte6kjyR8y\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":15,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"$\",\"obfuscation\":\"zh2iMhysH0xqrhF\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":16,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\",\\\"\",\"obfuscation\":\"FGeTYH6lFCZEF\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":17,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"block\",\"obfuscation\":\"PWIgAVM8KvX\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":18,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\":\\\"\",\"obfuscation\":\"xxR9beQFDFRKI\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":19,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"<\",\"obfuscation\":\"sLUveO94eZaeE5W\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":20,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"p\",\"obfuscation\":\"xkxOPo6jK7qjAlA\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":21,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\">You\",\"obfuscation\":\"oGyc4O6RpYq9\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":22,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\" look\",\"obfuscation\":\"oesTOJDwtde\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":23,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\" great\",\"obfuscation\":\"rqNFQSzcCy\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":24,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\" today\",\"obfuscation\":\"0h7iTFIA3M\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":25,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"!\",\"obfuscation\":\"Av4SiyCjLMhzttX\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":28,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"\\\"}\",\"obfuscation\":\"ejzrizOPxMsuIn\"}\n\nevent: response.function_call_arguments.delta\ndata: {\"type\":\"response.function_call_arguments.delta\",\"sequence_number\":29,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"delta\":\"]}\",\"obfuscation\":\"9SBifFIzi75p4E\"}\n\nevent: response.function_call_arguments.done\ndata: {\"type\":\"response.function_call_arguments.done\",\"sequence_number\":30,\"item_id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"output_index\":0,\"arguments\":\"{\\\"operations\\\":[{\\\"type\\\":\\\"update\\\",\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

You look great today!

\\\"}]}\"}\n\nevent: response.output_item.done\ndata: {\"type\":\"response.output_item.done\",\"sequence_number\":31,\"output_index\":0,\"item\":{\"id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"operations\\\":[{\\\"type\\\":\\\"update\\\",\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

You look great today!

\\\"}]}\",\"call_id\":\"call_QUjPke6rjKsntZkyRUmUcnA3\",\"name\":\"applyDocumentOperations\"}}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\",\"sequence_number\":32,\"response\":{\"id\":\"resp_09fb538de8ef257d006957cb43a23c8197a0af82b58ef83d2c\",\"object\":\"response\",\"created_at\":1767361347,\"status\":\"completed\",\"background\":false,\"completed_at\":1767361348,\"error\":null,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\":null,\"model\":\"gpt-4o-2024-08-06\",\"output\":[{\"id\":\"fc_09fb538de8ef257d006957cb43efe48197a17343e61f430f15\",\"type\":\"function_call\",\"status\":\"completed\",\"arguments\":\"{\\\"operations\\\":[{\\\"type\\\":\\\"update\\\",\\\"id\\\":\\\"ref1$\\\",\\\"block\\\":\\\"

You look great today!

\\\"}]}\",\"call_id\":\"call_QUjPke6rjKsntZkyRUmUcnA3\",\"name\":\"applyDocumentOperations\"}],\"parallel_tool_calls\":true,\"previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\":null,\"reasoning\":{\"effort\":null,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\",\"store\":true,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"required\",\"tools\":[{\"type\":\"function\",\"description\":null,\"name\":\"applyDocumentOperations\",\"parameters\":{\"type\":\"object\",\"properties\":{\"operations\":{\"type\":\"array\",\"items\":{\"anyOf\":[{\"type\":\"object\",\"description\":\"Update a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"update\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to update\"},\"block\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single HTML element)\"}},\"required\":[\"type\",\"id\",\"block\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Insert new blocks\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"add\"]},\"referenceId\":{\"type\":\"string\",\"description\":\"MUST be an id of a block in the document\"},\"position\":{\"type\":\"string\",\"enum\":[\"before\",\"after\"],\"description\":\"`after` to add blocks AFTER (below) the block with `referenceId`, `before` to add the block BEFORE (above)\"},\"blocks\":{\"items\":{\"type\":\"string\",\"description\":\"html of block (MUST be a single, VALID HTML element)\"},\"type\":\"array\"}},\"required\":[\"type\",\"referenceId\",\"position\",\"blocks\"],\"additionalProperties\":false},{\"type\":\"object\",\"description\":\"Delete a block\",\"properties\":{\"type\":{\"type\":\"string\",\"enum\":[\"delete\"]},\"id\":{\"type\":\"string\",\"description\":\"id of block to delete\"}},\"required\":[\"type\",\"id\"],\"additionalProperties\":false}]}}},\"additionalProperties\":false,\"required\":[\"operations\"]},\"strict\":true}],\"top_logprobs\":0,\"top_p\":1.0,\"truncation\":\"disabled\",\"usage\":{\"input_tokens\":572,\"input_tokens_details\":{\"cached_tokens\":0},\"output_tokens\":28,\"output_tokens_details\":{\"reasoning_tokens\":0},\"total_tokens\":600},\"user\":null,\"metadata\":{}}}\n\n", "headers": [] } -} \ No newline at end of file +} diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html index c3ddccdd39..921a895594 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/misc.html @@ -13,6 +13,6 @@

-

 

+

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html index 8b4abd40de..52705e4f81 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/empty.html @@ -1 +1 @@ -

 

\ No newline at end of file +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html index 59a36b2883..d7c238bb58 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/emptyBetween.html @@ -1,3 +1,3 @@

Before

-

 

+

After

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md index 5ab341feb0..4de6aa9b79 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/emptyBetween.md @@ -1,5 +1,5 @@ Before -  + After