diff --git a/packages/db/migrations/mysql/0044_add_assistant.sql b/packages/db/migrations/mysql/0044_add_assistant.sql index 511ab6a81..46315c4aa 100644 --- a/packages/db/migrations/mysql/0044_add_assistant.sql +++ b/packages/db/migrations/mysql/0044_add_assistant.sql @@ -26,7 +26,7 @@ CREATE TABLE `assistant_message` ( `thread_id` varchar(64) NOT NULL, `parent_id` varchar(128), `format` varchar(64) DEFAULT 'ai-sdk/v6' NOT NULL, - `content` text DEFAULT ('{"json": {}}') NOT NULL, + `content` mediumtext DEFAULT ('{"json": {}}') NOT NULL, `created_at` timestamp DEFAULT (now()) NOT NULL, CONSTRAINT `assistant_message_id` PRIMARY KEY(`id`) ); diff --git a/packages/db/schema/mysql.ts b/packages/db/schema/mysql.ts index cfe52485a..9f2fc8701 100644 --- a/packages/db/schema/mysql.ts +++ b/packages/db/schema/mysql.ts @@ -8,6 +8,7 @@ import { customType, index, int, + mediumtext, mysqlTable, primaryKey, smallint, @@ -536,7 +537,9 @@ export const assistantMessages = mysqlTable( .references(() => assistantThreads.id, { onDelete: "cascade" }), parentId: varchar({ length: 128 }), format: varchar({ length: 64 }).notNull().default("ai-sdk/v6"), - content: text().default(emptySuperJSON).notNull(), + // `text` only holds 64KB on MySQL, which a single base64 image attachment already exceeds. + // `mediumtext` holds 16MB, comfortably above the 2MB request cap enforced by the chat route. + content: mediumtext().default(emptySuperJSON).notNull(), createdAt: timestamp().notNull().defaultNow(), }, (message) => ({ diff --git a/packages/db/test/mysql-migration.spec.ts b/packages/db/test/mysql-migration.spec.ts index b781a9531..9e86e2003 100644 --- a/packages/db/test/mysql-migration.spec.ts +++ b/packages/db/test/mysql-migration.spec.ts @@ -1,9 +1,10 @@ import path from "path"; import { MySqlContainer } from "@testcontainers/mysql"; +import { eq } from "drizzle-orm"; import { drizzle } from "drizzle-orm/mysql2"; import { migrate } from "drizzle-orm/mysql2/migrator"; import mysql from "mysql2"; -import { describe, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { DB_CASING } from "@homarr/core/infrastructure/db/constants"; @@ -35,6 +36,22 @@ describe("Mysql Migration", () => { // Check if users table exists await database.query.users.findMany(); + // A single base64 image attachment already exceeds the 64KB that `text` holds on MySQL, so + // `assistant_message.content` has to be `mediumtext`. Everything below 64KB passes either way. + const largeContent = "x".repeat(200_000); + await database.insert(mysqlSchema.users).values({ id: "size-check-user", name: "size-check" }); + await database.insert(mysqlSchema.assistantThreads).values({ id: "size-check-thread", userId: "size-check-user" }); + await database.insert(mysqlSchema.assistantMessages).values({ + id: "size-check-message", + threadId: "size-check-thread", + parentId: null, + content: largeContent, + }); + const [storedMessage] = await database.query.assistantMessages.findMany({ + where: eq(mysqlSchema.assistantMessages.id, "size-check-message"), + }); + expect(storedMessage?.content).toHaveLength(largeContent.length); + connection.end(); await mysqlContainer.stop(); }, 120_000);