From 6d555898b666648e0766ab4549a85ef4e98be582 Mon Sep 17 00:00:00 2001 From: Nimesh Nayaju Date: Thu, 28 Aug 2025 16:22:18 -0400 Subject: [PATCH 1/5] Implement node methods for managing AI copilots and knowledge source + Documentation (#2613) --- CHANGELOG.md | 19 + docs/pages/api-reference/liveblocks-node.mdx | 376 +++ docs/references/v2.openapi.json | 2349 ++++++++++++----- .../src/__tests__/client.test.ts | 823 +++++- packages/liveblocks-node/src/client.ts | 486 ++++ 5 files changed, 3402 insertions(+), 651 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 628abf384bf..3d79bb139cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ ## vNEXT (not yet published) +## v3.5.0 + +### `@liveblocks/node` + +- Add the following methods for managing AI copilots and knowledge source: + 1. `getAiCopilots` + 2. `createAiCopilot` + 3. `getAiCopilot` + 4. `updateAiCopilot` + 5. `deleteAiCopilot` + 6. `createWebKnowledgeSource` + 7. `createFileKnowledgeSource` + 8. `deleteFileKnowledgeSource` + 9. `deleteWebKnowledgeSource` + 10. `getKnowledgeSources` + 11. `getKnowledgeSource` + 12. `getFileKnowledgeSourceMarkdown` + 13. `getWebKnowledgeSourceLinks` + ## v3.4.2 ### `@liveblocks/react-ui` diff --git a/docs/pages/api-reference/liveblocks-node.mdx b/docs/pages/api-reference/liveblocks-node.mdx index 46053e5dae2..77149074ded 100644 --- a/docs/pages/api-reference/liveblocks-node.mdx +++ b/docs/pages/api-reference/liveblocks-node.mdx @@ -2400,6 +2400,382 @@ await liveblocks.deleteNotificationSettings({ }); ``` +### AI Copilots + +#### Liveblocks.getAiCopilots [#get-ai-copilots] + +Returns a paginated list of AI copilots. The copilots are returned sorted by +creation date, from newest to oldest. This is a wrapper around the +[Get AI Copilots API](/docs/api-reference/rest-api-endpoints#get-ai-copilots) +and returns the same response. + +```ts +const { data: copilots, nextCursor } = await liveblocks.getAiCopilots(); + +// A list of AI copilots +// [{ type: "copilot", id: "co_abc123...", name: "My Copilot", ... }, ...] +console.log(copilots); + +// A pagination cursor used for retrieving the next page of results with `startingAfter` +// "L3YyL3Jvb21z..." +console.log(nextCursor); +``` + +Pagination options are available to control the number of results returned. + +```ts +const { data: copilots, nextCursor } = await liveblocks.getAiCopilots({ + // Optional, the amount of copilots to load, between 1 and 100, defaults to 20 + limit: 20, + + // Optional, cursor used for pagination, use `nextCursor` from the previous page's response + startingAfter: "L3YyL3Jvb21z...", +}); +``` + +#### Liveblocks.createAiCopilot [#create-ai-copilot] + +Creates a new AI copilot with the given configuration. This is a wrapper around +the +[Create AI Copilot API](/docs/api-reference/rest-api-endpoints#create-ai-copilot) +and returns the same response. + +```ts +const copilot = await liveblocks.createAiCopilot({ + name: "My AI Assistant", + systemPrompt: "You are a helpful AI assistant for our team.", + provider: "openai", + providerModel: "gpt-4", + providerApiKey: "sk-...", // Your OpenAI API key +}); + +// { type: "copilot", id: "co_abc123...", name: "My AI Assistant", ... } +console.log(copilot); +``` + +The method supports various configuration options for different AI providers. + +```ts +const copilot = await liveblocks.createAiCopilot({ + // Required, the name of the copilot + name: "Documentation Helper", + + // Optional, a description of what the copilot does + description: "Helps users understand our documentation", + + // Required, the system prompt that defines the copilot's behavior + systemPrompt: + "You are an expert at helping users understand technical documentation.", + + // Optional, additional knowledge context for the copilot + knowledgePrompt: "Use our company's style guide when providing examples.", + + // Required, the AI provider to use + provider: "openai", + + // Required for standard providers, the model to use + providerModel: "gpt-4-turbo", + + // Required, your API key for the provider + providerApiKey: "sk-...", + + // Optional, provider-specific options + providerOptions: {}, + + // Optional, model settings + settings: { + maxTokens: 1000, + temperature: 0.7, + topP: 0.9, + frequencyPenalty: 0.1, + presencePenalty: 0.1, + stopSequences: ["END"], + seed: 42, + maxRetries: 3, + }, +}); +``` + +For OpenAI-compatible providers, use a different configuration: + +```ts +const copilot = await liveblocks.createAiCopilot({ + name: "Custom AI Helper", + systemPrompt: "You are a helpful assistant.", + provider: "openai-compatible", + compatibleProviderName: "my-custom-provider", + providerBaseUrl: "https://api.mycustomprovider.com/v1", + providerApiKey: "your-api-key-here", // Your API key for the custom provider + // Note: providerModel is not used with openai-compatible providers +}); +``` + +#### Liveblocks.getAiCopilot [#get-ai-copilot] + +Returns an AI copilot by its ID. Throws an error if the copilot isn't found. +This is a wrapper around the +[Get AI Copilot API](/docs/api-reference/rest-api-endpoints#get-ai-copilot) and +returns the same response. + +```ts +const copilot = await liveblocks.getAiCopilot("co_abc123..."); + +// { type: "copilot", id: "co_abc123...", name: "My AI Assistant", ... } +console.log(copilot); +``` + +#### Liveblocks.updateAiCopilot [#update-ai-copilot] + +Updates an existing AI copilot's configuration. You only need to pass the +properties you want to update. Throws an error if the copilot isn't found. This +is a wrapper around the +[Update AI Copilot API](/docs/api-reference/rest-api-endpoints#update-ai-copilot) +and returns the same response. + +```ts +const updatedCopilot = await liveblocks.updateAiCopilot("co_abc123...", { + name: "Updated AI Assistant", + description: "Now with improved capabilities", +}); + +// { type: "copilot", id: "co_abc123...", name: "Updated AI Assistant", ... } +console.log(updatedCopilot); +``` + +You can update various aspects of the copilot: + +```ts +const updatedCopilot = await liveblocks.updateAiCopilot("co_abc123...", { + // Optional, update the name + name: "Better AI Helper", + + // Optional, update the description + description: "Enhanced with new features", + + // Optional, update the system prompt + systemPrompt: "You are an even more helpful AI assistant.", + + // Optional, update the knowledge prompt + knowledgePrompt: "Reference our latest guidelines.", + + // Optional, update provider-specific options + providerOptions: {}, + + // Optional, update model settings + settings: { + temperature: 0.5, + maxTokens: 1500, + }, +}); +``` + +#### Liveblocks.deleteAiCopilot [#delete-ai-copilot] + +Deletes an AI copilot by its ID. A deleted copilot is no longer accessible and +cannot be restored. Throws an error if the copilot isn't found. This is a +wrapper around the +[Delete AI Copilot API](/docs/api-reference/rest-api-endpoints#delete-ai-copilot) +and returns no response. + +```ts +await liveblocks.deleteAiCopilot("co_abc123..."); +``` + +### Knowledge Sources + +#### Liveblocks.createWebKnowledgeSource [#create-web-knowledge-source] + +Creates a web knowledge source for an AI copilot. This allows the copilot to +access and learn from web content. This is a wrapper around the +[Create Web Knowledge Source API](/docs/api-reference/rest-api-endpoints#create-web-knowledge-source) +and returns the ID of the created knowledge source. + +```ts +const { id } = await liveblocks.createWebKnowledgeSource({ + copilotId: "co_abc123...", + url: "https://example.com/documentation", + type: "individual_link", +}); + +// "ks_def456..." +console.log(id); +``` + +Different types of web knowledge sources are supported: + +```ts +// Index a single web page +const singlePage = await liveblocks.createWebKnowledgeSource({ + copilotId: "co_abc123...", + url: "https://example.com/important-page", + type: "individual_link", +}); + +// Crawl an entire website +const crawledSite = await liveblocks.createWebKnowledgeSource({ + copilotId: "co_abc123...", + url: "https://example.com", + type: "crawl", +}); + +// Use a sitemap to index multiple pages +const sitemapSource = await liveblocks.createWebKnowledgeSource({ + copilotId: "co_abc123...", + url: "https://example.com/sitemap.xml", + type: "sitemap", +}); +``` + +#### Liveblocks.createFileKnowledgeSource [#create-file-knowledge-source] + +Creates a file knowledge source for an AI copilot by uploading a file. The +copilot can then reference the content of the file when responding. This is a +wrapper around the +[Create File Knowledge Source API](/docs/api-reference/rest-api-endpoints#create-file-knowledge-source) +and returns the ID of the created knowledge source. + +**Note:** Currently only PDF files (`application/pdf`) and images (`image/*`) +are supported. + +```ts +const { id } = await liveblocks.createFileKnowledgeSource({ + copilotId: "co_abc123...", + file: pdfFile, // Must be a PDF or image file +}); + +// "ks_ghi789..." +console.log(id); +``` + +#### Liveblocks.getKnowledgeSources [#get-knowledge-sources] + +Returns a paginated list of knowledge sources for a specific AI copilot. This is +a wrapper around the +[Get Knowledge Sources API](/docs/api-reference/rest-api-endpoints#get-knowledge-sources) +and returns the same response. + +```ts +const { data: sources, nextCursor } = await liveblocks.getKnowledgeSources({ + copilotId: "co_abc123...", +}); + +// [{ type: "ai-knowledge-web-source", id: "ks_abc123...", ... }, ...] +console.log(sources); +``` + +Pagination options are available: + +```ts +const { data: sources, nextCursor } = await liveblocks.getKnowledgeSources({ + copilotId: "co_abc123...", + // Optional, the amount of knowledge sources to load, between 1 and 100, defaults to 20 + limit: 20, + // Optional, cursor used for pagination + startingAfter: "L3YyL3Jvb21z...", +}); +``` + +#### Liveblocks.getKnowledgeSource [#get-knowledge-source] + +Returns a specific knowledge source by its ID. Throws an error if the knowledge +source isn't found. This is a wrapper around the +[Get Knowledge Source API](/docs/api-reference/rest-api-endpoints#get-knowledge-source) +and returns the same response. + +```ts +const source = await liveblocks.getKnowledgeSource({ + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", +}); + +// { type: "ai-knowledge-web-source", id: "ks_def456...", ... } +// or { type: "ai-knowledge-file-source", id: "ks_def456...", ... } +console.log(source); +``` + +#### Liveblocks.getFileKnowledgeSourceMarkdown [#get-file-knowledge-source-markdown] + +Returns the content of a file knowledge source as Markdown. This allows you to +see what content the AI copilot has access to from uploaded files. Throws an +error if the knowledge source isn't found. This is a wrapper around the +[Get File Knowledge Source Content API](/docs/api-reference/rest-api-endpoints#get-file-knowledge-source-content) +and returns the content as a string. + +```ts +const content = await liveblocks.getFileKnowledgeSourceMarkdown({ + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", +}); + +// "# Document Title\n\nThis is the content of the uploaded file..." +console.log(content); +``` + +#### Liveblocks.getWebKnowledgeSourceLinks [#get-web-knowledge-source-links] + +Returns a paginated list of links that were indexed from a web knowledge source. +This is useful for understanding what content the AI copilot has access to from +web sources. This is a wrapper around the +[Get Web Knowledge Source Links API](/docs/api-reference/rest-api-endpoints#get-web-knowledge-source-links) +and returns the same response. + +```ts +const { data: links, nextCursor } = await liveblocks.getWebKnowledgeSourceLinks( + { + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", + } +); + +// [{ id: "link_123...", url: "https://example.com/page1", status: "ready", ... }, ...] +console.log(links); +``` + +Pagination options are available: + +```ts +const { data: links, nextCursor } = await liveblocks.getWebKnowledgeSourceLinks( + { + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", + // Optional, the amount of links to load, between 1 and 100, defaults to 20 + limit: 20, + // Optional, cursor used for pagination + startingAfter: "L3YyL3Jvb21z...", + } +); +``` + +#### Liveblocks.deleteWebKnowledgeSource [#delete-web-knowledge-source] + +Deletes a web knowledge source from an AI copilot. The copilot will no longer +have access to the content from this source. Throws an error if the knowledge +source isn't found. This is a wrapper around the +[Delete Web Knowledge Source API](/docs/api-reference/rest-api-endpoints#delete-web-knowledge-source) +and returns no response. + +```ts +await liveblocks.deleteWebKnowledgeSource({ + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", +}); +``` + +#### Liveblocks.deleteFileKnowledgeSource [#delete-file-knowledge-source] + +Deletes a file knowledge source from an AI copilot. The copilot will no longer +have access to the content from this file. Throws an error if the knowledge +source isn't found. This is a wrapper around the +[Delete File Knowledge Source API](/docs/api-reference/rest-api-endpoints#delete-file-knowledge-source) +and returns no response. + +```ts +await liveblocks.deleteFileKnowledgeSource({ + copilotId: "co_abc123...", + knowledgeSourceId: "ks_def456...", +}); +``` + ### Error handling [#error-handling] Errors in our API methods, such as network failures, invalid arguments, or diff --git a/docs/references/v2.openapi.json b/docs/references/v2.openapi.json index bddc1d49f80..3200a86077e 100644 --- a/docs/references/v2.openapi.json +++ b/docs/references/v2.openapi.json @@ -3767,6 +3767,643 @@ } } } + }, + "/ai/copilots": { + "get": { + "summary": "Get AI copilots", + "description": "This endpoint returns a paginated list of AI copilots. The copilots are returned sorted by creation date, from newest to oldest. Corresponds to [`liveblocks.getAiCopilots`](/docs/api-reference/liveblocks-node#get-ai-copilots).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "description": "A limit on the number of copilots to be returned. The limit can range between 1 and 100, and defaults to 20." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "startingAfter", + "description": "A cursor used for pagination. Get the value from the `nextCursor` response of the previous page." + } + ], + "responses": { + "200": { + "description": "Success. Returns the list of AI copilots.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetAiCopilots" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + } + }, + "operationId": "get-ai-copilots" + }, + "post": { + "summary": "Create AI copilot", + "description": "This endpoint creates a new AI copilot with the given configuration. Corresponds to [`liveblocks.createAiCopilot`](/docs/api-reference/liveblocks-node#create-ai-copilot).", + "tags": ["AI"], + "responses": { + "200": { + "description": "Success. Returns the created AI copilot.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCopilot" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "create-ai-copilot", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAiCopilot" + } + } + } + } + } + }, + "/ai/copilots/{copilotId}": { + "get": { + "summary": "Get AI copilot", + "description": "This endpoint returns an AI copilot by its ID. Corresponds to [`liveblocks.getAiCopilot`](/docs/api-reference/liveblocks-node#get-ai-copilot).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + } + ], + "responses": { + "200": { + "description": "Success. Returns the AI copilot.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCopilot" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-ai-copilot" + }, + "post": { + "summary": "Update AI copilot", + "description": "This endpoint updates an existing AI copilot's configuration. Corresponds to [`liveblocks.updateAiCopilot`](/docs/api-reference/liveblocks-node#update-ai-copilot).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + } + ], + "responses": { + "200": { + "description": "Success. Returns the updated AI copilot.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AiCopilot" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "update-ai-copilot", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAiCopilot" + } + } + } + } + }, + "delete": { + "summary": "Delete AI copilot", + "description": "This endpoint deletes an AI copilot by its ID. A deleted copilot is no longer accessible and cannot be restored. Corresponds to [`liveblocks.deleteAiCopilot`](/docs/api-reference/liveblocks-node#delete-ai-copilot).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + } + ], + "responses": { + "204": { + "description": "Success. The AI copilot was deleted." + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "delete-ai-copilot" + } + }, + "/ai/copilots/{copilotId}/knowledge": { + "get": { + "summary": "Get knowledge sources", + "description": "This endpoint returns a paginated list of knowledge sources for a specific AI copilot. Corresponds to [`liveblocks.getKnowledgeSources`](/docs/api-reference/liveblocks-node#get-knowledge-sources).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "description": "A limit on the number of knowledge sources to be returned. The limit can range between 1 and 100, and defaults to 20." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "startingAfter", + "description": "A cursor used for pagination. Get the value from the `nextCursor` response of the previous page." + } + ], + "responses": { + "200": { + "description": "Success. Returns the list of knowledge sources.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetKnowledgeSources" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-knowledge-sources" + } + }, + "/ai/copilots/{copilotId}/knowledge/{knowledgeSourceId}": { + "get": { + "summary": "Get knowledge source", + "description": "This endpoint returns a specific knowledge source by its ID. Corresponds to [`liveblocks.getKnowledgeSource`](/docs/api-reference/liveblocks-node#get-knowledge-source).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "knowledgeSourceId", + "in": "path", + "required": true, + "description": "ID of the knowledge source" + } + ], + "responses": { + "200": { + "description": "Success. Returns the knowledge source.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/KnowledgeSource" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-knowledge-source" + } + }, + "/ai/copilots/{copilotId}/knowledge/web": { + "post": { + "summary": "Create web knowledge source", + "description": "This endpoint creates a web knowledge source for an AI copilot. This allows the copilot to access and learn from web content. Corresponds to [`liveblocks.createWebKnowledgeSource`](/docs/api-reference/liveblocks-node#create-web-knowledge-source).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + } + ], + "responses": { + "200": { + "description": "Success. Returns the ID of the created knowledge source.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "create-web-knowledge-source", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateWebKnowledgeSource" + } + } + } + } + } + }, + "/ai/copilots/{copilotId}/knowledge/file/{name}": { + "put": { + "summary": "Create file knowledge source", + "description": "This endpoint creates a file knowledge source for an AI copilot by uploading a file. The copilot can then reference the content of the file when responding. Corresponds to [`liveblocks.createFileKnowledgeSource`](/docs/api-reference/liveblocks-node#create-file-knowledge-source).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "name", + "in": "path", + "required": true, + "description": "Name of the file" + } + ], + "responses": { + "200": { + "description": "Success. Returns the ID of the created knowledge source.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "422": { + "$ref": "#/components/responses/422" + } + }, + "operationId": "create-file-knowledge-source", + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "/ai/copilots/{copilotId}/knowledge/file/{knowledgeSourceId}": { + "get": { + "summary": "Get file knowledge source content", + "description": "This endpoint returns the content of a file knowledge source as Markdown. This allows you to see what content the AI copilot has access to from uploaded files. Corresponds to [`liveblocks.getFileKnowledgeSourceMarkdown`](/docs/api-reference/liveblocks-node#get-file-knowledge-source-markdown).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "knowledgeSourceId", + "in": "path", + "required": true, + "description": "ID of the knowledge source" + } + ], + "responses": { + "200": { + "description": "Success. Returns the content of the file knowledge source.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "content": { + "type": "string" + } + } + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-file-knowledge-source-content" + }, + "delete": { + "summary": "Delete file knowledge source", + "description": "This endpoint deletes a file knowledge source from an AI copilot. The copilot will no longer have access to the content from this file. Corresponds to [`liveblocks.deleteFileKnowledgeSource`](/docs/api-reference/liveblocks-node#delete-file-knowledge-source).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "knowledgeSourceId", + "in": "path", + "required": true, + "description": "ID of the knowledge source" + } + ], + "responses": { + "204": { + "description": "Success. The file knowledge source was deleted." + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "delete-file-knowledge-source" + } + }, + "/ai/copilots/{copilotId}/knowledge/web/{knowledgeSourceId}": { + "delete": { + "summary": "Delete web knowledge source", + "description": "This endpoint deletes a web knowledge source from an AI copilot. The copilot will no longer have access to the content from this source. Corresponds to [`liveblocks.deleteWebKnowledgeSource`](/docs/api-reference/liveblocks-node#delete-web-knowledge-source).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "knowledgeSourceId", + "in": "path", + "required": true, + "description": "ID of the knowledge source" + } + ], + "responses": { + "204": { + "description": "Success. The web knowledge source was deleted." + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "delete-web-knowledge-source" + } + }, + "/ai/copilots/{copilotId}/knowledge/web/{knowledgeSourceId}/links": { + "get": { + "summary": "Get web knowledge source links", + "description": "This endpoint returns a paginated list of links that were indexed from a web knowledge source. This is useful for understanding what content the AI copilot has access to from web sources. Corresponds to [`liveblocks.getWebKnowledgeSourceLinks`](/docs/api-reference/liveblocks-node#get-web-knowledge-source-links).", + "tags": ["AI"], + "parameters": [ + { + "schema": { + "type": "string" + }, + "name": "copilotId", + "in": "path", + "required": true, + "description": "ID of the AI copilot" + }, + { + "schema": { + "type": "string" + }, + "name": "knowledgeSourceId", + "in": "path", + "required": true, + "description": "ID of the knowledge source" + }, + { + "schema": { + "type": "number", + "minimum": 1, + "maximum": 100, + "default": 20 + }, + "in": "query", + "name": "limit", + "description": "A limit on the number of links to be returned. The limit can range between 1 and 100, and defaults to 20." + }, + { + "schema": { + "type": "string" + }, + "in": "query", + "name": "startingAfter", + "description": "A cursor used for pagination. Get the value from the `nextCursor` response of the previous page." + } + ], + "responses": { + "200": { + "description": "Success. Returns the list of web knowledge source links.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GetWebKnowledgeSourceLinks" + } + } + } + }, + "401": { + "$ref": "#/components/responses/401" + }, + "403": { + "$ref": "#/components/responses/403" + }, + "404": { + "$ref": "#/components/responses/404" + } + }, + "operationId": "get-web-knowledge-source-links" + } } }, "components": { @@ -3778,923 +4415,1338 @@ "id": { "type": "string" }, - "type": { - "type": "string", - "enum": ["room"] + "type": { + "type": "string", + "enum": ["room"] + }, + "lastConnectionAt": { + "type": "string", + "format": "date-time" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "defaultAccesses": { + "type": ["string", "array"], + "uniqueItems": true, + "items": {} + }, + "usersAccesses": { + "type": "object" + }, + "groupsAccesses": { + "type": "object" + } + } + }, + "UpdateRoom": { + "type": "object", + "title": "UpdateRoom", + "additionalProperties": false, + "properties": { + "defaultAccesses": { + "type": ["array", "null"], + "items": { + "type": "string" + } + }, + "usersAccesses": { + "type": ["object", "null"], + "description": "A map of user identifiers to permissions list. Setting the value as `null` will clear all users’ accesses. Setting one user identifier as `null` will clear this user’s accesses." + }, + "groupsAccesses": { + "type": ["object", "null"], + "description": "A map of group identifiers to permissions list. Setting the value as `null` will clear all groups’ accesses. Setting one group identifier as `null` will clear this group’s accesses." + }, + "metadata": { + "type": ["object", "null"], + "description": "A map of metadata keys to their values (`string` or `string[]`). Setting the value as `null` will clear all metadata. Setting a key as `null` will clear the key." + } + } + }, + "UpsertRoom": { + "type": "object", + "title": "UpsertRoom", + "additionalProperties": false, + "properties": { + "update": { + "type": "object", + "properties": { + "defaultAccesses": { + "type": ["array", "null"], + "items": { + "type": "string" + } + }, + "usersAccesses": { + "type": ["object", "null"], + "description": "A map of user identifiers to permissions list. Setting the value as `null` will clear all users’ accesses. Setting one user identifier as `null` will clear this user’s accesses." + }, + "groupsAccesses": { + "type": ["object", "null"], + "description": "A map of group identifiers to permissions list. Setting the value as `null` will clear all groups’ accesses. Setting one group identifier as `null` will clear this group’s accesses." + }, + "metadata": { + "type": ["object", "null"], + "description": "A map of metadata keys to their values (`string` or `string[]`). Setting the value as `null` will clear all metadata. Setting a key as `null` will clear the key." + } + } + }, + "create": { + "type": "object", + "properties": { + "defaultAccesses": { + "type": ["array"], + "items": { + "type": "string" + } + }, + "usersAccesses": { + "type": ["object"], + "description": "A map of user identifiers to permissions list." + }, + "groupsAccesses": { + "type": ["object"], + "description": "A map of group identifiers to permissions list." + }, + "metadata": { + "type": ["object"], + "description": "A map of metadata keys to their values (`string` or `string[]`)." + } + }, + "required": ["defaultAccesses"] + } + }, + "required": ["update"] + }, + "CreateRoom": { + "title": "CreateRoom", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "defaultAccesses": { + "type": "array", + "items": { + "type": "string" + } + }, + "usersAccesses": { + "type": "object" + }, + "groupsAccesses": { + "type": "object" + }, + "metadata": { + "type": "object" + } + }, + "required": ["id", "defaultAccesses"] + }, + "Error": { + "title": "Error", + "type": "object", + "properties": { + "error": { + "type": "string", + "description": "Error code" + }, + "message": { + "type": "string", + "description": "Message explaining the error" + }, + "suggestion": { + "type": "string", + "description": "A suggestion on how to fix the error" + }, + "docs": { + "type": "string", + "description": "A link to the documentation" + } + } + }, + "Authorization": { + "title": "Authorization", + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, + "TokenResponse": { + "title": "An HTTP response body containing a token.", + "type": "object", + "properties": { + "token": { + "type": "string" + } + } + }, + "AuthorizeUserRequest": { + "title": "AuthorizeUserRequest", + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "userInfo": { + "type": "object" + }, + "permissions": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "IdentifyUserRequest": { + "title": "IdentifyUserRequest", + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "groupIds": { + "type": "array", + "items": { + "type": "string" + } + }, + "userInfo": { + "type": "object" + } + } + }, + "CreateAuthorization": { + "title": "CreateAuthorization", + "type": "object", + "properties": { + "userId": { + "type": "string" + }, + "userInfo": { + "type": "object" + }, + "groupIds": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "GetRooms": { + "title": "GetRooms", + "type": "object", + "properties": { + "nextCursor": { + "type": "string" + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "lastConnectionAt": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "metadata": { + "type": "object" + }, + "defaultAccesses": { + "type": ["string", "array"], + "items": {} + }, + "usersAccesses": { + "type": "object" + }, + "groupsAccesses": { + "type": "object" + } + } + } + } + } + }, + "ActiveUsersResponse": { + "title": "ActiveUsersResponse", + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "connectionId": { + "type": "number" + } + } + } + } + } + }, + "PublicAuthorizeBodyRequest": { + "title": "PublicAuthorizeBodyRequest", + "type": "object", + "properties": { + "publicApiKey": { + "type": "string" + } + } + }, + "SchemaResponse": { + "title": "Schema", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" }, - "lastConnectionAt": { - "type": "string", - "format": "date-time" + "version": { + "type": "number" }, "createdAt": { "type": "string", "format": "date-time" }, - "metadata": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "defaultAccesses": { - "type": ["string", "array"], - "uniqueItems": true, - "items": {} + "updatedAt": { + "type": "string", + "format": "date-time" }, - "usersAccesses": { - "type": "object" + "body": { + "type": "string" + } + } + }, + "SchemaRequest": { + "title": "SchemaRequest", + "type": "object", + "properties": { + "name": { + "type": "string" }, - "groupsAccesses": { - "type": "object" + "body": { + "type": "string" } } }, - "UpdateRoom": { + "UpdateSchema": { + "title": "UpdateSchema", "type": "object", - "title": "UpdateRoom", - "additionalProperties": false, "properties": { - "defaultAccesses": { - "type": ["array", "null"], + "body": { + "type": "string" + } + } + }, + "AttachSchema": { + "title": "AttachSchema", + "type": "object", + "properties": { + "schema": { + "type": "string" + } + } + }, + "Thread": { + "type": "object", + "title": "Thread", + "properties": { + "type": { + "const": "thread" + }, + "id": { + "type": "string" + }, + "roomId": { + "type": "string" + }, + "comments": { + "type": "array", "items": { - "type": "string" + "$ref": "#/components/schemas/Comment" } }, - "usersAccesses": { - "type": ["object", "null"], - "description": "A map of user identifiers to permissions list. Setting the value as `null` will clear all users’ accesses. Setting one user identifier as `null` will clear this user’s accesses." - }, - "groupsAccesses": { - "type": ["object", "null"], - "description": "A map of group identifiers to permissions list. Setting the value as `null` will clear all groups’ accesses. Setting one group identifier as `null` will clear this group’s accesses." + "createdAt": { + "type": "string", + "format": "date-time" }, "metadata": { - "type": ["object", "null"], - "description": "A map of metadata keys to their values (`string` or `string[]`). Setting the value as `null` will clear all metadata. Setting a key as `null` will clear the key." + "type": "object" + }, + "resolved": { + "type": "boolean" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } - } + }, + "required": [ + "type", + "id", + "roomId", + "comments", + "createdAt", + "metadata" + ], + "examples": [ + { + "type": "thread", + "id": "thread-id", + "roomId": "room-id", + "comments": [ + { + "type": "comment", + "threadId": "thread-id", + "roomId": "room-id", + "id": "comment-id", + "userId": "string", + "createdAt": "2019-08-24T14:15:22Z", + "editedAt": "2019-08-24T14:15:22Z", + "deletedAt": "2019-08-24T14:15:22Z", + "body": {} + } + ], + "createdAt": "2019-08-24T14:15:22Z", + "metadata": {}, + "updatedAt": "2019-08-24T14:15:22Z" + } + ], + "description": "" }, - "UpsertRoom": { + "CreateThread": { + "title": "CreateThread", "type": "object", - "title": "UpsertRoom", - "additionalProperties": false, "properties": { - "update": { + "comment": { "type": "object", "properties": { - "defaultAccesses": { - "type": ["array", "null"], - "items": { - "type": "string" - } - }, - "usersAccesses": { - "type": ["object", "null"], - "description": "A map of user identifiers to permissions list. Setting the value as `null` will clear all users’ accesses. Setting one user identifier as `null` will clear this user’s accesses." + "userId": { + "type": "string" }, - "groupsAccesses": { - "type": ["object", "null"], - "description": "A map of group identifiers to permissions list. Setting the value as `null` will clear all groups’ accesses. Setting one group identifier as `null` will clear this group’s accesses." + "createdAt": { + "type": "string", + "format": "date-time" }, - "metadata": { - "type": ["object", "null"], - "description": "A map of metadata keys to their values (`string` or `string[]`). Setting the value as `null` will clear all metadata. Setting a key as `null` will clear the key." + "body": { + "type": "object", + "properties": { + "version": { + "type": "number" + }, + "content": { + "type": "array", + "items": { + "type": "object" + } + } + } } - } + }, + "required": ["userId", "body"] }, - "create": { - "type": "object", - "properties": { - "defaultAccesses": { - "type": ["array"], - "items": { - "type": "string" - } - }, - "usersAccesses": { - "type": ["object"], - "description": "A map of user identifiers to permissions list." - }, - "groupsAccesses": { - "type": ["object"], - "description": "A map of group identifiers to permissions list." - }, - "metadata": { - "type": ["object"], - "description": "A map of metadata keys to their values (`string` or `string[]`)." + "metadata": { + "type": "object" + } + }, + "required": ["comment"], + "examples": [ + { + "comment": { + "userId": "alice", + "createdAt": "2022-07-13T14:32:50.697Z", + "body": { + "version": 1, + "content": [] } }, - "required": ["defaultAccesses"] + "metadata": { + "color": "blue" + } + } + ], + "description": "" + }, + "UpdateThread": { + "title": "UpdateThread", + "type": "object", + "properties": { + "metadata": { + "type": "object" + }, + "userId": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" } }, - "required": ["update"] + "required": ["metadata", "userId"], + "examples": [ + { + "metadata": { + "color": "blue" + }, + "userId": "alice", + "createdAt": "2023-07-13T14:32:50.697Z" + } + ], + "description": "" + }, + "ThreadMetadata": { + "type": "object", + "title": "ThreadMetadata", + "properties": { + "type": "object" + }, + "required": [], + "examples": [ + { + "color": "blue", + "age": 25 + } + ], + "description": "" }, - "CreateRoom": { - "title": "CreateRoom", + "Comment": { "type": "object", + "title": "Comment", "properties": { - "id": { + "type": { + "const": "comment", + "readOnly": true, + "default": "comment", + "examples": ["comment"] + }, + "threadId": { "type": "string" }, - "defaultAccesses": { - "type": "array", - "items": { - "type": "string" - } + "roomId": { + "type": "string" }, - "usersAccesses": { - "type": "object" + "id": { + "type": "string" }, - "groupsAccesses": { - "type": "object" + "userId": { + "type": "string" }, - "metadata": { - "type": "object" - } - }, - "required": ["id", "defaultAccesses"] - }, - "Error": { - "title": "Error", - "type": "object", - "properties": { - "error": { + "createdAt": { "type": "string", - "description": "Error code" + "format": "date-time" }, - "message": { + "editedAt": { "type": "string", - "description": "Message explaining the error" + "format": "date-time" }, - "suggestion": { + "deletedAt": { "type": "string", - "description": "A suggestion on how to fix the error" + "format": "date-time" }, - "docs": { - "type": "string", - "description": "A link to the documentation" - } - } - }, - "Authorization": { - "title": "Authorization", - "type": "object", - "properties": { - "token": { - "type": "string" + "body": { + "type": "object" } - } - }, - "TokenResponse": { - "title": "An HTTP response body containing a token.", - "type": "object", - "properties": { - "token": { - "type": "string" + }, + "required": ["type", "threadId", "roomId", "id", "userId", "createdAt"], + "examples": [ + { + "type": "comment", + "threadId": "thread-id", + "roomId": "room-id", + "id": "comment-id", + "userId": "string", + "createdAt": "2019-08-24T14:15:22Z", + "editedAt": "2019-08-24T14:15:22Z", + "deletedAt": "2019-08-24T14:15:22Z", + "body": {} } - } + ] }, - "AuthorizeUserRequest": { - "title": "AuthorizeUserRequest", + "CreateComment": { + "title": "CreateComment", "type": "object", "properties": { "userId": { "type": "string" }, - "userInfo": { - "type": "object" + "createdAt": { + "type": "string", + "format": "date-time" }, - "permissions": { + "body": { "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" + "properties": { + "version": { + "type": "number" + }, + "content": { + "type": "array", + "items": { + "type": "object" + } } } } - } + }, + "required": ["userId", "body"], + "examples": [ + { + "userId": "alice", + "createdAt": "2022-07-13T14:32:50.697Z", + "body": { + "version": 1, + "content": [] + } + } + ], + "description": "" }, - "IdentifyUserRequest": { - "title": "IdentifyUserRequest", + "UpdateComment": { + "title": "UpdateComment", "type": "object", "properties": { - "userId": { - "type": "string" + "editedAt": { + "type": "string", + "format": "date-time" }, - "groupIds": { - "type": "array", - "items": { - "type": "string" + "body": { + "type": "object", + "properties": { + "version": { + "type": "number" + }, + "content": { + "type": "array", + "items": { + "type": "object" + } + } } - }, - "userInfo": { - "type": "object" } - } + }, + "required": ["body"], + "examples": [ + { + "editedAt": "2022-07-13T14:32:50.697Z", + "body": { + "version": 1, + "content": [] + } + } + ], + "description": "" }, - "CreateAuthorization": { - "title": "CreateAuthorization", + "CommentReaction": { "type": "object", + "title": "CommentReaction", "properties": { "userId": { "type": "string" }, - "userInfo": { - "type": "object" + "createdAt": { + "type": "string", + "format": "date-time" }, - "groupIds": { - "type": "array", - "items": { - "type": "string" - } + "emoji": { + "type": "string" } - } + }, + "required": ["userId", "emoji"], + "examples": [ + { + "emoji": "👨‍👩‍👧", + "createdAt": "2022-07-13T14:32:50.697Z", + "userId": "alice" + } + ] }, - "GetRooms": { - "title": "GetRooms", + "AddCommentReaction": { "type": "object", + "title": "AddCommentReaction", "properties": { - "nextCursor": { + "userId": { "type": "string" }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string" - }, - "lastConnectionAt": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "metadata": { - "type": "object" - }, - "defaultAccesses": { - "type": ["string", "array"], - "items": {} - }, - "usersAccesses": { - "type": "object" - }, - "groupsAccesses": { - "type": "object" - } - } - } - } - } - }, - "ActiveUsersResponse": { - "title": "ActiveUsersResponse", - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string" - }, - "connectionId": { - "type": "number" - } - } - } + "createdAt": { + "type": "string", + "format": "date-time" + }, + "emoji": { + "type": "string" } - } + }, + "required": ["userId", "emoji"], + "examples": [ + { + "emoji": "👨‍👩‍👧", + "createdAt": "2022-07-13T14:32:50.697Z", + "userId": "alice" + } + ] }, - "PublicAuthorizeBodyRequest": { - "title": "PublicAuthorizeBodyRequest", + "RemoveCommentReaction": { "type": "object", + "title": "RemoveCommentReaction", "properties": { - "publicApiKey": { + "userId": { + "type": "string" + }, + "removedAt": { + "type": "string", + "format": "date-time" + }, + "emoji": { "type": "string" } - } + }, + "required": ["userId", "emoji"], + "examples": [ + { + "emoji": "👨‍👩‍👧", + "removedAt": "2022-07-13T14:32:50.697Z", + "userId": "alice" + } + ] }, - "SchemaResponse": { - "title": "Schema", + "InboxNotificationThreadData": { + "title": "InboxNotificationThreadData", "type": "object", "properties": { "id": { "type": "string" }, - "name": { + "kind": { "type": "string" }, - "version": { - "type": "number" + "threadId": { + "type": "string" }, - "createdAt": { + "roomId": { + "type": "string" + }, + "readAt": { "type": "string", "format": "date-time" }, - "updatedAt": { + "notifiedAt": { "type": "string", "format": "date-time" - }, - "body": { - "type": "string" } } }, - "SchemaRequest": { - "title": "SchemaRequest", + "InboxNotificationCustomData": { + "title": "InboxNotificationCustomData", "type": "object", "properties": { - "name": { + "id": { "type": "string" }, - "body": { - "type": "string" - } - } - }, - "UpdateSchema": { - "title": "UpdateSchema", - "type": "object", - "properties": { - "body": { - "type": "string" - } - } - }, - "AttachSchema": { - "title": "AttachSchema", - "type": "object", - "properties": { - "schema": { + "kind": { "type": "string" - } - } - }, - "Thread": { - "type": "object", - "title": "Thread", - "properties": { - "type": { - "const": "thread" }, - "id": { + "subjectId": { "type": "string" }, "roomId": { "type": "string" }, - "comments": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Comment" - } + "readAt": { + "type": "string", + "format": "date-time" }, - "createdAt": { + "notifiedAt": { "type": "string", "format": "date-time" }, - "metadata": { + "activityData": { "type": "object" - }, - "resolved": { + } + } + }, + "NotificationChannelSettings": { + "type": "object", + "properties": { + "thread": { "type": "boolean" }, - "updatedAt": { - "type": "string", - "format": "date-time" + "textMention": { + "type": "boolean" } }, - "required": [ - "type", - "id", - "roomId", - "comments", - "createdAt", - "metadata" - ], - "examples": [ - { - "type": "thread", - "id": "thread-id", - "roomId": "room-id", - "comments": [ - { - "type": "comment", - "threadId": "thread-id", - "roomId": "room-id", - "id": "comment-id", - "userId": "string", - "createdAt": "2019-08-24T14:15:22Z", - "editedAt": "2019-08-24T14:15:22Z", - "deletedAt": "2019-08-24T14:15:22Z", - "body": {} - } - ], - "createdAt": "2019-08-24T14:15:22Z", - "metadata": {}, - "updatedAt": "2019-08-24T14:15:22Z" - } - ], - "description": "" + "additionalProperties": { + "type": "boolean", + "description": "Custom notification kinds prefixed by a '$'" + } }, - "CreateThread": { - "title": "CreateThread", + "NotificationSettings": { "type": "object", "properties": { - "comment": { - "type": "object", - "properties": { - "userId": { - "type": "string" - }, - "createdAt": { - "type": "string", - "format": "date-time" - }, - "body": { - "type": "object", - "properties": { - "version": { - "type": "number" - }, - "content": { - "type": "array", - "items": { - "type": "object" - } - } - } - } - }, - "required": ["userId", "body"] + "email": { + "$ref": "#/components/schemas/NotificationChannelSettings" }, - "metadata": { - "type": "object" + "slack": { + "$ref": "#/components/schemas/NotificationChannelSettings" + }, + "teams": { + "$ref": "#/components/schemas/NotificationChannelSettings" + }, + "webPush": { + "$ref": "#/components/schemas/NotificationChannelSettings" } }, - "required": ["comment"], - "examples": [ - { - "comment": { - "userId": "alice", - "createdAt": "2022-07-13T14:32:50.697Z", - "body": { - "version": 1, - "content": [] - } - }, - "metadata": { - "color": "blue" - } - } - ], - "description": "" + "description": "Notification settings for each supported channel" }, - "UpdateThread": { - "title": "UpdateThread", + "PartialNotificationSettings": { "type": "object", "properties": { - "metadata": { - "type": "object" + "email": { + "$ref": "#/components/schemas/NotificationChannelSettings" }, - "userId": { - "type": "string" + "slack": { + "$ref": "#/components/schemas/NotificationChannelSettings" }, - "updatedAt": { - "type": "string", - "format": "date-time" + "teams": { + "$ref": "#/components/schemas/NotificationChannelSettings" + }, + "webPush": { + "$ref": "#/components/schemas/NotificationChannelSettings" } }, - "required": ["metadata", "userId"], - "examples": [ - { - "metadata": { - "color": "blue" - }, - "userId": "alice", - "createdAt": "2023-07-13T14:32:50.697Z" - } - ], - "description": "" + "description": "Partial notification settings - all properties are optional" }, - "ThreadMetadata": { + "RoomSubscriptionSettings": { + "title": "RoomSubscriptionSettings", "type": "object", - "title": "ThreadMetadata", "properties": { - "type": "object" - }, - "required": [], - "examples": [ - { - "color": "blue", - "age": 25 + "threads": { + "enum": ["all", "replies_and_mentions", "none"] + }, + "textMentions": { + "enum": ["mine", "none"] } - ], - "description": "" + } }, - "Comment": { + "UserRoomSubscriptionSettings": { + "title": "UserRoomSubscriptionSettings", "type": "object", - "title": "Comment", "properties": { - "type": { - "const": "comment", - "readOnly": true, - "default": "comment", - "examples": ["comment"] + "threads": { + "enum": ["all", "replies_and_mentions", "none"] }, - "threadId": { - "type": "string" + "textMentions": { + "enum": ["mine", "none"] }, "roomId": { "type": "string" - }, - "id": { - "type": "string" - }, + } + } + }, + "TriggerInboxNotification": { + "title": "TriggerInboxNotification", + "type": "object", + "properties": { "userId": { "type": "string" }, - "createdAt": { - "type": "string", - "format": "date-time" + "kind": { + "type": "string" }, - "editedAt": { - "type": "string", - "format": "date-time" + "subjectId": { + "type": "string" }, - "deletedAt": { - "type": "string", - "format": "date-time" + "roomId": { + "type": "string" }, - "body": { + "activityData": { "type": "object" } }, - "required": ["type", "threadId", "roomId", "id", "userId", "createdAt"], + "required": ["userId", "kind", "subjectId", "activityData"], "examples": [ { - "type": "comment", - "threadId": "thread-id", - "roomId": "room-id", - "id": "comment-id", - "userId": "string", - "createdAt": "2019-08-24T14:15:22Z", - "editedAt": "2019-08-24T14:15:22Z", - "deletedAt": "2019-08-24T14:15:22Z", - "body": {} + "userId": "alice", + "kind": "file-uploaded", + "subjectId": "file123", + "activityData": { + "url": "url-to-file" + } } ] }, - "CreateComment": { - "title": "CreateComment", + "Subscription": { + "title": "Subscription", "type": "object", "properties": { - "userId": { + "kind": { + "type": "string" + }, + "subjectId": { "type": "string" }, "createdAt": { "type": "string", "format": "date-time" + } + } + }, + "UserSubscription": { + "title": "UserSubscription", + "allOf": [ + { + "$ref": "#/components/schemas/Subscription" }, - "body": { + { "type": "object", "properties": { - "version": { - "type": "number" - }, - "content": { - "type": "array", - "items": { - "type": "object" - } + "userId": { + "type": "string" } } } - }, - "required": ["userId", "body"], - "examples": [ - { - "userId": "alice", - "createdAt": "2022-07-13T14:32:50.697Z", - "body": { - "version": 1, - "content": [] - } - } - ], - "description": "" + ] }, - "UpdateComment": { - "title": "UpdateComment", + "AiCopilot": { + "title": "AiCopilot", "type": "object", "properties": { - "editedAt": { + "type": { + "const": "copilot" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "systemPrompt": { + "type": "string" + }, + "knowledgePrompt": { + "type": "string" + }, + "provider": { + "type": "string", + "enum": ["openai", "anthropic", "google", "openai-compatible"] + }, + "providerModel": { + "type": "string" + }, + "compatibleProviderName": { + "type": "string" + }, + "providerBaseUrl": { + "type": "string" + }, + "createdAt": { "type": "string", "format": "date-time" }, - "body": { + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "lastUsedAt": { + "type": "string", + "format": "date-time" + }, + "providerOptions": { + "type": "object" + }, + "settings": { "type": "object", "properties": { - "version": { + "maxTokens": { "type": "number" }, - "content": { + "temperature": { + "type": "number" + }, + "topP": { + "type": "number" + }, + "topK": { + "type": "number" + }, + "frequencyPenalty": { + "type": "number" + }, + "presencePenalty": { + "type": "number" + }, + "stopSequences": { "type": "array", "items": { - "type": "object" + "type": "string" } + }, + "seed": { + "type": "number" + }, + "maxRetries": { + "type": "number" } } } }, - "required": ["body"], - "examples": [ - { - "editedAt": "2022-07-13T14:32:50.697Z", - "body": { - "version": 1, - "content": [] - } - } - ], - "description": "" + "required": [ + "type", + "id", + "name", + "systemPrompt", + "provider", + "createdAt", + "updatedAt" + ] }, - "CommentReaction": { + "CreateAiCopilot": { + "title": "CreateAiCopilot", "type": "object", - "title": "CommentReaction", "properties": { - "userId": { + "name": { "type": "string" }, - "createdAt": { - "type": "string", - "format": "date-time" + "providerApiKey": { + "type": "string" }, - "emoji": { + "description": { "type": "string" - } - }, - "required": ["userId", "emoji"], - "examples": [ - { - "emoji": "👨‍👩‍👧", - "createdAt": "2022-07-13T14:32:50.697Z", - "userId": "alice" - } - ] - }, - "AddCommentReaction": { - "type": "object", - "title": "AddCommentReaction", - "properties": { - "userId": { + }, + "systemPrompt": { "type": "string" }, - "createdAt": { + "knowledgePrompt": { + "type": "string" + }, + "provider": { "type": "string", - "format": "date-time" + "enum": ["openai", "anthropic", "google", "openai-compatible"] }, - "emoji": { - "type": "string" - } - }, - "required": ["userId", "emoji"], - "examples": [ - { - "emoji": "👨‍👩‍👧", - "createdAt": "2022-07-13T14:32:50.697Z", - "userId": "alice" - } - ] - }, - "RemoveCommentReaction": { - "type": "object", - "title": "RemoveCommentReaction", - "properties": { - "userId": { + "providerModel": { "type": "string" }, - "removedAt": { - "type": "string", - "format": "date-time" + "compatibleProviderName": { + "type": "string" }, - "emoji": { + "providerBaseUrl": { "type": "string" + }, + "providerOptions": { + "type": "object" + }, + "settings": { + "type": "object", + "properties": { + "maxTokens": { + "type": "number" + }, + "temperature": { + "type": "number" + }, + "topP": { + "type": "number" + }, + "topK": { + "type": "number" + }, + "frequencyPenalty": { + "type": "number" + }, + "presencePenalty": { + "type": "number" + }, + "stopSequences": { + "type": "array", + "items": { + "type": "string" + } + }, + "seed": { + "type": "number" + }, + "maxRetries": { + "type": "number" + } + } } }, - "required": ["userId", "emoji"], - "examples": [ - { - "emoji": "👨‍👩‍👧", - "removedAt": "2022-07-13T14:32:50.697Z", - "userId": "alice" - } - ] + "required": ["name", "systemPrompt", "provider"] }, - "InboxNotificationThreadData": { - "title": "InboxNotificationThreadData", + "UpdateAiCopilot": { + "title": "UpdateAiCopilot", "type": "object", "properties": { - "id": { + "name": { "type": "string" }, - "kind": { + "providerApiKey": { "type": "string" }, - "threadId": { + "description": { "type": "string" }, - "roomId": { + "systemPrompt": { "type": "string" }, - "readAt": { - "type": "string", - "format": "date-time" + "knowledgePrompt": { + "type": "string" }, - "notifiedAt": { + "provider": { "type": "string", - "format": "date-time" - } - } - }, - "InboxNotificationCustomData": { - "title": "InboxNotificationCustomData", - "type": "object", - "properties": { - "id": { - "type": "string" + "enum": ["openai", "anthropic", "google", "openai-compatible"] }, - "kind": { + "providerModel": { "type": "string" }, - "subjectId": { + "compatibleProviderName": { "type": "string" }, - "roomId": { + "providerBaseUrl": { "type": "string" }, - "readAt": { - "type": "string", - "format": "date-time" - }, - "notifiedAt": { - "type": "string", - "format": "date-time" - }, - "activityData": { + "providerOptions": { "type": "object" + }, + "settings": { + "type": "object", + "properties": { + "maxTokens": { + "type": "number" + }, + "temperature": { + "type": "number" + }, + "topP": { + "type": "number" + }, + "topK": { + "type": "number" + }, + "frequencyPenalty": { + "type": "number" + }, + "presencePenalty": { + "type": "number" + }, + "stopSequences": { + "type": "array", + "items": { + "type": "string" + } + }, + "seed": { + "type": "number" + }, + "maxRetries": { + "type": "number" + } + } } } }, - "NotificationChannelSettings": { + "GetAiCopilots": { + "title": "GetAiCopilots", "type": "object", "properties": { - "thread": { - "type": "boolean" + "nextCursor": { + "type": "string" }, - "textMention": { - "type": "boolean" + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AiCopilot" + } } - }, - "additionalProperties": { - "type": "boolean", - "description": "Custom notification kinds prefixed by a '$'" } }, - "NotificationSettings": { + "KnowledgeSource": { + "title": "KnowledgeSource", "type": "object", - "properties": { - "email": { - "$ref": "#/components/schemas/NotificationChannelSettings" - }, - "slack": { - "$ref": "#/components/schemas/NotificationChannelSettings" - }, - "teams": { - "$ref": "#/components/schemas/NotificationChannelSettings" + "oneOf": [ + { + "properties": { + "type": { + "const": "ai-knowledge-web-source" + }, + "id": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "lastIndexedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": ["ingesting", "ready", "error"] + }, + "errorMessage": { + "type": "string" + }, + "link": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["individual_link", "crawl", "sitemap"] + } + } + } + } }, - "webPush": { - "$ref": "#/components/schemas/NotificationChannelSettings" + { + "properties": { + "type": { + "const": "ai-knowledge-file-source" + }, + "id": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "lastIndexedAt": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "enum": ["ingesting", "ready", "error"] + }, + "errorMessage": { + "type": "string" + }, + "file": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "mimeType": { + "type": "string" + } + } + } + } } - }, - "description": "Notification settings for each supported channel" + ] }, - "PartialNotificationSettings": { + "CreateWebKnowledgeSource": { + "title": "CreateWebKnowledgeSource", "type": "object", "properties": { - "email": { - "$ref": "#/components/schemas/NotificationChannelSettings" - }, - "slack": { - "$ref": "#/components/schemas/NotificationChannelSettings" + "copilotId": { + "type": "string" }, - "teams": { - "$ref": "#/components/schemas/NotificationChannelSettings" + "url": { + "type": "string" }, - "webPush": { - "$ref": "#/components/schemas/NotificationChannelSettings" + "type": { + "type": "string", + "enum": ["individual_link", "crawl", "sitemap"] } }, - "description": "Partial notification settings - all properties are optional" - }, - "RoomSubscriptionSettings": { - "title": "RoomSubscriptionSettings", - "type": "object", - "properties": { - "threads": { - "enum": ["all", "replies_and_mentions", "none"] - }, - "textMentions": { - "enum": ["mine", "none"] - } - } + "required": ["copilotId", "url", "type"] }, - "UserRoomSubscriptionSettings": { - "title": "UserRoomSubscriptionSettings", + "GetKnowledgeSources": { + "title": "GetKnowledgeSources", "type": "object", "properties": { - "threads": { - "enum": ["all", "replies_and_mentions", "none"] - }, - "textMentions": { - "enum": ["mine", "none"] - }, - "roomId": { + "nextCursor": { "type": "string" + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/KnowledgeSource" + } } } }, - "TriggerInboxNotification": { - "title": "TriggerInboxNotification", + "WebKnowledgeSourceLink": { + "title": "WebKnowledgeSourceLink", "type": "object", "properties": { - "userId": { + "id": { "type": "string" }, - "kind": { + "url": { "type": "string" }, - "subjectId": { - "type": "string" + "status": { + "type": "string", + "enum": ["ingesting", "ready", "error"] }, - "roomId": { - "type": "string" + "createdAt": { + "type": "string", + "format": "date-time" }, - "activityData": { - "type": "object" + "lastIndexedAt": { + "type": "string", + "format": "date-time" } }, - "required": ["userId", "kind", "subjectId", "activityData"], - "examples": [ - { - "userId": "alice", - "kind": "file-uploaded", - "subjectId": "file123", - "activityData": { - "url": "url-to-file" - } - } - ] + "required": ["id", "url", "status", "createdAt", "lastIndexedAt"] }, - "Subscription": { - "title": "Subscription", + "GetWebKnowledgeSourceLinks": { + "title": "GetWebKnowledgeSourceLinks", "type": "object", "properties": { - "kind": { - "type": "string" - }, - "subjectId": { + "nextCursor": { "type": "string" }, - "createdAt": { - "type": "string", - "format": "date-time" - } - } - }, - "UserSubscription": { - "title": "UserSubscription", - "allOf": [ - { - "$ref": "#/components/schemas/Subscription" - }, - { - "type": "object", - "properties": { - "userId": { - "type": "string" - } + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebKnowledgeSourceLink" } } - ] + } } }, "securitySchemes": { @@ -5120,6 +6172,9 @@ { "name": "Notifications" }, + { + "name": "AI" + }, { "name": "Deprecated" } diff --git a/packages/liveblocks-node/src/__tests__/client.test.ts b/packages/liveblocks-node/src/__tests__/client.test.ts index 318aaa87092..9c75b0faeab 100644 --- a/packages/liveblocks-node/src/__tests__/client.test.ts +++ b/packages/liveblocks-node/src/__tests__/client.test.ts @@ -12,7 +12,7 @@ import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest"; -import { Liveblocks, LiveblocksError } from "../client"; +import { type AiCopilot, Liveblocks, LiveblocksError } from "../client"; import { getBaseUrl } from "../utils"; const DEFAULT_BASE_URL = getBaseUrl(); @@ -1297,7 +1297,7 @@ describe("client", () => { const update = new Uint8Array([21, 31]); server.use( http.get(`${DEFAULT_BASE_URL}/v2/rooms/:roomId/ydoc-binary`, () => { - return HttpResponse.arrayBuffer(update); + return HttpResponse.arrayBuffer(update.buffer); }) ); @@ -1316,9 +1316,9 @@ describe("client", () => { ({ request }) => { const url = new URL(request.url); if (url.searchParams.get("guid") === "subdoc") { - return HttpResponse.arrayBuffer(update); + return HttpResponse.arrayBuffer(update.buffer); } - return HttpResponse.arrayBuffer(new Uint8Array([0])); + return HttpResponse.arrayBuffer(new Uint8Array([0]).buffer); } ) ); @@ -2357,4 +2357,819 @@ describe("client", () => { ).resolves.toBeUndefined(); }); }); + + describe("AI copilots", () => { + const copilot: AiCopilot = { + type: "copilot", + id: "copilot_123", + name: "Test Copilot", + description: "A test AI copilot", + systemPrompt: "You are a helpful assistant", + providerModel: "gpt-4o", + knowledgePrompt: "Use the provided knowledge", + provider: "openai", + providerOptions: {}, + createdAt: new Date("2023-01-01T00:00:00.000Z"), + updatedAt: new Date("2023-01-02T00:00:00.000Z"), + lastUsedAt: new Date("2023-01-03T00:00:00.000Z"), + settings: { + maxTokens: 1000, + temperature: 0.7, + }, + }; + + describe("get AI copilots", () => { + test("should return a list of AI copilots when getAiCopilots receives a successful response", async () => { + server.use( + http.get(`${DEFAULT_BASE_URL}/v2/ai/copilots`, () => { + return HttpResponse.json( + { + nextCursor: "cursor1", + data: [copilot], + }, + { status: 200 } + ); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect(client.getAiCopilots()).resolves.toEqual({ + nextCursor: "cursor1", + data: [copilot], + }); + }); + + test("should return a list of AI copilots with pagination parameters", async () => { + server.use( + http.get(`${DEFAULT_BASE_URL}/v2/ai/copilots`, ({ request }) => { + const url = new URL(request.url); + expect(url.searchParams.get("limit")).toEqual("10"); + expect(url.searchParams.get("startingAfter")).toEqual("cursor1"); + + return HttpResponse.json( + { + nextCursor: "cursor2", + data: [copilot], + }, + { status: 200 } + ); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getAiCopilots({ limit: 10, startingAfter: "cursor1" }) + ).resolves.toEqual({ + nextCursor: "cursor2", + data: [copilot], + }); + }); + + test("should throw a LiveblocksError when getAiCopilots receives an error response", async () => { + const error = { + error: "UNAUTHORIZED", + message: "Invalid secret key", + }; + + server.use( + http.get(`${DEFAULT_BASE_URL}/v2/ai/copilots`, () => { + return HttpResponse.json(error, { status: 401 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.getAiCopilots(); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(401); + expect(err.message).toBe("Invalid secret key"); + expect(err.name).toBe("LiveblocksError"); + } + } + }); + }); + + describe("create AI copilot", () => { + test("should create an AI copilot when createAiCopilot receives a successful response", async () => { + const createData = { + name: "Test Copilot", + description: "A test AI copilot", + systemPrompt: "You are a helpful assistant", + knowledgePrompt: "Use the provided knowledge", + provider: "openai" as const, + providerApiKey: "sk_xxx", + providerModel: "gpt-4o", + settings: { + maxTokens: 1000, + temperature: 0.7, + }, + }; + + server.use( + http.post( + `${DEFAULT_BASE_URL}/v2/ai/copilots`, + async ({ request }) => { + const data = await request.json(); + expect(data).toEqual(createData); + return HttpResponse.json(copilot, { status: 201 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.createAiCopilot(createData); + expect(result).toEqual(copilot); + }); + + test("should throw a LiveblocksError when createAiCopilot receives an error response", async () => { + const error = { + error: "INVALID_REQUEST", + message: "Invalid copilot data", + }; + + server.use( + http.post(`${DEFAULT_BASE_URL}/v2/ai/copilots`, () => { + return HttpResponse.json(error, { status: 400 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.createAiCopilot({ + name: "Test", + systemPrompt: "Test", + providerApiKey: "sk_xxx", + provider: "openai", + providerModel: "gpt-4", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(400); + expect(err.message).toBe("Invalid copilot data"); + } + } + }); + }); + + describe("get AI copilot", () => { + test("should return an AI copilot when getAiCopilot receives a successful response", async () => { + server.use( + http.get(`${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, () => { + return HttpResponse.json(copilot, { status: 200 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect(client.getAiCopilot("copilot_123")).resolves.toEqual( + copilot + ); + }); + + test("should throw a LiveblocksError when getAiCopilot receives an error response", async () => { + server.use( + http.get(`${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, () => { + return new HttpResponse(null, { status: 404 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.getAiCopilot("nonexistent"); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + } + } + }); + }); + + describe("update AI copilot", () => { + test("should update an AI copilot when updateAiCopilot receives a successful response", async () => { + const updateData = { + name: "Updated Copilot", + systemPrompt: "You are an updated assistant", + }; + + const updatedCopilot = { + ...copilot, + name: "Updated Copilot", + systemPrompt: "You are an updated assistant", + }; + + server.use( + http.post( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, + async ({ request }) => { + const data = await request.json(); + expect(data).toEqual(updateData); + return HttpResponse.json(updatedCopilot, { status: 200 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.updateAiCopilot("copilot_123", updateData); + expect(result).toEqual(updatedCopilot); + }); + + test("should throw a LiveblocksError when updateAiCopilot receives an error response", async () => { + server.use( + http.post(`${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, () => { + return new HttpResponse(null, { status: 404 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.updateAiCopilot("nonexistent", { name: "Updated" }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + } + } + }); + }); + + describe("delete AI copilot", () => { + test("should delete an AI copilot when deleteAiCopilot receives a successful response", async () => { + server.use( + http.delete(`${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, () => { + return HttpResponse.text(null, { status: 204 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.deleteAiCopilot("copilot_123"); + expect(result).toBeUndefined(); + }); + + test("should throw a LiveblocksError when deleteAiCopilot receives an error response", async () => { + server.use( + http.delete(`${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId`, () => { + return new HttpResponse(null, { status: 404 }); + }) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.deleteAiCopilot("nonexistent"); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + } + } + }); + }); + }); + + describe("knowledge source management", () => { + const webKnowledgeSource = { + id: "ks_web_123", + type: "ai-knowledge-web-source" as const, + link: { + url: "https://example.com", + type: "individual_link" as const, + }, + status: "ready" as const, + createdAt: new Date("2023-01-01T00:00:00.000Z"), + updatedAt: new Date("2023-01-02T00:00:00.000Z"), + lastIndexedAt: new Date("2023-01-03T00:00:00.000Z"), + }; + + const fileKnowledgeSource = { + id: "ks_file_123", + type: "ai-knowledge-file-source" as const, + file: { + name: "document.pdf", + mimeType: "application/pdf", + }, + status: "ready" as const, + createdAt: new Date("2023-01-01T00:00:00.000Z"), + updatedAt: new Date("2023-01-02T00:00:00.000Z"), + lastIndexedAt: new Date("2023-01-03T00:00:00.000Z"), + }; + + const webKnowledgeSourceLink = { + id: "link_123", + url: "https://example.com/page1", + status: "ready" as const, + createdAt: new Date("2023-01-01T00:00:00.000Z"), + lastIndexedAt: new Date("2023-01-03T00:00:00.000Z"), + }; + + describe("create web knowledge source", () => { + test("should create a web knowledge source when createWebKnowledgeSource receives a successful response", async () => { + const createData = { + copilotId: "copilot_123", + url: "https://example.com", + type: "individual_link" as const, + }; + + server.use( + http.post( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web`, + async ({ request }) => { + const data = await request.json(); + expect(data).toEqual(createData); + return HttpResponse.json({ id: "ks_web_123" }, { status: 201 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.createWebKnowledgeSource(createData); + expect(result).toEqual({ id: "ks_web_123" }); + }); + + test("should throw a LiveblocksError when createWebKnowledgeSource receives an error response", async () => { + const error = { + error: "INVALID_URL", + message: "Invalid URL provided", + }; + + server.use( + http.post( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web`, + () => { + return HttpResponse.json(error, { status: 400 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.createWebKnowledgeSource({ + copilotId: "copilot_123", + url: "invalid-url", + type: "individual_link", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(400); + expect(err.message).toBe("Invalid URL provided"); + } + } + }); + }); + + describe("create file knowledge source", () => { + test("should create a file knowledge source when createFileKnowledgeSource receives a successful response", async () => { + // Create a mock File object + const file = new File(["test content"], "test.pdf", { + type: "application/pdf", + }); + + server.use( + http.put( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:name`, + async ({ request, params }) => { + expect(params.name).toBe("test.pdf"); + expect(request.headers.get("Content-Type")).toBe( + "application/pdf" + ); + const body = await request.text(); + expect(body).toBe("test content"); + return HttpResponse.json({ id: "ks_file_123" }, { status: 201 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.createFileKnowledgeSource({ + copilotId: "copilot_123", + file, + }); + expect(result).toEqual({ id: "ks_file_123" }); + }); + + test("should throw a LiveblocksError when createFileKnowledgeSource receives an error response", async () => { + const error = { + error: "INVALID_FILE", + message: "Invalid file provided", + }; + + const file = new File(["test content"], "test.pdf", { + type: "application/pdf", + }); + + server.use( + http.put( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:name`, + () => { + return HttpResponse.json(error, { status: 400 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.createFileKnowledgeSource({ + copilotId: "copilot_123", + file, + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(400); + expect(err.message).toBe("Invalid file provided"); + } + } + }); + }); + + describe("delete web knowledge source", () => { + test("should delete a web knowledge source when deleteWebKnowledgeSource receives a successful response", async () => { + server.use( + http.delete( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web/:knowledgeSourceId`, + () => { + return HttpResponse.text(null, { status: 204 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.deleteWebKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_web_123", + }); + expect(result).toBeUndefined(); + }); + + test("should throw a LiveblocksError when deleteWebKnowledgeSource receives an error response", async () => { + const error = { + error: "KNOWLEDGE_SOURCE_NOT_FOUND", + message: "Knowledge source not found", + }; + + server.use( + http.delete( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web/:knowledgeSourceId`, + () => { + return HttpResponse.json(error, { status: 404 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.deleteWebKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "nonexistent", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + expect(err.message).toBe("Knowledge source not found"); + } + } + }); + }); + + describe("delete file knowledge source", () => { + test("should delete a file knowledge source when deleteFileKnowledgeSource receives a successful response", async () => { + server.use( + http.delete( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:knowledgeSourceId`, + () => { + return HttpResponse.text(null, { status: 204 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + const result = await client.deleteFileKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_file_123", + }); + expect(result).toBeUndefined(); + }); + + test("should throw a LiveblocksError when deleteFileKnowledgeSource receives an error response", async () => { + const error = { + error: "KNOWLEDGE_SOURCE_NOT_FOUND", + message: "Knowledge source not found", + }; + + server.use( + http.delete( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:knowledgeSourceId`, + () => { + return HttpResponse.json(error, { status: 404 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.deleteFileKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "nonexistent", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + expect(err.message).toBe("Knowledge source not found"); + } + } + }); + }); + + describe("get knowledge sources", () => { + test("should return a list of knowledge sources when getKnowledgeSources receives a successful response", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge`, + () => { + return HttpResponse.json( + { + nextCursor: "cursor1", + data: [webKnowledgeSource, fileKnowledgeSource], + }, + { status: 200 } + ); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getKnowledgeSources({ copilotId: "copilot_123" }) + ).resolves.toEqual({ + nextCursor: "cursor1", + data: [webKnowledgeSource, fileKnowledgeSource], + }); + }); + + test("should return a list of knowledge sources with pagination parameters", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge`, + ({ request }) => { + const url = new URL(request.url); + expect(url.searchParams.get("limit")).toEqual("10"); + expect(url.searchParams.get("startingAfter")).toEqual("cursor1"); + + return HttpResponse.json( + { + nextCursor: "cursor2", + data: [webKnowledgeSource], + }, + { status: 200 } + ); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getKnowledgeSources({ + copilotId: "copilot_123", + limit: 10, + startingAfter: "cursor1", + }) + ).resolves.toEqual({ + nextCursor: "cursor2", + data: [webKnowledgeSource], + }); + }); + }); + + describe("get knowledge source", () => { + test("should return a knowledge source when getKnowledgeSource receives a successful response", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/:knowledgeSourceId`, + () => { + return HttpResponse.json(webKnowledgeSource, { + status: 200, + }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_web_123", + }) + ).resolves.toEqual(webKnowledgeSource); + }); + + test("should throw a LiveblocksError when getKnowledgeSource receives an error response", async () => { + const error = { + error: "KNOWLEDGE_SOURCE_NOT_FOUND", + message: "Knowledge source not found", + }; + + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/:knowledgeSourceId`, + () => { + return HttpResponse.json(error, { status: 404 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.getKnowledgeSource({ + copilotId: "copilot_123", + knowledgeSourceId: "nonexistent", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + expect(err.message).toBe("Knowledge source not found"); + } + } + }); + }); + + describe("get file knowledge source markdown", () => { + test("should return file content when getFileKnowledgeSourceMarkdown receives a successful response", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:knowledgeSourceId`, + () => { + return HttpResponse.json( + { + id: "ks_file_123", + content: "# Document Title\n\nThis is the content.", + }, + { status: 200 } + ); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getFileKnowledgeSourceMarkdown({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_file_123", + }) + ).resolves.toEqual("# Document Title\n\nThis is the content."); + }); + + test("should throw a LiveblocksError when getFileKnowledgeSourceMarkdown receives an error response", async () => { + const error = { + error: "KNOWLEDGE_SOURCE_NOT_FOUND", + message: "Knowledge source not found", + }; + + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/file/:knowledgeSourceId`, + () => { + return HttpResponse.json(error, { status: 404 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.getFileKnowledgeSourceMarkdown({ + copilotId: "copilot_123", + knowledgeSourceId: "nonexistent", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + expect(err.message).toBe("Knowledge source not found"); + } + } + }); + }); + + describe("get web knowledge source links", () => { + test("should return a list of links when getWebKnowledgeSourceLinks receives a successful response", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web/:knowledgeSourceId/links`, + () => { + return HttpResponse.json( + { + nextCursor: "cursor1", + data: [webKnowledgeSourceLink], + }, + { status: 200 } + ); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getWebKnowledgeSourceLinks({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_web_123", + }) + ).resolves.toEqual({ + nextCursor: "cursor1", + data: [webKnowledgeSourceLink], + }); + }); + + test("should return a list of links with pagination parameters", async () => { + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web/:knowledgeSourceId/links`, + ({ request }) => { + const url = new URL(request.url); + expect(url.searchParams.get("limit")).toEqual("20"); + expect(url.searchParams.get("startingAfter")).toEqual("cursor1"); + + return HttpResponse.json( + { + nextCursor: "cursor2", + data: [webKnowledgeSourceLink], + }, + { status: 200 } + ); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + await expect( + client.getWebKnowledgeSourceLinks({ + copilotId: "copilot_123", + knowledgeSourceId: "ks_web_123", + limit: 20, + startingAfter: "cursor1", + }) + ).resolves.toEqual({ + nextCursor: "cursor2", + data: [webKnowledgeSourceLink], + }); + }); + + test("should throw a LiveblocksError when getWebKnowledgeSourceLinks receives an error response", async () => { + const error = { + error: "KNOWLEDGE_SOURCE_NOT_FOUND", + message: "Knowledge source not found", + }; + + server.use( + http.get( + `${DEFAULT_BASE_URL}/v2/ai/copilots/:copilotId/knowledge/web/:knowledgeSourceId/links`, + () => { + return HttpResponse.json(error, { status: 404 }); + } + ) + ); + + const client = new Liveblocks({ secret: "sk_xxx" }); + + try { + await client.getWebKnowledgeSourceLinks({ + copilotId: "copilot_123", + knowledgeSourceId: "nonexistent", + }); + expect(true).toBe(false); + } catch (err) { + expect(err instanceof LiveblocksError).toBe(true); + if (err instanceof LiveblocksError) { + expect(err.status).toBe(404); + expect(err.message).toBe("Knowledge source not found"); + } + } + }); + }); + }); }); diff --git a/packages/liveblocks-node/src/client.ts b/packages/liveblocks-node/src/client.ts index 0b99867a2f1..c5da289afa8 100644 --- a/packages/liveblocks-node/src/client.ts +++ b/packages/liveblocks-node/src/client.ts @@ -169,6 +169,41 @@ export type RoomData = { type RoomDataPlain = DateToString; +export type AiCopilot = { + type: "copilot"; + id: string; + name: string; + systemPrompt: string; + knowledgePrompt?: string; + description?: string; + createdAt: Date; + updatedAt: Date; + lastUsedAt?: Date; + providerModel: string; + providerOptions?: Record>; + settings?: { + maxTokens?: number; + temperature?: number; + topP?: number; + topK?: number; + frequencyPenalty?: number; + presencePenalty?: number; + stopSequences?: string[]; + seed?: number; + maxRetries?: number; + }; +} & ( + | { + provider: "openai" | "anthropic" | "google"; + } + | { + provider: "openai-compatible"; + compatibleProviderName: string; + providerBaseUrl: string; + } +); + +type AiCopilotPlain = DateToString; export type RoomUser = { type: "user"; id: string | null; @@ -323,6 +358,118 @@ export type UpsertRoomOptions = { create?: CreateRoomOptions; }; +export type GetAiCopilotsOptions = PaginationOptions; +type ProviderSettings = { + maxTokens?: number; + temperature?: number; + topP?: number; + topK?: number; + frequencyPenalty?: number; + presencePenalty?: number; + stopSequences?: string[]; + seed?: number; + maxRetries?: number; +}; + +export type CreateAiCopilotOptions = { + name: string; + providerApiKey: string; + providerModel: string; + description?: string; + systemPrompt: string; + knowledgePrompt?: string; + providerOptions?: Record>; + settings?: ProviderSettings; +} & ( + | { + provider: "openai" | "anthropic" | "google"; + } + | { + provider: "openai-compatible"; + compatibleProviderName: string; + providerBaseUrl: string; + } +); + +export type UpdateAiCopilotOptions = { + name?: string; + providerApiKey?: string; + providerModel?: string; + description?: string | null; + systemPrompt?: string; + knowledgePrompt?: string | null; + providerOptions?: Record> | null; + settings?: ProviderSettings | null; +} & ( + | { + provider?: "openai" | "anthropic" | "google"; + compatibleProviderName?: null; + providerBaseUrl?: null; + } + | { + provider?: "openai-compatible"; + compatibleProviderName?: string; + providerBaseUrl?: string; + } +); + +export type CreateWebKnowledgeSourceOptions = { + copilotId: string; + url: string; + type: "individual_link" | "crawl" | "sitemap"; +}; + +export type CreateFileKnowledgeSourceOptions = { + copilotId: string; + file: File; +}; + +export type GetKnowledgeSourcesOptions = { + copilotId: string; +} & PaginationOptions; + +export type GetWebKnowledgeSourceLinksOptions = { + copilotId: string; + knowledgeSourceId: string; +} & PaginationOptions; + +type KnowledgeSourcePlain = DateToString; + +export type KnowledgeSource = ( + | { + type: "ai-knowledge-web-source"; + link: { + url: string; + type: "individual_link" | "crawl" | "sitemap"; + }; + } + | { + type: "ai-knowledge-file-source"; + file: { + name: string; + mimeType: string; + }; + } +) & { + id: string; + createdAt: Date; + updatedAt: Date; + lastIndexedAt: Date; +} & ( + | { status: "ingesting" | "ready" } + | { status: "error"; errorMessage: string } + ); + +type WebKnowledgeSourceLinkPlain = DateToString; + +export type WebKnowledgeSourceLink = { + id: string; + url: string; + status: "ingesting" | "ready" | "error"; + createdAt: Date; + lastIndexedAt: Date; +}; + export type RequestOptions = { signal?: AbortSignal; }; @@ -344,6 +491,34 @@ function inflateRoomData(room: RoomDataPlain): RoomData { }; } +function inflateAiCopilot(copilot: AiCopilotPlain): AiCopilot { + return { + ...copilot, + createdAt: new Date(copilot.createdAt), + updatedAt: new Date(copilot.updatedAt), + lastUsedAt: copilot.lastUsedAt ? new Date(copilot.lastUsedAt) : undefined, + }; +} + +function inflateKnowledgeSource(source: KnowledgeSourcePlain): KnowledgeSource { + return { + ...source, + createdAt: new Date(source.createdAt), + updatedAt: new Date(source.updatedAt), + lastIndexedAt: new Date(source.lastIndexedAt), + }; +} + +function inflateWebKnowledgeSourceLink( + link: WebKnowledgeSourceLinkPlain +): WebKnowledgeSourceLink { + return { + ...link, + createdAt: new Date(link.createdAt), + lastIndexedAt: new Date(link.lastIndexedAt), + }; +} + /** * Interact with the Liveblocks API from your Node.js backend. */ @@ -2376,6 +2551,317 @@ export class Liveblocks { // }; // return data; } + + /** + * Returns a paginated list of AI copilots. The copilots are returned sorted by creation date, from newest to oldest. + * @param params.limit (optional) A limit on the number of copilots to return. The limit can range between 1 and 100, and defaults to 20. + * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns A paginated list of AI copilots. + */ + public async getAiCopilots( + params: PaginationOptions = {}, + options?: RequestOptions + ): Promise> { + const res = await this.#get( + url`/v2/ai/copilots`, + { + limit: params.limit, + startingAfter: params.startingAfter, + }, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const page = (await res.json()) as Page; + return { + ...page, + data: page.data.map(inflateAiCopilot), + }; + } + + /** + * Creates an AI copilot. + * @param params The parameters to create the copilot with. + * @returns The created copilot. + */ + public async createAiCopilot( + params: CreateAiCopilotOptions, + options?: RequestOptions + ): Promise { + const res = await this.#post(url`/v2/ai/copilots`, params, options); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as AiCopilotPlain; + return inflateAiCopilot(data); + } + + /** + * Returns an AI copilot with the given id. + * @param copilotId The id of the copilot to return. + * @returns The copilot with the given id. + * @param options.signal (optional) An abort signal to cancel the request. + */ + public async getAiCopilot( + copilotId: string, + options?: RequestOptions + ): Promise { + const res = await this.#get( + url`/v2/ai/copilots/${copilotId}`, + undefined, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + + const data = (await res.json()) as AiCopilotPlain; + return inflateAiCopilot(data); + } + + /** + * Updates an AI copilot with the given id. + * @param copilotId The id of the copilot to update. + * @param params The parameters to update the copilot with. + * @returns The updated copilot. + */ + public async updateAiCopilot( + copilotId: string, + params: UpdateAiCopilotOptions, + options?: RequestOptions + ): Promise { + const res = await this.#post( + url`/v2/ai/copilots/${copilotId}`, + params, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as AiCopilotPlain; + return inflateAiCopilot(data); + } + + /** + * Deletes an AI copilot with the given id. A deleted copilot is no longer accessible from the API or the dashboard and it cannot be restored. + * @param copilotId The id of the copilot to delete. + * @param options.signal (optional) An abort signal to cancel the request. + */ + public async deleteAiCopilot( + copilotId: string, + options?: RequestOptions + ): Promise { + const res = await this.#delete(url`/v2/ai/copilots/${copilotId}`, options); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + } + + /** + * Creates a web knowledge source. + * @param params.url The URL of the web knowledge source. + * @param params.type The type of the web knowledge source: "individual_link", "crawl" or "sitemap". + * @param options.signal (optional) An abort signal to cancel the request. + * @returns The id of the created web knowledge source. + */ + public async createWebKnowledgeSource( + params: CreateWebKnowledgeSourceOptions, + options?: RequestOptions + ): Promise<{ id: string }> { + const res = await this.#post( + url`/v2/ai/copilots/${params.copilotId}/knowledge/web`, + params, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as { id: string }; + return data; + } + + /** + * Creates a file knowledge source. + * @param params.copilotId The id of the copilot. + * @param params.name The name of the file knowledge source. + * @param params.file The file to create the knowledge source from. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns The id of the created file knowledge source. + */ + public async createFileKnowledgeSource( + params: CreateFileKnowledgeSourceOptions, + options?: RequestOptions + ): Promise<{ id: string }> { + const fetch = await fetchPolyfill(); + const res = await fetch( + urljoin( + this.#baseUrl, + url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.file.name}` + ), + { + method: "PUT", + body: params.file, + headers: { + Authorization: `Bearer ${this.#secret}`, + "Content-Type": params.file.type, + "Content-Length": String(params.file.size), + }, + signal: options?.signal, + } + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as { id: string }; + return data; + } + + /** + * Deletes a file knowledge source. + * @param params.copilotId The id of the copilot. + * @param params.knowledgeSourceId The id of the knowledge source to delete. + * @param options.signal (optional) An abort signal to cancel the request. + */ + public async deleteFileKnowledgeSource( + params: { copilotId: string; knowledgeSourceId: string }, + options?: RequestOptions + ): Promise { + const res = await this.#delete( + url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + } + + /** + * Deletes a web knowledge source. + * @param params.copilotId The id of the copilot. + * @param params.knowledgeSourceId The id of the knowledge source to delete. + * @param options.signal (optional) An abort signal to cancel the request. + */ + public async deleteWebKnowledgeSource( + params: { copilotId: string; knowledgeSourceId: string }, + options?: RequestOptions + ): Promise { + const res = await this.#delete( + url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}`, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + } + + /** + * Returns a paginated list of knowledge sources. + * @param params.copilotId The id of the copilot. + * @param params.limit (optional) A limit on the number of knowledge sources to return. The limit can range between 1 and 100, and defaults to 20. + * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns A paginated list of knowledge sources. + */ + public async getKnowledgeSources( + params: GetKnowledgeSourcesOptions, + options?: RequestOptions + ): Promise> { + const res = await this.#get( + url`/v2/ai/copilots/${params.copilotId}/knowledge`, + { + limit: params.limit, + startingAfter: params.startingAfter, + }, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const page = (await res.json()) as Page; + return { + ...page, + data: page.data.map(inflateKnowledgeSource), + }; + } + + /** + * Returns a knowledge source with the given id. + * @param params.copilotId The id of the copilot. + * @param params.knowledgeSourceId The id of the knowledge source to return. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns The knowledge source. + */ + public async getKnowledgeSource( + params: { copilotId: string; knowledgeSourceId: string }, + options?: RequestOptions + ): Promise { + const res = await this.#get( + url`/v2/ai/copilots/${params.copilotId}/knowledge/${params.knowledgeSourceId}`, + undefined, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as KnowledgeSourcePlain; + return inflateKnowledgeSource(data); + } + + /** + * Returns the content of a file knowledge source. + * @param params.copilotId The id of the copilot. + * @param params.knowledgeSourceId The id of the knowledge source. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns The content of the file knowledge source. + */ + public async getFileKnowledgeSourceMarkdown( + params: { copilotId: string; knowledgeSourceId: string }, + options?: RequestOptions + ): Promise { + const res = await this.#get( + url`/v2/ai/copilots/${params.copilotId}/knowledge/file/${params.knowledgeSourceId}`, + undefined, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const data = (await res.json()) as { id: string; content: string }; + return data.content; + } + + /** + * Returns a paginated list of web knowledge source links. + * @param params.copilotId The id of the copilot. + * @param params.knowledgeSourceId The id of the knowledge source. + * @param params.limit (optional) A limit on the number of links to return. The limit can range between 1 and 100, and defaults to 20. + * @param params.startingAfter (optional) A cursor used for pagination. You get the value from the response of the previous page. + * @param options.signal (optional) An abort signal to cancel the request. + * @returns A paginated list of web knowledge source links. + */ + public async getWebKnowledgeSourceLinks( + params: GetWebKnowledgeSourceLinksOptions, + options?: RequestOptions + ): Promise> { + const res = await this.#get( + url`/v2/ai/copilots/${params.copilotId}/knowledge/web/${params.knowledgeSourceId}/links`, + { + limit: params.limit, + startingAfter: params.startingAfter, + }, + options + ); + if (!res.ok) { + throw await LiveblocksError.from(res); + } + const page = (await res.json()) as Page; + return { + ...page, + data: page.data.map(inflateWebKnowledgeSourceLink), + }; + } } export class LiveblocksError extends Error { From 932b67e830fd93c1b9dd71c9f770af9ef41fab88 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 28 Aug 2025 20:51:26 +0000 Subject: [PATCH 2/5] Bump to 3.5.0 --- package-lock.json | 98 +++++++++---------- packages/liveblocks-client/package.json | 4 +- packages/liveblocks-core/package.json | 2 +- packages/liveblocks-emails/package.json | 6 +- packages/liveblocks-node-lexical/package.json | 6 +- .../liveblocks-node-prosemirror/package.json | 6 +- packages/liveblocks-node/package.json | 4 +- .../liveblocks-react-blocknote/package.json | 14 +-- .../liveblocks-react-lexical/package.json | 12 +-- packages/liveblocks-react-tiptap/package.json | 12 +-- packages/liveblocks-react-ui/package.json | 8 +- packages/liveblocks-react/package.json | 6 +- packages/liveblocks-redux/package.json | 6 +- packages/liveblocks-yjs/package.json | 6 +- packages/liveblocks-zustand/package.json | 6 +- 15 files changed, 98 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index d59dbf75e10..34851067be6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37111,10 +37111,10 @@ }, "packages/liveblocks-client": { "name": "@liveblocks/client", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.4.2" + "@liveblocks/core": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37123,7 +37123,7 @@ }, "packages/liveblocks-core": { "name": "@liveblocks/core", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37141,11 +37141,11 @@ }, "packages/liveblocks-emails": { "name": "@liveblocks/emails", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2" + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37164,10 +37164,10 @@ }, "packages/liveblocks-node": { "name": "@liveblocks/node", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.4.2", + "@liveblocks/core": "3.5.0", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" @@ -37182,11 +37182,11 @@ }, "packages/liveblocks-node-lexical": { "name": "@liveblocks/node-lexical", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2", + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0", "yjs": "^13.6.18" }, "devDependencies": { @@ -37203,11 +37203,11 @@ }, "packages/liveblocks-node-prosemirror": { "name": "@liveblocks/node-prosemirror", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2", + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0", "yjs": "^13.6.20" }, "devDependencies": { @@ -37227,11 +37227,11 @@ }, "packages/liveblocks-react": { "name": "@liveblocks/react", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37261,15 +37261,15 @@ }, "packages/liveblocks-react-blocknote": { "name": "@liveblocks/react-blocknote", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-tiptap": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-tiptap": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@tiptap/core": "^2.7.2", "vitest-tsconfig-paths": "^3.4.1" }, @@ -37306,15 +37306,15 @@ }, "packages/liveblocks-react-lexical": { "name": "@liveblocks/react-lexical", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.1", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "yjs": "^13.6.18" @@ -37843,15 +37843,15 @@ }, "packages/liveblocks-react-tiptap": { "name": "@liveblocks/react-tiptap", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "@tiptap/core": "^2.7.2", @@ -38376,13 +38376,13 @@ }, "packages/liveblocks-react-ui": { "name": "@liveblocks/react-ui", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.1.0", @@ -39710,11 +39710,11 @@ }, "packages/liveblocks-redux": { "name": "@liveblocks/redux", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -39871,10 +39871,10 @@ }, "packages/liveblocks-yjs": { "name": "@liveblocks/yjs", - "version": "3.4.2", + "version": "3.5.0", "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" @@ -39940,11 +39940,11 @@ }, "packages/liveblocks-zustand": { "name": "@liveblocks/zustand", - "version": "3.4.2", + "version": "3.5.0", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-client/package.json b/packages/liveblocks-client/package.json index 01ee0d02942..0412988d527 100644 --- a/packages/liveblocks-client/package.json +++ b/packages/liveblocks-client/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/client", - "version": "3.4.2", + "version": "3.5.0", "description": "A client that lets you interact with Liveblocks servers. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,7 +34,7 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.4.2" + "@liveblocks/core": "3.5.0" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-core/package.json b/packages/liveblocks-core/package.json index f45a2b90915..984e4022407 100644 --- a/packages/liveblocks-core/package.json +++ b/packages/liveblocks-core/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/core", - "version": "3.4.2", + "version": "3.5.0", "description": "Private internals for Liveblocks. DO NOT import directly from this package!", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/liveblocks-emails/package.json b/packages/liveblocks-emails/package.json index 7b7d9db890b..c5ea8cdadbb 100644 --- a/packages/liveblocks-emails/package.json +++ b/packages/liveblocks-emails/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/emails", - "version": "3.4.2", + "version": "3.5.0", "description": "A set of functions and utilities to make sending emails based on Liveblocks notification events easy. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -35,8 +35,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2" + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc" diff --git a/packages/liveblocks-node-lexical/package.json b/packages/liveblocks-node-lexical/package.json index b4b6d2f7f07..0d7b2a86ac6 100644 --- a/packages/liveblocks-node-lexical/package.json +++ b/packages/liveblocks-node-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-lexical", - "version": "3.4.2", + "version": "3.5.0", "description": "A server-side utility that lets you modify lexical documents hosted in Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2", + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0", "yjs": "^13.6.18" }, "peerDependencies": { diff --git a/packages/liveblocks-node-prosemirror/package.json b/packages/liveblocks-node-prosemirror/package.json index 6d4fcd6c5f5..449f957e225 100644 --- a/packages/liveblocks-node-prosemirror/package.json +++ b/packages/liveblocks-node-prosemirror/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-prosemirror", - "version": "3.4.2", + "version": "3.5.0", "description": "A server-side utility that lets you modify prosemirror and tiptap documents hosted in Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.4.2", - "@liveblocks/node": "3.4.2", + "@liveblocks/core": "3.5.0", + "@liveblocks/node": "3.5.0", "yjs": "^13.6.20" }, "peerDependencies": { diff --git a/packages/liveblocks-node/package.json b/packages/liveblocks-node/package.json index aeca076bab2..6cf3b98f0c4 100644 --- a/packages/liveblocks-node/package.json +++ b/packages/liveblocks-node/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node", - "version": "3.4.2", + "version": "3.5.0", "description": "A server-side utility that lets you set up a Liveblocks authentication endpoint. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,7 +34,7 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.4.2", + "@liveblocks/core": "3.5.0", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" diff --git a/packages/liveblocks-react-blocknote/package.json b/packages/liveblocks-react-blocknote/package.json index c0b7384d7ff..e3370a261c8 100644 --- a/packages/liveblocks-react-blocknote/package.json +++ b/packages/liveblocks-react-blocknote/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-blocknote", - "version": "3.4.2", + "version": "3.5.0", "description": "An integration of BlockNote + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -42,12 +42,12 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-tiptap": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-tiptap": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@tiptap/core": "^2.7.2", "vitest-tsconfig-paths": "^3.4.1" }, diff --git a/packages/liveblocks-react-lexical/package.json b/packages/liveblocks-react-lexical/package.json index 258ce52a4c4..f3333fc9fd0 100644 --- a/packages/liveblocks-react-lexical/package.json +++ b/packages/liveblocks-react-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-lexical", - "version": "3.4.2", + "version": "3.5.0", "description": "An integration of Lexical + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -43,11 +43,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.1", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "yjs": "^13.6.18" diff --git a/packages/liveblocks-react-tiptap/package.json b/packages/liveblocks-react-tiptap/package.json index 15d94d7a57d..c25b11cf934 100644 --- a/packages/liveblocks-react-tiptap/package.json +++ b/packages/liveblocks-react-tiptap/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-tiptap", - "version": "3.4.2", + "version": "3.5.0", "description": "An integration of TipTap + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -43,11 +43,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", - "@liveblocks/react-ui": "3.4.2", - "@liveblocks/yjs": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", + "@liveblocks/react-ui": "3.5.0", + "@liveblocks/yjs": "3.5.0", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "@tiptap/core": "^2.7.2", diff --git a/packages/liveblocks-react-ui/package.json b/packages/liveblocks-react-ui/package.json index 476b5a9bf19..de81db54d89 100644 --- a/packages/liveblocks-react-ui/package.json +++ b/packages/liveblocks-react-ui/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-ui", - "version": "3.4.2", + "version": "3.5.0", "description": "A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -76,9 +76,9 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", - "@liveblocks/react": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", + "@liveblocks/react": "3.5.0", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.1.0", diff --git a/packages/liveblocks-react/package.json b/packages/liveblocks-react/package.json index daa47ea1760..605215e7950 100644 --- a/packages/liveblocks-react/package.json +++ b/packages/liveblocks-react/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react", - "version": "3.4.2", + "version": "3.5.0", "description": "A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -61,8 +61,8 @@ "showdeps": "depcruise src --include-only '^src' --exclude='__tests__' --output-type dot | dot -T svg > /tmp/dependency-graph.svg && open /tmp/dependency-graph.svg" }, "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "peerDependencies": { "@types/react": "*", diff --git a/packages/liveblocks-redux/package.json b/packages/liveblocks-redux/package.json index 3eab4a37f83..8ebd5ebb0b1 100644 --- a/packages/liveblocks-redux/package.json +++ b/packages/liveblocks-redux/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/redux", - "version": "3.4.2", + "version": "3.5.0", "description": "A store enhancer to integrate Liveblocks into Redux stores. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -33,8 +33,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "peerDependencies": { "redux": "^4 || ^5" diff --git a/packages/liveblocks-yjs/package.json b/packages/liveblocks-yjs/package.json index 610aa10b311..301e86e393b 100644 --- a/packages/liveblocks-yjs/package.json +++ b/packages/liveblocks-yjs/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/yjs", - "version": "3.4.2", + "version": "3.5.0", "description": "Integrate your existing or new Yjs documents with Liveblocks.", "icense": "Apache-2.0", "type": "module", @@ -33,8 +33,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2", + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" diff --git a/packages/liveblocks-zustand/package.json b/packages/liveblocks-zustand/package.json index d1df089f044..d23b00067e8 100644 --- a/packages/liveblocks-zustand/package.json +++ b/packages/liveblocks-zustand/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/zustand", - "version": "3.4.2", + "version": "3.5.0", "description": "A middleware for Zustand to automatically synchronize your stores with Liveblocks. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.4.2", - "@liveblocks/core": "3.4.2" + "@liveblocks/client": "3.5.0", + "@liveblocks/core": "3.5.0" }, "peerDependencies": { "zustand": "^5.0.1" From d34170c3e41c57d679bd7227d01adbe1933f2322 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 28 Aug 2025 15:24:56 -0700 Subject: [PATCH 3/5] Tiptap thread delete (#2626) --- packages/liveblocks-react-tiptap/src/LiveblocksExtension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/liveblocks-react-tiptap/src/LiveblocksExtension.ts b/packages/liveblocks-react-tiptap/src/LiveblocksExtension.ts index 3f30f2a549c..bfdceaa2eae 100644 --- a/packages/liveblocks-react-tiptap/src/LiveblocksExtension.ts +++ b/packages/liveblocks-react-tiptap/src/LiveblocksExtension.ts @@ -341,7 +341,7 @@ export const useLiveblocksExtension = ( pos + node.nodeSize, this.editor.state.doc.content.size - 1 ); - tr.removeMark(trimmedFrom, trimmedTo, commentMarkType); + tr.removeMark(trimmedFrom, trimmedTo, mark); tr.addMark( trimmedFrom, trimmedTo, From b1c9f7717d0be886b08e7029679c972aa7370cac Mon Sep 17 00:00:00 2001 From: Jonathan Date: Thu, 28 Aug 2025 17:23:54 -0700 Subject: [PATCH 4/5] release 3.5.1 (#2627) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d79bb139cd..6faa5bc44fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## vNEXT (not yet published) +## v3.5.1 + +### `@liveblocks/react-tiptap` + +- Fixes a bug where deleting a thread/comment from TipTap would also remove any + comments contained within it. + ## v3.5.0 ### `@liveblocks/node` From 66e669b44d98efd7411c201f15dce014d58ca974 Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Fri, 29 Aug 2025 00:31:06 +0000 Subject: [PATCH 5/5] Bump to 3.5.1 --- package-lock.json | 98 +++++++++---------- packages/liveblocks-client/package.json | 4 +- packages/liveblocks-core/package.json | 2 +- packages/liveblocks-emails/package.json | 6 +- packages/liveblocks-node-lexical/package.json | 6 +- .../liveblocks-node-prosemirror/package.json | 6 +- packages/liveblocks-node/package.json | 4 +- .../liveblocks-react-blocknote/package.json | 14 +-- .../liveblocks-react-lexical/package.json | 12 +-- packages/liveblocks-react-tiptap/package.json | 12 +-- packages/liveblocks-react-ui/package.json | 8 +- packages/liveblocks-react/package.json | 6 +- packages/liveblocks-redux/package.json | 6 +- packages/liveblocks-yjs/package.json | 6 +- packages/liveblocks-zustand/package.json | 6 +- 15 files changed, 98 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 34851067be6..6b6da9d1e06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37111,10 +37111,10 @@ }, "packages/liveblocks-client": { "name": "@liveblocks/client", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.5.0" + "@liveblocks/core": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37123,7 +37123,7 @@ }, "packages/liveblocks-core": { "name": "@liveblocks/core", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37141,11 +37141,11 @@ }, "packages/liveblocks-emails": { "name": "@liveblocks/emails", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0" + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37164,10 +37164,10 @@ }, "packages/liveblocks-node": { "name": "@liveblocks/node", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.5.0", + "@liveblocks/core": "3.5.1", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" @@ -37182,11 +37182,11 @@ }, "packages/liveblocks-node-lexical": { "name": "@liveblocks/node-lexical", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0", + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1", "yjs": "^13.6.18" }, "devDependencies": { @@ -37203,11 +37203,11 @@ }, "packages/liveblocks-node-prosemirror": { "name": "@liveblocks/node-prosemirror", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0", + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1", "yjs": "^13.6.20" }, "devDependencies": { @@ -37227,11 +37227,11 @@ }, "packages/liveblocks-react": { "name": "@liveblocks/react", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -37261,15 +37261,15 @@ }, "packages/liveblocks-react-blocknote": { "name": "@liveblocks/react-blocknote", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-tiptap": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-tiptap": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@tiptap/core": "^2.7.2", "vitest-tsconfig-paths": "^3.4.1" }, @@ -37306,15 +37306,15 @@ }, "packages/liveblocks-react-lexical": { "name": "@liveblocks/react-lexical", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.1", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "yjs": "^13.6.18" @@ -37843,15 +37843,15 @@ }, "packages/liveblocks-react-tiptap": { "name": "@liveblocks/react-tiptap", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "@tiptap/core": "^2.7.2", @@ -38376,13 +38376,13 @@ }, "packages/liveblocks-react-ui": { "name": "@liveblocks/react-ui", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.1.0", @@ -39710,11 +39710,11 @@ }, "packages/liveblocks-redux": { "name": "@liveblocks/redux", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", @@ -39871,10 +39871,10 @@ }, "packages/liveblocks-yjs": { "name": "@liveblocks/yjs", - "version": "3.5.0", + "version": "3.5.1", "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" @@ -39940,11 +39940,11 @@ }, "packages/liveblocks-zustand": { "name": "@liveblocks/zustand", - "version": "3.5.0", + "version": "3.5.1", "license": "Apache-2.0", "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-client/package.json b/packages/liveblocks-client/package.json index 0412988d527..924c3e2187d 100644 --- a/packages/liveblocks-client/package.json +++ b/packages/liveblocks-client/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/client", - "version": "3.5.0", + "version": "3.5.1", "description": "A client that lets you interact with Liveblocks servers. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,7 +34,7 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.5.0" + "@liveblocks/core": "3.5.1" }, "devDependencies": { "@liveblocks/eslint-config": "*", diff --git a/packages/liveblocks-core/package.json b/packages/liveblocks-core/package.json index 984e4022407..4113d6394b5 100644 --- a/packages/liveblocks-core/package.json +++ b/packages/liveblocks-core/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/core", - "version": "3.5.0", + "version": "3.5.1", "description": "Private internals for Liveblocks. DO NOT import directly from this package!", "type": "module", "main": "./dist/index.cjs", diff --git a/packages/liveblocks-emails/package.json b/packages/liveblocks-emails/package.json index c5ea8cdadbb..820e49f7a85 100644 --- a/packages/liveblocks-emails/package.json +++ b/packages/liveblocks-emails/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/emails", - "version": "3.5.0", + "version": "3.5.1", "description": "A set of functions and utilities to make sending emails based on Liveblocks notification events easy. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -35,8 +35,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0" + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc" diff --git a/packages/liveblocks-node-lexical/package.json b/packages/liveblocks-node-lexical/package.json index 0d7b2a86ac6..e9ce587b2a8 100644 --- a/packages/liveblocks-node-lexical/package.json +++ b/packages/liveblocks-node-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-lexical", - "version": "3.5.0", + "version": "3.5.1", "description": "A server-side utility that lets you modify lexical documents hosted in Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0", + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1", "yjs": "^13.6.18" }, "peerDependencies": { diff --git a/packages/liveblocks-node-prosemirror/package.json b/packages/liveblocks-node-prosemirror/package.json index 449f957e225..43d7be66016 100644 --- a/packages/liveblocks-node-prosemirror/package.json +++ b/packages/liveblocks-node-prosemirror/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node-prosemirror", - "version": "3.5.0", + "version": "3.5.1", "description": "A server-side utility that lets you modify prosemirror and tiptap documents hosted in Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.5.0", - "@liveblocks/node": "3.5.0", + "@liveblocks/core": "3.5.1", + "@liveblocks/node": "3.5.1", "yjs": "^13.6.20" }, "peerDependencies": { diff --git a/packages/liveblocks-node/package.json b/packages/liveblocks-node/package.json index 6cf3b98f0c4..a5e482b9446 100644 --- a/packages/liveblocks-node/package.json +++ b/packages/liveblocks-node/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/node", - "version": "3.5.0", + "version": "3.5.1", "description": "A server-side utility that lets you set up a Liveblocks authentication endpoint. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,7 +34,7 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/core": "3.5.0", + "@liveblocks/core": "3.5.1", "@stablelib/base64": "^1.0.1", "fast-sha256": "^1.3.0", "node-fetch": "^2.6.1" diff --git a/packages/liveblocks-react-blocknote/package.json b/packages/liveblocks-react-blocknote/package.json index e3370a261c8..3e6fbd670e4 100644 --- a/packages/liveblocks-react-blocknote/package.json +++ b/packages/liveblocks-react-blocknote/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-blocknote", - "version": "3.5.0", + "version": "3.5.1", "description": "An integration of BlockNote + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -42,12 +42,12 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-tiptap": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-tiptap": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@tiptap/core": "^2.7.2", "vitest-tsconfig-paths": "^3.4.1" }, diff --git a/packages/liveblocks-react-lexical/package.json b/packages/liveblocks-react-lexical/package.json index f3333fc9fd0..64b8e8398c2 100644 --- a/packages/liveblocks-react-lexical/package.json +++ b/packages/liveblocks-react-lexical/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-lexical", - "version": "3.5.0", + "version": "3.5.1", "description": "An integration of Lexical + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -43,11 +43,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.1", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "yjs": "^13.6.18" diff --git a/packages/liveblocks-react-tiptap/package.json b/packages/liveblocks-react-tiptap/package.json index c25b11cf934..f8197cbd096 100644 --- a/packages/liveblocks-react-tiptap/package.json +++ b/packages/liveblocks-react-tiptap/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-tiptap", - "version": "3.5.0", + "version": "3.5.1", "description": "An integration of TipTap + React to enable collaboration, comments, live cursors, and more with Liveblocks.", "license": "Apache-2.0", "type": "module", @@ -43,11 +43,11 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", - "@liveblocks/react-ui": "3.5.0", - "@liveblocks/yjs": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", + "@liveblocks/react-ui": "3.5.1", + "@liveblocks/yjs": "3.5.1", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-toggle": "^1.1.0", "@tiptap/core": "^2.7.2", diff --git a/packages/liveblocks-react-ui/package.json b/packages/liveblocks-react-ui/package.json index de81db54d89..c797f13b8a8 100644 --- a/packages/liveblocks-react-ui/package.json +++ b/packages/liveblocks-react-ui/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react-ui", - "version": "3.5.0", + "version": "3.5.1", "description": "A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -76,9 +76,9 @@ }, "dependencies": { "@floating-ui/react-dom": "^2.1.2", - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", - "@liveblocks/react": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", + "@liveblocks/react": "3.5.1", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-slot": "^1.1.0", diff --git a/packages/liveblocks-react/package.json b/packages/liveblocks-react/package.json index 605215e7950..6c781d08c32 100644 --- a/packages/liveblocks-react/package.json +++ b/packages/liveblocks-react/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/react", - "version": "3.5.0", + "version": "3.5.1", "description": "A set of React hooks and providers to use Liveblocks declaratively. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -61,8 +61,8 @@ "showdeps": "depcruise src --include-only '^src' --exclude='__tests__' --output-type dot | dot -T svg > /tmp/dependency-graph.svg && open /tmp/dependency-graph.svg" }, "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "peerDependencies": { "@types/react": "*", diff --git a/packages/liveblocks-redux/package.json b/packages/liveblocks-redux/package.json index 8ebd5ebb0b1..8ca09acff46 100644 --- a/packages/liveblocks-redux/package.json +++ b/packages/liveblocks-redux/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/redux", - "version": "3.5.0", + "version": "3.5.1", "description": "A store enhancer to integrate Liveblocks into Redux stores. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -33,8 +33,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "peerDependencies": { "redux": "^4 || ^5" diff --git a/packages/liveblocks-yjs/package.json b/packages/liveblocks-yjs/package.json index 301e86e393b..a0cfdcd6fa8 100644 --- a/packages/liveblocks-yjs/package.json +++ b/packages/liveblocks-yjs/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/yjs", - "version": "3.5.0", + "version": "3.5.1", "description": "Integrate your existing or new Yjs documents with Liveblocks.", "icense": "Apache-2.0", "type": "module", @@ -33,8 +33,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0", + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1", "@noble/hashes": "^1.8.0", "js-base64": "^3.7.7", "y-indexeddb": "^9.0.12" diff --git a/packages/liveblocks-zustand/package.json b/packages/liveblocks-zustand/package.json index d23b00067e8..60b7eb8caca 100644 --- a/packages/liveblocks-zustand/package.json +++ b/packages/liveblocks-zustand/package.json @@ -1,6 +1,6 @@ { "name": "@liveblocks/zustand", - "version": "3.5.0", + "version": "3.5.1", "description": "A middleware for Zustand to automatically synchronize your stores with Liveblocks. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.", "license": "Apache-2.0", "type": "module", @@ -34,8 +34,8 @@ "test:watch": "NODE_OPTIONS=\"--no-deprecation\" vitest" }, "dependencies": { - "@liveblocks/client": "3.5.0", - "@liveblocks/core": "3.5.0" + "@liveblocks/client": "3.5.1", + "@liveblocks/core": "3.5.1" }, "peerDependencies": { "zustand": "^5.0.1"