diff --git a/bun.lock b/bun.lock index adfc9612..29ce1e0f 100644 --- a/bun.lock +++ b/bun.lock @@ -84,11 +84,13 @@ "e2b": "2.52.0", "effect": "catalog:", "linkedom": "^0.18.13", + "mime-types": "^3.0.2", "minisearch": "^7.2.0", "turndown": "^7.2.4", "undici": "7.29.1", }, "devDependencies": { + "@types/mime-types": "^3.0.1", "@types/pg": "catalog:", "@types/turndown": "^5.0.6", "pg": "^8.23.0", @@ -1076,6 +1078,8 @@ "@types/mdx": ["@types/mdx@2.0.14", "", {}, "sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg=="], + "@types/mime-types": ["@types/mime-types@3.0.1", "", {}, "sha512-xRMsfuQbnRq1Ef+C+RKaENOxXX87Ygl38W1vDfPHRku02TgQr+Qd8iivLtAMcR0KF5/29xlnFihkTlbqFrGOVQ=="], + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], "@types/node": ["@types/node@26.6.2", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g=="], diff --git a/packages/contracts/src/attachments.ts b/packages/contracts/src/attachments.ts new file mode 100644 index 00000000..4dedf3f2 --- /dev/null +++ b/packages/contracts/src/attachments.ts @@ -0,0 +1,45 @@ +import { Schema } from "effect"; +import { uuidSchema } from "./uuid.ts"; + +/** The largest file a person can attach to a message, in bytes. */ +export const MAX_ATTACHMENT_BYTES = 30 * 1024 * 1024; + +/** The most files one message can carry, and so the most a person can have uploaded and not yet sent. */ +export const MAX_ATTACHMENTS_PER_MESSAGE = 20; + +const MAX_ATTACHMENT_NAME_CHARACTERS = 255; + +/** A file's name as the person's computer had it, shown beside it and offered when it is downloaded. */ +export const attachmentNameSchema = Schema.Trim.check( + Schema.isMinLength(1), + Schema.isMaxLength(MAX_ATTACHMENT_NAME_CHARACTERS), + // Control characters would break the name out of a download's header. + Schema.isPattern(/^[^\p{Cc}]*$/u), +); + +/** + * A media type such as `image/png` or `text/plain; charset=utf-8`, served + * back as the file's `Content-Type`, so nothing in it can break out of that + * header. + */ +export const mediaTypeSchema = Schema.Trim.check( + Schema.isMaxLength(255), + Schema.isPattern(/^[\w.+-]+\/[\w.+-]+(\s*;\s*[\w.+-]+=("[^"\r\n]*"|[\w.+-]+))*$/), +); + +export const uploadAttachmentQuerySchema = Schema.Struct({ + name: attachmentNameSchema, + /** The file's media type, when the uploader knows it; otherwise it is taken from the name. */ + type: Schema.optional(mediaTypeSchema), +}); + +/** A file uploaded to a chat, ready to attach to a message. */ +export const uploadedAttachmentSchema = Schema.Struct({ + id: uuidSchema, + name: attachmentNameSchema, + contentType: mediaTypeSchema, + /** In bytes. */ + size: Schema.Int, +}); + +export type UploadedAttachment = typeof uploadedAttachmentSchema.Type; diff --git a/packages/contracts/src/http/groups/chats.ts b/packages/contracts/src/http/groups/chats.ts index cd4e3a58..4ce08eaa 100644 --- a/packages/contracts/src/http/groups/chats.ts +++ b/packages/contracts/src/http/groups/chats.ts @@ -1,5 +1,6 @@ import { Schema } from "effect"; import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "effect/unstable/httpapi"; +import { uploadAttachmentQuerySchema, uploadedAttachmentSchema } from "../../attachments.ts"; import { activityFeedSchema, approvalInboxSchema, @@ -14,7 +15,7 @@ import { } from "../../chats.ts"; import { messageSchema, newMessageSchema } from "../../threads.ts"; import { workspaceIdOrSlugSchema } from "../../workspaces.ts"; -import { BadRequest, Conflict, refused } from "../errors.ts"; +import { BadRequest, Conflict, PayloadTooLarge, refused } from "../errors.ts"; import { Session } from "../middleware.ts"; export class ChatsApi extends HttpApiGroup.make("chats") @@ -69,5 +70,22 @@ export class ChatsApi extends HttpApiGroup.make("chats") success: messageSchema.pipe(HttpApiSchema.status(201)), error: [BadRequest, Conflict, ...refused], }), + /** + * Uploads a file to attach to a message the caller then sends. The body + * is the file's bytes, and `Content-Type` its media type. + */ + HttpApiEndpoint.post("uploadFile", "/chats/:chatId/files", { + params: { chatId: Schema.String }, + query: uploadAttachmentQuerySchema, + payload: Schema.Uint8Array.pipe(HttpApiSchema.asUint8Array()), + success: uploadedAttachmentSchema.pipe(HttpApiSchema.status(201)), + error: [BadRequest, PayloadTooLarge, Conflict, ...refused], + }), + /** A file attached to a message in the chat, with its own media type. */ + HttpApiEndpoint.get("file", "/chats/:chatId/files/:fileId", { + params: { chatId: Schema.String, fileId: Schema.String }, + success: HttpApiSchema.StreamUint8Array(), + error: refused, + }), ) .middleware(Session) {} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index e3bec20c..7a6e9a1a 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -6,6 +6,7 @@ export * from "./agents.ts"; export * from "./api.ts"; export * from "./appearance.ts"; export * from "./artifacts.ts"; +export * from "./attachments.ts"; export * from "./built-in-tools.ts"; export * from "./chats.ts"; export * from "./connections.ts"; diff --git a/packages/core/drizzle/20261014032053_chat_attachments/migration.sql b/packages/core/drizzle/20261014032053_chat_attachments/migration.sql new file mode 100644 index 00000000..83f39af9 --- /dev/null +++ b/packages/core/drizzle/20261014032053_chat_attachments/migration.sql @@ -0,0 +1,25 @@ +-- Edited by hand, keep when regenerating: `kind` has no default in the schema, +-- so drizzle-kit writes this column without one, which fails on a table that +-- already has rows. The DEFAULT here fills in the rows already there, all tool +-- results, and the last statement drops it again. +ALTER TABLE "thread_file" ADD COLUMN "kind" text DEFAULT 'tool_result' NOT NULL;--> statement-breakpoint +ALTER TABLE "thread_file" ADD COLUMN "name" text;--> statement-breakpoint +ALTER TABLE "thread_file" ADD COLUMN "uploader_id" uuid;--> statement-breakpoint +ALTER TABLE "thread_file" ADD COLUMN "message_id" uuid;--> statement-breakpoint +ALTER TABLE "thread_file" ADD COLUMN "position" integer;--> statement-breakpoint +CREATE INDEX "thread_file_message_idx" ON "thread_file" ("message_id");--> statement-breakpoint +ALTER TABLE "thread_file" ADD CONSTRAINT "thread_file_uploader_id_user_id_fkey" FOREIGN KEY ("uploader_id") REFERENCES "user"("id") ON DELETE SET NULL;--> statement-breakpoint +ALTER TABLE "thread_file" ADD CONSTRAINT "thread_file_message_id_message_id_fkey" FOREIGN KEY ("message_id") REFERENCES "message"("id") ON DELETE CASCADE;--> statement-breakpoint +ALTER TABLE "thread_file" ADD CONSTRAINT "thread_file_kind_valid" CHECK ("kind" in ('tool_result', 'upload', 'agent_attachment'));--> statement-breakpoint +ALTER TABLE "thread_file" ADD CONSTRAINT "thread_file_source_valid" CHECK (case "kind" + when 'upload' then "name" is not null and "tool_call_id" is null + and ("message_id" is null) = ("position" is null) + when 'agent_attachment' then "name" is not null and "tool_call_id" is null + and "uploader_id" is null and "message_id" is not null + and "position" is not null + else "name" is null and "uploader_id" is null and "message_id" is null + and "position" is null + end);--> statement-breakpoint +-- Edited by hand, keep when regenerating: drops the DEFAULT the first statement +-- added to fill in existing rows, so every new row says its kind. +ALTER TABLE "thread_file" ALTER COLUMN "kind" DROP DEFAULT; \ No newline at end of file diff --git a/packages/core/drizzle/20261014032053_chat_attachments/snapshot.json b/packages/core/drizzle/20261014032053_chat_attachments/snapshot.json new file mode 100644 index 00000000..aa3bbe56 --- /dev/null +++ b/packages/core/drizzle/20261014032053_chat_attachments/snapshot.json @@ -0,0 +1,10139 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "ddd2fc73-0e48-404e-b9e9-bdd9548719c1", + "prevIds": [ + "728d003a-1723-43a9-8318-51496f9f557f" + ], + "ddl": [ + { + "isRlsEnabled": false, + "name": "blob", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "model_request", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "artifact", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "artifact_version", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "blob_deletion", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "chat", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "collaboration", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "message", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "message_reaction", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "routine", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "routine_execution", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread_compaction", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread_file", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread_participant", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread_read", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "thread_summary", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "tool_call", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "turn", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "event", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "connection", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "model_provider", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "provider_model", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "search_provider", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "workspace_default_model", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox_allowed_host", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox_blocked_host", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox_lease", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox_pod_allowed_host", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "sandbox_provider", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "lane", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "lane_request", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "account", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "agent", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pod", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "pod_member", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "session", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "verification", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "workspace", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "workspace_invite", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "workspace_member", + "entityType": "tables", + "schema": "public" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "bytea", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bytes", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "blob" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "purpose", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "agent_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "turn_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "step", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preset", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "outcome", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "input_tokens", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cache_read_tokens", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cache_write_tokens", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "output_tokens", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reasoning_tokens", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "numeric(18,10)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cost_usd", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cost_source", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ended_at", + "entityType": "columns", + "schema": "public", + "table": "model_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "current_version", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_agent_id", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_in_thread_id", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "artifact" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "artifact_id", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "number", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "author_agent_id", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "artifact_version" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "blob_deletion" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "prefix", + "entityType": "columns", + "schema": "public", + "table": "blob_deletion" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "queued_at", + "entityType": "columns", + "schema": "public", + "table": "blob_deletion" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host_agent_id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "main_thread_id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "initiator_user_id", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "chat" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parent_thread_id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parent_message_id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "turn_id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "child_thread_id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "collaborator_agent_id", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "brief", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'waiting'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "answer", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "at_offset", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "collaboration" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "author_user_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "author_agent_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "routine_trigger", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parts", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "mentions", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "turn_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "routine_execution_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reply_to_message_id", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "message" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "message_reaction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message_id", + "entityType": "columns", + "schema": "public", + "table": "message_reaction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "message_reaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "emoji", + "entityType": "columns", + "schema": "public", + "table": "message_reaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "message_reaction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "agent_id", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instructions", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger_kind", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cron_expression", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "schedule", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cron_timezone", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "next_scheduled_at", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "webhook_auth", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "webhook_secret", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "webhook_secret_digest", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'enabled'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'keep_in_run'", + "generated": null, + "identity": null, + "name": "results", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_agent_id", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "deleted_at", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "routine" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "routine_id", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "agent_id", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger_kind", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger_identity", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "routine_name", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "instructions", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'keep_in_run'", + "generated": null, + "identity": null, + "name": "results", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'queued'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "error", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pending_terminal_state", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pending_terminal_error", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "accepted_at", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "routine_execution" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host_agent_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "chat_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "title", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "system_agent_key", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "parent_thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "initiator_user_id", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "thread" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread_compaction" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "summary", + "entityType": "columns", + "schema": "public", + "table": "thread_compaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "history_starts_at", + "entityType": "columns", + "schema": "public", + "table": "thread_compaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kept_from", + "entityType": "columns", + "schema": "public", + "table": "thread_compaction" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "thread_compaction" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tool_call_id", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "uploader_id", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message_id", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "position", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content_type", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "size", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "thread_file" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "thread_participant" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread_participant" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "thread_participant" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "agent_id", + "entityType": "columns", + "schema": "public", + "table": "thread_participant" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "thread_participant" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "read_through", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "thread_read" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "thread_summary" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "content", + "entityType": "columns", + "schema": "public", + "table": "thread_summary" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_message_id", + "entityType": "columns", + "schema": "public", + "table": "thread_summary" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "thread_summary" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "turn_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tool", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sdk_tool_call_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "approval_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "approval_status", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "approval_reason", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "decided_by_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "decided_at", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_id", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "connection_revision", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_tool_name", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "execution_input", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "input", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "output", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'running'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "error", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "mutating", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "at_offset", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "tool_call" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "agent_id", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "trigger_message_id", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "owner", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_tokens", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_capacity", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "cancel_requested", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "runs", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "mutation_started", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "checkpoint", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "error", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "started_at", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "finished_at", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "turn" + }, + { + "type": "bigserial", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "seq", + "entityType": "columns", + "schema": "public", + "table": "event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "channel", + "entityType": "columns", + "schema": "public", + "table": "event" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "type", + "entityType": "columns", + "schema": "public", + "table": "event" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "event" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "event" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "handle", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "url", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'header'", + "generated": null, + "identity": null, + "name": "auth_kind", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret_header", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "secret_encrypted", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_encrypted", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_state", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "1", + "generated": null, + "identity": null, + "name": "configuration_revision", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "tools", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{}'", + "generated": null, + "identity": null, + "name": "tool_access", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_tested_at", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_test_error", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "connection" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preset", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "base_url", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "api_format", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "active", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "api_key_encrypted", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "custom_headers_encrypted", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "oauth_tokens_encrypted", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_tested_at", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_test_error", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "model_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model_id", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "display_name", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "capabilities", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "disabled_capabilities", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "context_length", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "provider_model" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preset", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "base_url", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "api_key_encrypted", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_tested_at", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_test_error", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "search_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_default_model" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_default_model" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "workspace_default_model" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sandbox_provider_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_sandbox_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "paused_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "last_used_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "added_by_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "blocked_by_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "sandbox_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_lease" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "holder_kind", + "entityType": "columns", + "schema": "public", + "table": "sandbox_lease" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "holder_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_lease" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_lease" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "host", + "entityType": "columns", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "added_by_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": { + "as": "settings->>'preset'", + "type": "stored" + }, + "identity": null, + "name": "preset", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "settings", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "enabled", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "api_key_encrypted", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "managed_resources", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_tested_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_test_error", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "sandbox_provider" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "key", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "subject", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'idle'", + "generated": null, + "identity": null, + "name": "state", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workflow", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "execution_id", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "lane" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "lane_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "lane_key", + "entityType": "columns", + "schema": "public", + "table": "lane_request" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workflow", + "entityType": "columns", + "schema": "public", + "table": "lane_request" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "payload", + "entityType": "columns", + "schema": "public", + "table": "lane_request" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "lane_request" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "account_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provider_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id_token", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "access_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "refresh_token_expires_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "scope", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "account" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "handle", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "system_agent_key", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "provisioned_key", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "description", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "face", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "''", + "generated": null, + "identity": null, + "name": "prompt", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'[]'", + "generated": null, + "identity": null, + "name": "disabled_tools", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "uses_sandbox", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "agent" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "owner_id", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'{\"facilitator\":false}'", + "generated": null, + "identity": null, + "name": "routing", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_id", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "pod" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "pod_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "pod_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "pod_id", + "entityType": "columns", + "schema": "public", + "table": "pod_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "pod_member" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "pod_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ip_address", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_agent", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "session" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "email_verified", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "image", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "referred_by", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "referral_code", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "identifier", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "value", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "verification" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "name", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "slug", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "logo", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'UTC'", + "generated": null, + "identity": null, + "name": "time_zone", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "setup_completed_at", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "workspace" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'pending'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "inviter_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "workspace_invite" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "uuidv7()", + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "workspace_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "workspace_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_member" + }, + { + "type": "uuid", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "workspace_member" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'member'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "workspace_member" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "now()", + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "workspace_member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": { + "name": "text_pattern_ops", + "default": false + } + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "blob_key_prefix_idx", + "entityType": "indexes", + "schema": "public", + "table": "blob" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "started_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "model_request_workspace_started_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "model_request" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"thread_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "model_request_thread_idx", + "entityType": "indexes", + "schema": "public", + "table": "model_request" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "artifact_pod_updated_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "artifact" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "artifact_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "number", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "artifact_version_number_idx", + "entityType": "indexes", + "schema": "public", + "table": "artifact_version" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "host_agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "chat_pod_host_idx", + "entityType": "indexes", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "parent_message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "collaboration_parent_message_idx", + "entityType": "indexes", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "child_thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "collaboration_child_thread_idx", + "entityType": "indexes", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "collaborator_agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "collaboration_collaborator_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "turn_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "collaboration_turn_idx", + "entityType": "indexes", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "message_thread_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "routine_execution_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"routine_execution_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "message_routine_execution_idx", + "entityType": "indexes", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "to_tsvector('english', \"content\")", + "isExpression": true, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"status\" = 'complete'", + "with": "", + "method": "gin", + "concurrently": false, + "name": "message_content_search_idx", + "entityType": "indexes", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "to_tsvector('simple', \"content\")", + "isExpression": true, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"status\" = 'complete'", + "with": "", + "method": "gin", + "concurrently": false, + "name": "message_content_simple_search_idx", + "entityType": "indexes", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "content", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": { + "name": "gin_trgm_ops", + "default": false + } + } + ], + "isUnique": false, + "where": "\"status\" = 'complete'", + "with": "", + "method": "gin", + "concurrently": false, + "name": "message_content_trigram_idx", + "entityType": "indexes", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "emoji", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "message_reaction_message_user_emoji_idx", + "entityType": "indexes", + "schema": "public", + "table": "message_reaction" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"deleted_at\" is null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_agent_name_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "next_scheduled_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"deleted_at\" is null and \"state\" = 'enabled' and \"trigger_kind\" = 'cron'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_due_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_execution_thread_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "routine_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "trigger_kind", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "trigger_identity", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"trigger_identity\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_execution_trigger_identity_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "routine_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"state\" = 'running'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_execution_running_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "routine_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "accepted_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "routine_execution_list_idx", + "entityType": "indexes", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "parent_thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "system_agent_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_system_agent_key_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_workspace_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "host_agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_pod_host_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_workspace_updated_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "host_agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_pod_host_updated_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "chat_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_chat_updated_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_file_thread_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_file_message_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"user_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_participant_user_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread_participant" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"agent_id\" is not null", + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_participant_agent_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread_participant" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "thread_read_user_thread_idx", + "entityType": "indexes", + "schema": "public", + "table": "thread_read" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tool_call_message_idx", + "entityType": "indexes", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "turn_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tool_call_turn_idx", + "entityType": "indexes", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "turn_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "sdk_tool_call_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tool_call_sdk_call_idx", + "entityType": "indexes", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "approval_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "tool_call_approval_idx", + "entityType": "indexes", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "started_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "turn_thread_started_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "trigger_message_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "agent_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "turn_trigger_agent_idx", + "entityType": "indexes", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "started_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"status\" = 'running'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "turn_active_idx", + "entityType": "indexes", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "channel", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "seq", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "event_channel_seq_idx", + "entityType": "indexes", + "schema": "public", + "table": "event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "event_created_at_idx", + "entityType": "indexes", + "schema": "public", + "table": "event" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "connection_name_idx", + "entityType": "indexes", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "handle", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "connection_handle_idx", + "entityType": "indexes", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "connection_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "connection_id_pod_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "oauth_state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "connection_oauth_state_idx", + "entityType": "indexes", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "model_provider_name_idx", + "entityType": "indexes", + "schema": "public", + "table": "model_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "model_provider_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "model_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "provider_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "model_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "provider_model_provider_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "provider_model" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "model_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "provider_model_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "provider_model" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "search_provider_workspace_idx", + "entityType": "indexes", + "schema": "public", + "table": "search_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "search_provider_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "search_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sandbox_pod_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "sandbox" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"enabled\"", + "with": "", + "method": "btree", + "concurrently": false, + "name": "sandbox_provider_enabled_idx", + "entityType": "indexes", + "schema": "public", + "table": "sandbox_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "sandbox_provider_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "sandbox_provider" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "state", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"state\" <> 'idle'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "lane_busy_idx", + "entityType": "indexes", + "schema": "public", + "table": "lane" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "subject", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": "\"state\" <> 'idle'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "lane_subject_idx", + "entityType": "indexes", + "schema": "public", + "table": "lane" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "lane_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "lane_request_order_idx", + "entityType": "indexes", + "schema": "public", + "table": "lane_request" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "account_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_name_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "handle", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_handle_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "provisioned_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_provisioned_key_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "system_agent_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_system_agent_key_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "agent_pod_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"kind\" = 'shared'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "pod_slug_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "owner_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"kind\" = 'personal'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "personal_pod_owner_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pod_id_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pod_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "pod_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pod_member_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod_member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "pod_member_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "pod_member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_token_idx", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "session_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "referral_code", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_referral_code_idx", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "referred_by", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "user_referred_by_idx", + "entityType": "indexes", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "identifier", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "verification_identifier_idx", + "entityType": "indexes", + "schema": "public", + "table": "verification" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "slug", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_slug_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_invite_workspace_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace_invite" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "email", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_invite_email_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace_invite" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_member_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace_member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "user_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_member_user_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace_member" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "workspace_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": "\"role\" = 'owner'", + "with": "", + "method": "btree", + "concurrently": false, + "name": "workspace_member_owner_idx", + "entityType": "indexes", + "schema": "public", + "table": "workspace_member" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "artifact_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "artifact_created_by_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact" + }, + { + "nameExplicit": false, + "columns": [ + "created_in_thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "artifact_created_in_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "artifact_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact" + }, + { + "nameExplicit": false, + "columns": [ + "artifact_id" + ], + "schemaTo": "public", + "tableTo": "artifact", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "artifact_version_artifact_id_artifact_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact_version" + }, + { + "nameExplicit": false, + "columns": [ + "author_agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "artifact_version_author_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "artifact_version" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "chat_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": false, + "columns": [ + "main_thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "chat_main_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": false, + "columns": [ + "initiator_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "chat_initiator_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "chat_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": true, + "columns": [ + "host_agent_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "chat_host_agent_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "chat" + }, + { + "nameExplicit": false, + "columns": [ + "parent_thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "collaboration_parent_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": false, + "columns": [ + "parent_message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "collaboration_parent_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": false, + "columns": [ + "turn_id" + ], + "schemaTo": "public", + "tableTo": "turn", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "collaboration_turn_id_turn_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": false, + "columns": [ + "child_thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "collaboration_child_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": false, + "columns": [ + "collaborator_agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "collaboration_collaborator_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "collaboration" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "message_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "author_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "message_author_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "author_agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "message_author_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "turn_id" + ], + "schemaTo": "public", + "tableTo": "turn", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "message_turn_id_turn_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "routine_execution_id" + ], + "schemaTo": "public", + "tableTo": "routine_execution", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "message_routine_execution_id_routine_execution_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "reply_to_message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "message_reply_to_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message" + }, + { + "nameExplicit": false, + "columns": [ + "message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "message_reaction_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "message_reaction_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "message_reaction" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "routine_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "routine_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": true, + "columns": [ + "agent_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "routine_agent_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine" + }, + { + "nameExplicit": false, + "columns": [ + "routine_id" + ], + "schemaTo": "public", + "tableTo": "routine", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "routine_execution_routine_id_routine_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "routine_execution_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "routine_execution_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + "agent_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "routine_execution_agent_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": true, + "columns": [ + "routine_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "routine", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "routine_execution_routine_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "routine_execution" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": false, + "columns": [ + "chat_id" + ], + "schemaTo": "public", + "tableTo": "chat", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_chat_id_chat_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": false, + "columns": [ + "parent_thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_parent_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": false, + "columns": [ + "initiator_user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "RESTRICT", + "name": "thread_initiator_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": true, + "columns": [ + "host_agent_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_host_agent_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_compaction_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_compaction" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_file_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": false, + "columns": [ + "tool_call_id" + ], + "schemaTo": "public", + "tableTo": "tool_call", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "thread_file_tool_call_id_tool_call_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": false, + "columns": [ + "uploader_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "thread_file_uploader_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": false, + "columns": [ + "message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_file_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_file" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_participant_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_participant" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_participant_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_participant" + }, + { + "nameExplicit": false, + "columns": [ + "agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_participant_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_participant" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_read_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_read" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_read_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_read" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_summary_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_summary" + }, + { + "nameExplicit": false, + "columns": [ + "source_message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "thread_summary_source_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "thread_summary" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tool_call_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": false, + "columns": [ + "message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tool_call_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": false, + "columns": [ + "turn_id" + ], + "schemaTo": "public", + "tableTo": "turn", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "tool_call_turn_id_turn_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": false, + "columns": [ + "decided_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "tool_call_decided_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "tool_call" + }, + { + "nameExplicit": false, + "columns": [ + "thread_id" + ], + "schemaTo": "public", + "tableTo": "thread", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "turn_thread_id_thread_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": false, + "columns": [ + "agent_id" + ], + "schemaTo": "public", + "tableTo": "agent", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "turn_agent_id_agent_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": false, + "columns": [ + "trigger_message_id" + ], + "schemaTo": "public", + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "turn_trigger_message_id_message_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "turn" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "connection_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "connection_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "connection_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "connection" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "model_provider_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "model_provider" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "model_provider_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "model_provider" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "provider_model_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "provider_model" + }, + { + "nameExplicit": true, + "columns": [ + "provider_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "model_provider", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "provider_model_provider_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "provider_model" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "search_provider_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "search_provider" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "search_provider_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "search_provider" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "workspace_default_model_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "workspace_default_model" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox" + }, + { + "nameExplicit": true, + "columns": [ + "sandbox_provider_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "sandbox_provider", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_provider_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_allowed_host_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "nameExplicit": false, + "columns": [ + "added_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "sandbox_allowed_host_added_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_blocked_host_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "nameExplicit": false, + "columns": [ + "blocked_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "sandbox_blocked_host_blocked_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "nameExplicit": false, + "columns": [ + "sandbox_id" + ], + "schemaTo": "public", + "tableTo": "sandbox", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_lease_sandbox_id_sandbox_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_lease" + }, + { + "nameExplicit": false, + "columns": [ + "added_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "sandbox_pod_allowed_host_added_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_pod_allowed_host_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "sandbox_provider_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_provider" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "sandbox_provider_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "sandbox_provider" + }, + { + "nameExplicit": false, + "columns": [ + "lane_key" + ], + "schemaTo": "public", + "tableTo": "lane", + "columnsTo": [ + "key" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "lane_request_lane_key_lane_key_fkey", + "entityType": "fks", + "schema": "public", + "table": "lane_request" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "account_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "account" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "agent_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "agent_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "agent_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "agent" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pod_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": false, + "columns": [ + "created_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "pod_created_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + "workspace_id", + "owner_id" + ], + "schemaTo": "public", + "tableTo": "workspace_member", + "columnsTo": [ + "workspace_id", + "user_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pod_owner_workspace_member_fkey", + "entityType": "fks", + "schema": "public", + "table": "pod" + }, + { + "nameExplicit": true, + "columns": [ + "pod_id", + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "pod", + "columnsTo": [ + "id", + "workspace_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pod_member_pod_workspace_fkey", + "entityType": "fks", + "schema": "public", + "table": "pod_member" + }, + { + "nameExplicit": true, + "columns": [ + "workspace_id", + "user_id" + ], + "schemaTo": "public", + "tableTo": "workspace_member", + "columnsTo": [ + "workspace_id", + "user_id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "pod_member_workspace_member_fkey", + "entityType": "fks", + "schema": "public", + "table": "pod_member" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "session_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "session" + }, + { + "nameExplicit": false, + "columns": [ + "referred_by" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "name": "user_referred_by_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "workspace_invite_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "workspace_invite" + }, + { + "nameExplicit": false, + "columns": [ + "inviter_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "workspace_invite_inviter_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "workspace_invite" + }, + { + "nameExplicit": false, + "columns": [ + "workspace_id" + ], + "schemaTo": "public", + "tableTo": "workspace", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "workspace_member_workspace_id_workspace_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "workspace_member" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "name": "workspace_member_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "workspace_member" + }, + { + "columns": [ + "workspace_id", + "host" + ], + "nameExplicit": false, + "name": "sandbox_allowed_host_pkey", + "entityType": "pks", + "schema": "public", + "table": "sandbox_allowed_host" + }, + { + "columns": [ + "workspace_id", + "host" + ], + "nameExplicit": false, + "name": "sandbox_blocked_host_pkey", + "entityType": "pks", + "schema": "public", + "table": "sandbox_blocked_host" + }, + { + "columns": [ + "sandbox_id", + "holder_kind", + "holder_id" + ], + "nameExplicit": false, + "name": "sandbox_lease_pkey", + "entityType": "pks", + "schema": "public", + "table": "sandbox_lease" + }, + { + "columns": [ + "pod_id", + "host" + ], + "nameExplicit": false, + "name": "sandbox_pod_allowed_host_pkey", + "entityType": "pks", + "schema": "public", + "table": "sandbox_pod_allowed_host" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "blob_pkey", + "schema": "public", + "table": "blob", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "model_request_pkey", + "schema": "public", + "table": "model_request", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "artifact_pkey", + "schema": "public", + "table": "artifact", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "artifact_version_pkey", + "schema": "public", + "table": "artifact_version", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "blob_deletion_pkey", + "schema": "public", + "table": "blob_deletion", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "chat_pkey", + "schema": "public", + "table": "chat", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "collaboration_pkey", + "schema": "public", + "table": "collaboration", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pkey", + "schema": "public", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_reaction_pkey", + "schema": "public", + "table": "message_reaction", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "routine_pkey", + "schema": "public", + "table": "routine", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "routine_execution_pkey", + "schema": "public", + "table": "routine_execution", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "thread_pkey", + "schema": "public", + "table": "thread", + "entityType": "pks" + }, + { + "columns": [ + "thread_id" + ], + "nameExplicit": false, + "name": "thread_compaction_pkey", + "schema": "public", + "table": "thread_compaction", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "thread_file_pkey", + "schema": "public", + "table": "thread_file", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "thread_participant_pkey", + "schema": "public", + "table": "thread_participant", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "thread_read_pkey", + "schema": "public", + "table": "thread_read", + "entityType": "pks" + }, + { + "columns": [ + "thread_id" + ], + "nameExplicit": false, + "name": "thread_summary_pkey", + "schema": "public", + "table": "thread_summary", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "tool_call_pkey", + "schema": "public", + "table": "tool_call", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "turn_pkey", + "schema": "public", + "table": "turn", + "entityType": "pks" + }, + { + "columns": [ + "seq" + ], + "nameExplicit": false, + "name": "event_pkey", + "schema": "public", + "table": "event", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "connection_pkey", + "schema": "public", + "table": "connection", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "model_provider_pkey", + "schema": "public", + "table": "model_provider", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "provider_model_pkey", + "schema": "public", + "table": "provider_model", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "search_provider_pkey", + "schema": "public", + "table": "search_provider", + "entityType": "pks" + }, + { + "columns": [ + "workspace_id" + ], + "nameExplicit": false, + "name": "workspace_default_model_pkey", + "schema": "public", + "table": "workspace_default_model", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sandbox_pkey", + "schema": "public", + "table": "sandbox", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sandbox_provider_pkey", + "schema": "public", + "table": "sandbox_provider", + "entityType": "pks" + }, + { + "columns": [ + "key" + ], + "nameExplicit": false, + "name": "lane_pkey", + "schema": "public", + "table": "lane", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "lane_request_pkey", + "schema": "public", + "table": "lane_request", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pkey", + "schema": "public", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "agent_pkey", + "schema": "public", + "table": "agent", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pod_pkey", + "schema": "public", + "table": "pod", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pod_member_pkey", + "schema": "public", + "table": "pod_member", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pkey", + "schema": "public", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "user_pkey", + "schema": "public", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "verification_pkey", + "schema": "public", + "table": "verification", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pkey", + "schema": "public", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_invite_pkey", + "schema": "public", + "table": "workspace_invite", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_member_pkey", + "schema": "public", + "table": "workspace_member", + "entityType": "pks" + }, + { + "value": "\"purpose\" in ('agent-turn', 'facilitation', 'summary', 'compaction', 'trial', 'provider-check')", + "name": "model_request_purpose_valid", + "entityType": "checks", + "schema": "public", + "table": "model_request" + }, + { + "value": "\"outcome\" in ('started', 'completed', 'failed', 'aborted')", + "name": "model_request_outcome_valid", + "entityType": "checks", + "schema": "public", + "table": "model_request" + }, + { + "value": "(\"outcome\" = 'started') = (\"ended_at\" is null)", + "name": "model_request_ended_valid", + "entityType": "checks", + "schema": "public", + "table": "model_request" + }, + { + "value": "(\"cost_usd\" is null) = (\"cost_source\" is null) and (\"cost_usd\" is null or \"cost_usd\" >= 0)", + "name": "model_request_cost_valid", + "entityType": "checks", + "schema": "public", + "table": "model_request" + }, + { + "value": "\"kind\" in ('document', 'html')", + "name": "artifact_kind_valid", + "entityType": "checks", + "schema": "public", + "table": "artifact" + }, + { + "value": "num_nonnulls(\"author_user_id\", \"author_agent_id\", \"routine_trigger\") = 1", + "name": "message_one_author", + "entityType": "checks", + "schema": "public", + "table": "message" + }, + { + "value": "\"state\" in ('enabled', 'paused')", + "name": "routine_state_valid", + "entityType": "checks", + "schema": "public", + "table": "routine" + }, + { + "value": "\"results\" in ('keep_in_run', 'post_to_chat')", + "name": "routine_results_valid", + "entityType": "checks", + "schema": "public", + "table": "routine" + }, + { + "value": "\"created_by_agent_id\" is null or \"created_by_agent_id\" = \"agent_id\"", + "name": "routine_created_by_agent_valid", + "entityType": "checks", + "schema": "public", + "table": "routine" + }, + { + "value": "(\n\t\t\t\t\"trigger_kind\" = 'cron'\n\t\t\t\tand (\"cron_expression\" is null) <> (\"schedule\" is null)\n\t\t\t\tand \"cron_timezone\" is not null\n\t\t\t\tand \"webhook_auth\" is null\n\t\t\t\tand \"webhook_secret\" is null\n\t\t\t\tand \"webhook_secret_digest\" is null\n\t\t\t) or (\n\t\t\t\t\"trigger_kind\" = 'webhook'\n\t\t\t\tand \"cron_expression\" is null\n\t\t\t\tand \"schedule\" is null\n\t\t\t\tand \"cron_timezone\" is null\n\t\t\t\tand \"next_scheduled_at\" is null\n\t\t\t\tand \"webhook_auth\" is not null\n\t\t\t\tand (\"webhook_secret\" is null or \"webhook_secret_digest\" is null)\n\t\t\t\tand (\"webhook_secret_digest\" is null or \"webhook_auth\"->>'kind' = 'bearer')\n\t\t\t)", + "name": "routine_trigger_valid", + "entityType": "checks", + "schema": "public", + "table": "routine" + }, + { + "value": "\"trigger_kind\" <> 'cron'\n\t\t\t\tor (\"state\" = 'enabled' and \"next_scheduled_at\" is not null)\n\t\t\t\tor (\"state\" = 'paused' and \"next_scheduled_at\" is null)", + "name": "routine_schedule_state_valid", + "entityType": "checks", + "schema": "public", + "table": "routine" + }, + { + "value": "\"trigger_kind\" in ('cron', 'webhook', 'manual')\n\t\t\t\tand \"trigger\"->>'kind' = \"trigger_kind\"\n\t\t\t\tand (\"trigger_kind\" = 'webhook' or \"trigger_identity\" is not null)", + "name": "routine_execution_trigger_valid", + "entityType": "checks", + "schema": "public", + "table": "routine_execution" + }, + { + "value": "(\n\t\t\t\t\"state\" = 'queued'\n\t\t\t\tand \"started_at\" is null\n\t\t\t\tand \"finished_at\" is null\n\t\t\t\tand \"error\" is null\n\t\t\t) or (\n\t\t\t\t\"state\" = 'running'\n\t\t\t\tand \"started_at\" is not null\n\t\t\t\tand \"finished_at\" is null\n\t\t\t) or (\n\t\t\t\t\"state\" in ('completed', 'failed', 'cancelled')\n\t\t\t\tand \"finished_at\" is not null\n\t\t\t)", + "name": "routine_execution_state_valid", + "entityType": "checks", + "schema": "public", + "table": "routine_execution" + }, + { + "value": "(\"pending_terminal_state\" is null and \"pending_terminal_error\" is null)\n\t\t\t\tor (\"state\" = 'running' and \"pending_terminal_state\" = 'cancelled' and \"pending_terminal_error\" is null)\n\t\t\t\tor (\"state\" = 'running' and \"pending_terminal_state\" = 'failed')", + "name": "routine_execution_pending_terminal_valid", + "entityType": "checks", + "schema": "public", + "table": "routine_execution" + }, + { + "value": "\"results\" in ('keep_in_run', 'post_to_chat')", + "name": "routine_execution_results_valid", + "entityType": "checks", + "schema": "public", + "table": "routine_execution" + }, + { + "value": "\"type\" in ('chat', 'collaboration', 'routine', 'system_agent')", + "name": "thread_type_valid", + "entityType": "checks", + "schema": "public", + "table": "thread" + }, + { + "value": "\"kind\" in ('tool_result', 'upload', 'agent_attachment')", + "name": "thread_file_kind_valid", + "entityType": "checks", + "schema": "public", + "table": "thread_file" + }, + { + "value": "case \"kind\"\n\t\t\t\twhen 'upload' then \"name\" is not null and \"tool_call_id\" is null\n\t\t\t\t\tand (\"message_id\" is null) = (\"position\" is null)\n\t\t\t\twhen 'agent_attachment' then \"name\" is not null and \"tool_call_id\" is null\n\t\t\t\t\tand \"uploader_id\" is null and \"message_id\" is not null\n\t\t\t\t\tand \"position\" is not null\n\t\t\t\telse \"name\" is null and \"uploader_id\" is null and \"message_id\" is null\n\t\t\t\t\tand \"position\" is null\n\t\t\tend", + "name": "thread_file_source_valid", + "entityType": "checks", + "schema": "public", + "table": "thread_file" + }, + { + "value": "num_nonnulls(\"user_id\", \"agent_id\") = 1", + "name": "thread_participant_one_identity", + "entityType": "checks", + "schema": "public", + "table": "thread_participant" + }, + { + "value": "\"holder_kind\" in ('turn', 'viewer')", + "name": "sandbox_lease_holder_kind_check", + "entityType": "checks", + "schema": "public", + "table": "sandbox_lease" + }, + { + "value": "\"preset\" in ('opensandbox', 'e2b')", + "name": "sandbox_provider_preset_check", + "entityType": "checks", + "schema": "public", + "table": "sandbox_provider" + }, + { + "value": "\"state\" in ('idle', 'starting', 'running')", + "name": "lane_state_check", + "entityType": "checks", + "schema": "public", + "table": "lane" + }, + { + "value": "(\"state\" = 'idle') = (\"execution_id\" is null)", + "name": "lane_execution_check", + "entityType": "checks", + "schema": "public", + "table": "lane" + }, + { + "value": "(\"system_agent_key\" is null) = (\"pod_id\" is not null)", + "name": "agent_placement_check", + "entityType": "checks", + "schema": "public", + "table": "agent" + }, + { + "value": "\"kind\" in ('personal', 'shared')", + "name": "pod_kind_check", + "entityType": "checks", + "schema": "public", + "table": "pod" + }, + { + "value": "\"kind\" = 'shared' or \"owner_id\" is not null", + "name": "pod_personal_owner_check", + "entityType": "checks", + "schema": "public", + "table": "pod" + }, + { + "value": "(\"kind\" = 'personal') = (\"slug\" = 'personal')", + "name": "pod_personal_slug_check", + "entityType": "checks", + "schema": "public", + "table": "pod" + }, + { + "value": "\"role\" in ('admin', 'member', 'viewer')", + "name": "workspace_invite_role_check", + "entityType": "checks", + "schema": "public", + "table": "workspace_invite" + }, + { + "value": "\"status\" in ('pending', 'accepted', 'canceled')", + "name": "workspace_invite_status_check", + "entityType": "checks", + "schema": "public", + "table": "workspace_invite" + }, + { + "value": "\"role\" in ('owner', 'admin', 'member', 'viewer')", + "name": "workspace_member_role_check", + "entityType": "checks", + "schema": "public", + "table": "workspace_member" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index da40d16e..bc933e06 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,11 +38,13 @@ "e2b": "2.52.0", "effect": "catalog:", "linkedom": "^0.18.13", + "mime-types": "^3.0.2", "minisearch": "^7.2.0", "turndown": "^7.2.4", "undici": "7.29.1" }, "devDependencies": { + "@types/mime-types": "^3.0.1", "@types/pg": "catalog:", "@types/turndown": "^5.0.6", "pg": "^8.23.0" diff --git a/packages/core/src/conversations/chats/chat-files.ts b/packages/core/src/conversations/chats/chat-files.ts new file mode 100644 index 00000000..ae4e16b4 --- /dev/null +++ b/packages/core/src/conversations/chats/chat-files.ts @@ -0,0 +1,99 @@ +export * as ChatFiles from "./chat-files.ts"; + +import { MAX_ATTACHMENT_BYTES, MAX_ATTACHMENTS_PER_MESSAGE } from "@sugabots/contracts"; +import { Context, Effect, Layer, type Stream } from "effect"; +import { contentType as mediaTypeForName } from "mime-types"; +import type { ResourceHidden } from "../../authorization/access.ts"; +import { CurrentActor } from "../../authorization/current-actor.ts"; +import { Visibility } from "../../authorization/visibility.ts"; +import type { BlobStore } from "../../blob-store/blob-store.ts"; +import { serviceOperations } from "../../database/database.ts"; +import { ThreadFiles } from "../thread-files/thread-files.ts"; + +/** + * Files people attach to messages in a chat, kept in its main thread. Anyone + * who can see the chat can upload to it, and read what was attached there. + */ +export interface Interface { + /** + * Keeps `stream` as an upload the actor can attach to a message they send + * to the chat, as `type`, or, when that is missing or says only that it is + * bytes, as the type its name suggests. Fails with `FileTooLarge` past + * `MAX_ATTACHMENT_BYTES`, or below it past what the thread keeps, and with + * `TooManyUnsentUploads` once the actor has a message's worth unsent. + */ + readonly upload: (input: { + chatId: string; + name: string; + type: string | undefined; + stream: Stream.Stream; + }) => Effect.Effect< + ThreadFiles.Upload, + | E + | ResourceHidden + | ThreadFiles.FileTooLarge + | ThreadFiles.TooManyUnsentUploads + | BlobStore.Failure, + CurrentActor.Service + >; + /** A file attached to a message in the chat. An upload not yet sent is not found. */ + readonly read: (input: { + chatId: string; + fileId: string; + }) => Effect.Effect< + ThreadFiles.StoredFile, + ResourceHidden | ThreadFiles.FileNotFound | BlobStore.Failure, + CurrentActor.Service + >; +} + +export class Service extends Context.Service()("@sugabots/core/ChatFiles") {} + +export const make = Effect.gen(function* () { + const operation = yield* serviceOperations("ChatFiles"); + const visibility = yield* Visibility.Service; + const files = yield* ThreadFiles.Service; + + return Service.of({ + upload: ({ chatId, name, type, stream }) => + operation( + "upload", + Effect.gen(function* () { + const { mainThreadId } = yield* visibility.chat(chatId); + const { userId } = yield* CurrentActor.Service; + return yield* files.upload({ + threadId: mainThreadId, + uploaderId: userId, + name, + maxSize: MAX_ATTACHMENT_BYTES, + maxUnsent: MAX_ATTACHMENTS_PER_MESSAGE, + content: { stream, contentType: mediaTypeOf(name, type) }, + }); + }), + ), + read: ({ chatId, fileId }) => + operation( + "read", + Effect.flatMap(visibility.chat(chatId), ({ mainThreadId }) => + files.read({ threadId: mainThreadId, fileId }), + ), + ), + }); +}); + +export const layerNoDeps = Layer.effect(Service, make); + +export const layer = layerNoDeps.pipe(Layer.provide([Visibility.layer, ThreadFiles.layer])); + +/** What a browser calls a file whose type it does not know. */ +const UNKNOWN_MEDIA_TYPE = "application/octet-stream"; + +/** + * `declared`, unless it is missing or only says the file is bytes: browsers + * report no type for many text files, such as Markdown or source code, which + * bots could then not read as text. + */ +function mediaTypeOf(name: string, declared: string | undefined): string { + if (declared !== undefined && declared !== UNKNOWN_MEDIA_TYPE) return declared; + return mediaTypeForName(name) || UNKNOWN_MEDIA_TYPE; +} diff --git a/packages/core/src/conversations/chats/chats.test.ts b/packages/core/src/conversations/chats/chats.test.ts index a3c9ae7b..14ea69e9 100644 --- a/packages/core/src/conversations/chats/chats.test.ts +++ b/packages/core/src/conversations/chats/chats.test.ts @@ -1,6 +1,6 @@ import { type ChatMessageItem, handleFromName, threadChannel } from "@sugabots/contracts"; import { and, eq, like } from "drizzle-orm"; -import { Context, Effect } from "effect"; +import { Context, Effect, Stream } from "effect"; import { afterAll, beforeEach, describe, expect, it } from "vitest"; import { Visibility } from "../../authorization/visibility.ts"; import { EventBus } from "../../database/events/bus.ts"; @@ -32,6 +32,7 @@ import { conversationsForTests } from "../testing.ts"; import { ThreadView } from "../thread-view.ts"; import { queueFacilitationForTests, runningTurns } from "../turns/testing.ts"; import { Turns } from "../turns/turns.ts"; +import { ChatFiles } from "./chat-files.ts"; import { ChatView } from "./chat-view.ts"; import { Chats } from "./chats.ts"; @@ -1463,6 +1464,29 @@ describe.skipIf(!process.env.DATABASE_URL)("chats, against Postgres", async () = ); }); }); + + it("keeps an uploaded file as the type it was given, or else the type its name suggests", async () => { + const { id: chatId } = await chats.open({ + workspace: workspaceId, + podId, + hostAgentId: agentId, + }); + const files = onPostgresAs(userId)(Context.get(conversations, ChatFiles.Service)); + const upload = (name: string, type: string | undefined) => + files.upload({ chatId, name, type, stream: Stream.succeed(new TextEncoder().encode("x")) }); + + // Browsers give no type for many text files, which bots must still read as text. + expect(await upload("notes.md", undefined)).toMatchObject({ + contentType: "text/markdown; charset=utf-8", + }); + expect(await upload("report.csv", "application/octet-stream")).toMatchObject({ + contentType: "text/csv; charset=utf-8", + }); + expect(await upload("chart", "image/png")).toMatchObject({ contentType: "image/png" }); + expect(await upload("data", undefined)).toMatchObject({ + contentType: "application/octet-stream", + }); + }); }); function timelineItemId(item: ChatMessageItem) { diff --git a/packages/core/src/conversations/conversations.ts b/packages/core/src/conversations/conversations.ts index 1356cd39..0b12f941 100644 --- a/packages/core/src/conversations/conversations.ts +++ b/packages/core/src/conversations/conversations.ts @@ -3,6 +3,7 @@ export * as Conversations from "./conversations.ts"; import { Context, Effect, Layer } from "effect"; import { DomainEvents } from "../database/events/domain-events.ts"; import { EventOutbox } from "../database/events/outbox.ts"; +import { ChatFiles } from "./chats/chat-files.ts"; import { ChatView } from "./chats/chat-view.ts"; import { Chats } from "./chats/chats.ts"; import { Compactions } from "./compaction/compactions.ts"; @@ -31,6 +32,7 @@ import { Turns } from "./turns/turns.ts"; const services = Layer.mergeAll( Chats.layer, Reactions.layer, + ChatFiles.layer, Collaborations.layer, Routines.layer, Routines.webhooksLayer, diff --git a/packages/core/src/conversations/sql.ts b/packages/core/src/conversations/sql.ts index 3442c817..cadd697e 100644 --- a/packages/core/src/conversations/sql.ts +++ b/packages/core/src/conversations/sql.ts @@ -577,6 +577,12 @@ export const toolCall = pgTable( export type ToolCallRow = typeof toolCall.$inferSelect; +/** + * Where a thread's file came from: a tool's result too large to hand a model + * whole, a person's upload, or a file an agent attached to its own reply. + */ +export type ThreadFileKind = "tool_result" | "upload" | "agent_attachment"; + /** * A file in a thread, which its agents can read. Its bytes are in the blob * store, under the thread's folder (see `thread-files/thread-files.ts`). @@ -588,14 +594,45 @@ export const threadFile = pgTable( threadId: uuid("thread_id") .notNull() .references(() => thread.id, { onDelete: "cascade" }), - /** The call whose result this is. */ + kind: text("kind").$type().notNull(), + /** For a tool result: the call whose result this is. */ toolCallId: uuid("tool_call_id").references(() => toolCall.id, { onDelete: "set null" }), + /** For an upload, the file's name as the person's computer had it; for an agent's attachment, the name it gave. */ + name: text("name"), + /** For an upload: who uploaded it, the only one who may attach it while it is unsent. */ + uploaderId: uuid("uploader_id").references(() => user.id, { onDelete: "set null" }), + /** + * For an upload, the message it is attached to, unset until that message + * is sent; for an agent's attachment, the reply it was attached to. + */ + messageId: uuid("message_id").references(() => message.id, { onDelete: "cascade" }), + /** For a file on a message: its place among the message's files, from 0. */ + position: integer("position"), contentType: text("content_type").notNull(), /** In bytes. */ size: integer("size").notNull(), createdAt: stamp("created_at"), }, - (table) => [index("thread_file_thread_idx").on(table.threadId)], + (table) => [ + index("thread_file_thread_idx").on(table.threadId), + index("thread_file_message_idx").on(table.messageId), + check( + "thread_file_kind_valid", + sql`${table.kind} in ('tool_result', 'upload', 'agent_attachment')`, + ), + check( + "thread_file_source_valid", + sql`case ${table.kind} + when 'upload' then ${table.name} is not null and ${table.toolCallId} is null + and (${table.messageId} is null) = (${table.position} is null) + when 'agent_attachment' then ${table.name} is not null and ${table.toolCallId} is null + and ${table.uploaderId} is null and ${table.messageId} is not null + and ${table.position} is not null + else ${table.name} is null and ${table.uploaderId} is null and ${table.messageId} is null + and ${table.position} is null + end`, + ), + ], ); export const threadSummary = pgTable("thread_summary", { diff --git a/packages/core/src/conversations/thread-files/thread-files.test.ts b/packages/core/src/conversations/thread-files/thread-files.test.ts index 00fcc918..2a6b54cf 100644 --- a/packages/core/src/conversations/thread-files/thread-files.test.ts +++ b/packages/core/src/conversations/thread-files/thread-files.test.ts @@ -1,5 +1,5 @@ import { eq } from "drizzle-orm"; -import { ConfigProvider, Effect, Layer, Stream } from "effect"; +import { ConfigProvider, Duration, Effect, Layer, Stream } from "effect"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { BlobStore } from "../../blob-store/blob-store.ts"; import { BlobDeletions } from "../../blob-store/deletions.ts"; @@ -46,7 +46,12 @@ describe.skipIf(!process.env.DATABASE_URL)("thread files, against Postgres", () .returning(), ); if (!created) throw new Error("Could not create thread"); - return { threadId: created.id, podId: owner.podId, agentId: owner.agentId }; + return { + threadId: created.id, + podId: owner.podId, + agentId: owner.agentId, + userId: owner.userId, + }; } /** A finished call to a tool in a turn of `agentId`'s in `threadId`. */ @@ -170,6 +175,136 @@ describe.skipIf(!process.env.DATABASE_URL)("thread files, against Postgres", () ).toEqual([]); }); + /** A message from `userId` in `threadId`, that files can be attached to. */ + async function aMessage(threadId: string, userId: string) { + const [sent] = await onDatabase((db) => + db + .insert(message) + .values({ + threadId, + authorUserId: userId, + kind: "text", + status: "complete", + parts: [], + content: "", + }) + .returning({ id: message.id }), + ); + if (!sent) throw new Error("Could not create message"); + return sent.id; + } + + const attach = (fileId: string, messageId: string) => + onDatabase((db) => + db.update(threadFile).set({ messageId, position: 0 }).where(eq(threadFile.id, fileId)), + ); + + it("keeps an upload unreadable until it is attached to a message", async () => { + const { threadId, userId } = await aThread(); + const uploaded = await files.upload({ + threadId, + uploaderId: userId, + name: "notes.txt", + maxSize: 1024, + maxUnsent: 20, + content: text("draft"), + }); + + await expect(files.read({ threadId, fileId: uploaded.id })).rejects.toBeInstanceOf( + ThreadFiles.FileNotFound, + ); + + await attach(uploaded.id, await aMessage(threadId, userId)); + const read = await files.read({ threadId, fileId: uploaded.id }); + + expect(uploaded).toEqual({ + id: expect.any(String), + name: "notes.txt", + contentType: "text/plain; charset=utf-8", + size: 5, + }); + expect(read).toMatchObject({ name: "notes.txt", size: 5 }); + expect(await textOf(read.stream)).toBe("draft"); + }); + + it("refuses an upload over its own limit, keeping nothing", async () => { + const { threadId, userId } = await aThread(); + + await expect( + files.upload({ + threadId, + uploaderId: userId, + name: "big.bin", + maxSize: 4, + maxUnsent: 20, + content: text("too long"), + }), + ).rejects.toEqual(new ThreadFiles.FileTooLarge({ maxSize: 4 })); + expect( + await onDatabase((db) => + db.select().from(threadFile).where(eq(threadFile.threadId, threadId)), + ), + ).toEqual([]); + }); + + it("refuses an upload past its uploader's unsent ones, counting only those", async () => { + const { threadId, userId } = await aThread(); + const other = await aRoutineOwner(); + const upload = (uploaderId: string, name: string) => + files.upload({ + threadId, + uploaderId, + name, + maxSize: 1024, + maxUnsent: 2, + content: text(name), + }); + const first = await upload(userId, "one.txt"); + await upload(userId, "two.txt"); + + await expect(upload(userId, "three.txt")).rejects.toEqual( + new ThreadFiles.TooManyUnsentUploads({ maxUnsent: 2 }), + ); + // Someone else's unsent uploads are theirs to count. + await expect(upload(other.userId, "theirs.txt")).resolves.toMatchObject({ name: "theirs.txt" }); + // Sending one frees its place. + await attach(first.id, await aMessage(threadId, userId)); + await expect(upload(userId, "three.txt")).resolves.toMatchObject({ name: "three.txt" }); + }); + + it("removes uploads left unsent, with their blobs, and nothing else", async () => { + const { threadId, userId } = await aThread(); + const upload = (name: string) => + files.upload({ + threadId, + uploaderId: userId, + name, + maxSize: 1024, + maxUnsent: 20, + content: text(name), + }); + await upload("abandoned.txt"); + const sent = await upload("sent.txt"); + await attach(sent.id, await aMessage(threadId, userId)); + const result = await files.write({ threadId, content: text("a tool's result") }); + const blobsUnder = () => + runOnPostgres( + Effect.flatMap(BlobStore.Service, (blobs) => + blobs.list(BlobStore.key("threads", threadId)), + ), + ); + + await files.removeUnsentUploads(Duration.zero); + + const left = await onDatabase((db) => + db.select({ id: threadFile.id }).from(threadFile).where(eq(threadFile.threadId, threadId)), + ); + expect(left.map(({ id }) => id).sort()).toEqual([sent.id, result.id].sort()); + expect((await blobsUnder()).map(({ key }) => key).sort()).toEqual( + [sent.id, result.id].map((id) => BlobStore.key("threads", threadId, "files", id)).sort(), + ); + }); + it("deletes a thread's blobs once a cascade has deleted the thread", async () => { const { threadId, podId } = await aThread(); const written = await files.write({ threadId, content: text("kept until the pod goes") }); diff --git a/packages/core/src/conversations/thread-files/thread-files.ts b/packages/core/src/conversations/thread-files/thread-files.ts index d2c5e248..642274fe 100644 --- a/packages/core/src/conversations/thread-files/thread-files.ts +++ b/packages/core/src/conversations/thread-files/thread-files.ts @@ -1,15 +1,22 @@ export * as ThreadFiles from "./thread-files.ts"; -import { and, eq, isNotNull } from "drizzle-orm"; -import { Config, Context, Data, Effect, Layer, Option, Schema, Stream } from "effect"; +import { userText } from "@sugabots/errors"; +import { and, count, eq, isNotNull, lt, not, sql } from "drizzle-orm"; +import { Config, Context, Data, Duration, Effect, Layer, Option, Schema, Stream } from "effect"; import { BlobStore } from "../../blob-store/blob-store.ts"; import { query, serviceOperations } from "../../database/database.ts"; import { threadFile } from "../../database/schema.ts"; import { Ids, isUuid } from "../../ids/ids.ts"; +import type { UserFacing } from "../../user-message.ts"; /** - * The only reader and writer of `thread_file`: files in a thread, which only - * that thread's agents can read. A file goes when its thread does. + * The only reader and writer of `thread_file`: files in a thread, which its + * agents read, and people too when a chat serves them. A file goes when its + * thread does. + * + * A file is a tool's result, kept with `write`, or a person's upload, kept + * with `upload`. An upload stays unsent, and unreadable, until it is attached + * to a message; one never attached is removed by `removeUnsentUploads`. */ export interface Interface { /** @@ -23,20 +30,40 @@ export interface Interface { toolCallId?: string; content: BlobStore.Content; }) => Effect.Effect; + /** + * Keeps `content` as a file `uploaderId` uploaded to the thread, unsent + * until it is attached to a message. Fails as `write` does, with + * `FileTooLarge` past `maxSize` bytes, and with `TooManyUnsentUploads`, + * keeping nothing, when the uploader already has `maxUnsent` unsent + * uploads in the thread. Uploads at the same moment may each pass that + * check, so it bounds them only roughly. + */ + readonly upload: (file: { + threadId: string; + uploaderId: string; + name: string; + maxSize: number; + maxUnsent: number; + content: BlobStore.Content; + }) => Effect.Effect; /** * The file. Fails with `FileNotFound` when no file in the thread has that - * id, including when `fileId` is not an id at all. The stream fails with - * `FileNotFound` if the thread is deleted while it is read. + * id, including when `fileId` is not an id at all, and for an unsent + * upload. The stream fails with `FileNotFound` if the thread is deleted + * while it is read. */ readonly read: (file: { threadId: string; fileId: string; - }) => Effect.Effect< - FileDescription & { stream: Stream.Stream }, - FileNotFound | BlobStore.Failure - >; + }) => Effect.Effect; /** The files in the thread kept as tool calls' results: each call's file id, by the call's id. */ readonly resultFiles: (threadId: string) => Effect.Effect>; + /** + * Removes every upload still unsent `unsentFor` after it was uploaded, and + * returns how many. A blob that cannot be removed is left to go with its + * thread's folder. + */ + readonly removeUnsentUploads: (unsentFor: Duration.Duration) => Effect.Effect; } export class Service extends Context.Service()("@sugabots/core/ThreadFiles") {} @@ -46,31 +73,70 @@ export const make = Effect.gen(function* () { const blobs = yield* BlobStore.Service; const ids = yield* Ids.Service; const maxFileBytes = yield* Config.option(maxFileBytesConfig); + + /** + * Keeps `content` as a new file in the thread, its row saying where it came + * from. The blob first: a row is never without its blob. A row that then + * fails to insert would leave a blob nothing reads, so it is removed; if + * that fails too, it goes with the thread's folder. + */ + const keep = ( + threadId: string, + content: BlobStore.Content, + source: FileSource, + maxSize = Number.POSITIVE_INFINITY, + ) => + Effect.gen(function* () { + const { contentType } = content; + const limit = Math.min( + maxSize, + Option.getOrElse(maxFileBytes, () => Number.POSITIVE_INFINITY), + ); + const stream = + limit === Number.POSITIVE_INFINITY + ? content.stream + : BlobStore.atMost(content.stream, limit); + const id = yield* ids.next; + const key = fileKey(threadId, id); + const { size } = yield* blobs.put(key, { stream, contentType }).pipe( + Effect.catchIf( + (error) => error instanceof BlobStore.TooLarge, + ({ maxSize }) => new FileTooLarge({ maxSize }), + ), + ); + yield* query((db) => + db.insert(threadFile).values({ id, threadId, contentType, size, ...source }), + ).pipe(Effect.onError(() => blobs.delete(key).pipe(Effect.ignore))); + return { id, contentType, size }; + }); + return Service.of({ write: ({ threadId, toolCallId, content }) => + operation("write", keep(threadId, content, { kind: "tool_result", toolCallId })), + upload: ({ threadId, uploaderId, name, maxSize, maxUnsent, content }) => operation( - "write", + "upload", Effect.gen(function* () { - const { contentType } = content; - const stream = Option.match(maxFileBytes, { - onNone: () => content.stream, - onSome: (maxSize) => BlobStore.atMost(content.stream, maxSize), - }); - const id = yield* ids.next; - const key = fileKey(threadId, id); - // The blob first: a row is never without its blob. A row that then - // fails to insert would leave a blob nothing reads, so it is removed; - // if that fails too, it goes with the thread's folder. - const { size } = yield* blobs.put(key, { stream, contentType }).pipe( - Effect.catchIf( - (error) => error instanceof BlobStore.TooLarge, - ({ maxSize }) => new FileTooLarge({ maxSize }), - ), + const [{ unsent } = { unsent: 0 }] = yield* query((db) => + db + .select({ unsent: count() }) + .from(threadFile) + .where( + and( + eq(threadFile.threadId, threadId), + eq(threadFile.uploaderId, uploaderId), + unsentUpload, + ), + ), + ); + if (unsent >= maxUnsent) return yield* new TooManyUnsentUploads({ maxUnsent }); + const file = yield* keep( + threadId, + content, + { kind: "upload", uploaderId, name }, + maxSize, ); - yield* query((db) => - db.insert(threadFile).values({ id, threadId, toolCallId, contentType, size }), - ).pipe(Effect.onError(() => blobs.delete(key).pipe(Effect.ignore))); - return { id, contentType, size }; + return { ...file, name }; }), ), read: ({ threadId, fileId }) => @@ -82,19 +148,25 @@ export const make = Effect.gen(function* () { if (!isUuid(fileId)) return yield* new FileNotFound({ fileId }); const [file] = yield* query((db) => db - .select({ id: threadFile.id }) + .select({ id: threadFile.id, name: threadFile.name }) .from(threadFile) - .where(and(eq(threadFile.id, fileId), eq(threadFile.threadId, threadId))), + .where( + and( + eq(threadFile.id, fileId), + eq(threadFile.threadId, threadId), + not(unsentUpload), + ), + ), ); if (!file) return yield* new FileNotFound({ fileId }); - // A row is never without its blob, as `write` puts the blob first. + // A row is never without its blob, as `keep` puts the blob first. const stored = yield* blobs .get(fileKey(threadId, file.id)) .pipe(Effect.catchTag("BlobNotFound", Effect.die)); const stream = stored.stream.pipe( Stream.catchTag("BlobNotFound", () => Stream.fail(new FileNotFound({ fileId }))), ); - return { id: file.id, ...stored, stream }; + return { id: file.id, name: file.name ?? undefined, ...stored, stream }; }), ), resultFiles: (threadId) => @@ -112,6 +184,40 @@ export const make = Effect.gen(function* () { ); }), ), + removeUnsentUploads: (unsentFor) => + operation( + "removeUnsentUploads", + Effect.gen(function* () { + // Measured on the database's clock, which stamped `created_at`. + const removed = yield* query((db) => + db + .delete(threadFile) + .where( + and( + unsentUpload, + lt( + threadFile.createdAt, + sql`now() - make_interval(secs => ${Duration.toSeconds(unsentFor)})`, + ), + ), + ) + .returning({ id: threadFile.id, threadId: threadFile.threadId }), + ); + yield* Effect.forEach( + removed, + ({ id, threadId }) => + blobs + .delete(fileKey(threadId, id)) + .pipe( + Effect.catchCause((cause) => + Effect.logWarning("Removing an unsent upload's blob failed", cause), + ), + ), + { discard: true }, + ); + return removed.length; + }), + ), }); }); @@ -133,14 +239,69 @@ export interface FileDescription { size: number; } -export class FileNotFound extends Data.TaggedError("ThreadFileNotFound")<{ - fileId: string; -}> {} +/** A file being read: its description, its name when it is an upload, and its bytes. */ +export interface StoredFile extends FileDescription { + /** An upload's name; a tool result has none. */ + name: string | undefined; + stream: Stream.Stream; +} + +/** A file a person uploaded, with the name it had on their computer. */ +export interface Upload extends FileDescription { + name: string; +} + +/** An upload not yet attached to a message, which only its uploader knows of. */ +const unsentUpload = sql`(${threadFile.kind} = 'upload' and ${threadFile.messageId} is null)`; + +/** Which kind of file a row is, with the columns that kind sets. */ +type FileSource = + | { kind: "tool_result"; toolCallId: string | undefined } + | { kind: "upload"; uploaderId: string; name: string }; + +export class FileNotFound + extends Data.TaggedError("ThreadFileNotFound")<{ + fileId: string; + }> + implements UserFacing +{ + get userMessage() { + return userText`That file isn't there`; + } +} + +export class TooManyUnsentUploads + extends Data.TaggedError("TooManyUnsentUploads")<{ + /** How many unsent uploads one person may have in a thread. */ + maxUnsent: number; + }> + implements UserFacing +{ + get userMessage() { + return userText`You can have ${this.maxUnsent} files waiting to be sent at most. Send or remove some first`; + } +} + +export class FileTooLarge + extends Data.TaggedError("ThreadFileTooLarge")<{ + /** The largest size kept, in bytes. */ + maxSize: number; + }> + implements UserFacing +{ + get userMessage() { + const { amount, unit } = sizeForPeople(this.maxSize); + return userText`The file is larger than ${amount} ${unit}, the most that can be kept`; + } +} -export class FileTooLarge extends Data.TaggedError("ThreadFileTooLarge")<{ - /** The largest size kept, in bytes. */ - maxSize: number; -}> {} +/** `bytes` as people read a file's size, such as 30 MB or 512 KB. */ +function sizeForPeople(bytes: number): { amount: number; unit: "MB" | "KB" } { + const mebibytes = bytes / (1024 * 1024); + return mebibytes >= 1 + ? { amount: Math.floor(mebibytes), unit: "MB" } + : { amount: Math.max(1, Math.floor(bytes / 1024)), unit: "KB" }; +} /** * Under the thread's folder, `threads/`, which the diff --git a/packages/core/src/conversations/thread-files/unsent-uploads.ts b/packages/core/src/conversations/thread-files/unsent-uploads.ts new file mode 100644 index 00000000..df26c1b0 --- /dev/null +++ b/packages/core/src/conversations/thread-files/unsent-uploads.ts @@ -0,0 +1,27 @@ +export * as UnsentUploads from "./unsent-uploads.ts"; + +import { Duration, Effect, Layer, Schedule } from "effect"; +import { ThreadFiles } from "./thread-files.ts"; + +/** How long an upload may wait to be attached to a message before it is removed. */ +const UNSENT_FOR = Duration.hours(24); + +/** How often unsent uploads are looked for. */ +const INTERVAL = Duration.hours(1); + +/** + * Removes uploads never attached to a message, such as files added to a draft + * that was abandoned, every `INTERVAL` for as long as the layer's scope is + * open, starting at once. + */ +export const layer = Layer.effectDiscard( + Effect.gen(function* () { + const files = yield* ThreadFiles.Service; + yield* Effect.forkScoped( + files.removeUnsentUploads(UNSENT_FOR).pipe( + Effect.catchCause((cause) => Effect.logError("Removing unsent uploads failed", cause)), + Effect.repeat(Schedule.spaced(INTERVAL)), + ), + ); + }), +).pipe(Layer.provide(ThreadFiles.layer)); diff --git a/packages/core/src/conversations/tools/read-thread-file/tool.test.ts b/packages/core/src/conversations/tools/read-thread-file/tool.test.ts index f31696e7..15978bc2 100644 --- a/packages/core/src/conversations/tools/read-thread-file/tool.test.ts +++ b/packages/core/src/conversations/tools/read-thread-file/tool.test.ts @@ -19,6 +19,7 @@ async function read( threadId === THREAD && fileId === FILE ? Effect.succeed({ id: FILE, + name: undefined, contentType, size: text.length, stream: Stream.succeed(new TextEncoder().encode(text)), diff --git a/packages/server/src/http/app.test-support.ts b/packages/server/src/http/app.test-support.ts index 6ce0e720..d915f574 100644 --- a/packages/server/src/http/app.test-support.ts +++ b/packages/server/src/http/app.test-support.ts @@ -4,6 +4,7 @@ import { Usage } from "@sugabots/core/accounting/usage"; import { Accounts } from "@sugabots/core/accounts/accounts"; import { Artifacts } from "@sugabots/core/artifacts/artifacts"; import { ArtifactPages } from "@sugabots/core/artifacts/pages"; +import { ChatFiles } from "@sugabots/core/conversations/chats/chat-files"; import { ChatView } from "@sugabots/core/conversations/chats/chat-view"; import { Chats } from "@sugabots/core/conversations/chats/chats"; import { ModelTrials } from "@sugabots/core/conversations/model-trials/model-trials"; @@ -120,6 +121,7 @@ const fakes: Layer.Layer = Layer.mergeAll( unimplemented(ModelTrials.Service), unimplemented(Usage.Service), unimplemented(Chats.Service), + unimplemented(ChatFiles.Service), unimplemented(ChatView.Service), unimplemented(Reactions.Service), unimplemented(ThreadView.Service), diff --git a/packages/server/src/http/services.ts b/packages/server/src/http/services.ts index a517c1f3..66e9cd6b 100644 --- a/packages/server/src/http/services.ts +++ b/packages/server/src/http/services.ts @@ -2,6 +2,7 @@ import type { Usage } from "@sugabots/core/accounting/usage"; import type { Accounts } from "@sugabots/core/accounts/accounts"; import type { Artifacts } from "@sugabots/core/artifacts/artifacts"; import type { ArtifactPages } from "@sugabots/core/artifacts/pages"; +import type { ChatFiles } from "@sugabots/core/conversations/chats/chat-files"; import type { ChatView } from "@sugabots/core/conversations/chats/chat-view"; import type { Chats } from "@sugabots/core/conversations/chats/chats"; import type { ModelTrials } from "@sugabots/core/conversations/model-trials/model-trials"; @@ -52,6 +53,7 @@ export type HttpServices = | ModelTrials.Service | Usage.Service | Chats.Service + | ChatFiles.Service | ChatView.Service | Reactions.Service | ThreadView.Service diff --git a/packages/server/src/http/session.test.ts b/packages/server/src/http/session.test.ts index fdda2244..c1ebb4bc 100644 --- a/packages/server/src/http/session.test.ts +++ b/packages/server/src/http/session.test.ts @@ -40,6 +40,7 @@ const PLACEHOLDERS: Record = { messageId: "0199a3a0-0000-7000-8000-00000000000f", // 👍, as a client puts it in the address. emoji: "%F0%9F%91%8D", + fileId: "0199a3a0-0000-7000-8000-000000000010", }; function fill(pattern: string): string { diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index bbfa4e31..942636f9 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -9,6 +9,7 @@ import { Conversations } from "@sugabots/core/conversations/conversations"; import { ModelTrials } from "@sugabots/core/conversations/model-trials/model-trials"; import { Routines } from "@sugabots/core/conversations/routines/routines"; import { RoutineRuns } from "@sugabots/core/conversations/routines/runs"; +import { UnsentUploads } from "@sugabots/core/conversations/thread-files/unsent-uploads"; import { DesktopViewer } from "@sugabots/core/conversations/tools/browser/viewer"; import { BuiltInTools } from "@sugabots/core/conversations/tools/built-in"; import { ConnectionTools } from "@sugabots/core/conversations/tools/connections"; @@ -120,6 +121,7 @@ const Background = Layer.mergeAll( Routines.schedulerLayer, EventPruning.layer, BlobDeletions.layer, + UnsentUploads.layer, PresetSeeding.layer, PodSandboxes.pauseSweepLayer, ); diff --git a/packages/server/src/routes/chats/file-response.ts b/packages/server/src/routes/chats/file-response.ts new file mode 100644 index 00000000..c21782b3 --- /dev/null +++ b/packages/server/src/routes/chats/file-response.ts @@ -0,0 +1,54 @@ +import type { ThreadFiles } from "@sugabots/core/conversations/thread-files/thread-files"; +import { HttpServerResponse } from "effect/unstable/http"; + +/** + * Media types a browser may show in place. Anything else, HTML and SVG + * included, is offered as a download, so a file someone uploaded can never + * run as a page of the app. + */ +const SHOWN_IN_PLACE = new Set([ + "image/png", + "image/jpeg", + "image/gif", + "image/webp", + "image/avif", +]); + +/** + * A stored file as a response: its own media type, never sniffed for another, + * shown in place only when it is a common image, and sandboxed should a + * browser open it anyway. Files never change once kept, so a browser may keep + * its copy. + */ +export function fileResponse(file: ThreadFiles.StoredFile): HttpServerResponse.HttpServerResponse { + const shownInPlace = SHOWN_IN_PLACE.has(essenceOf(file.contentType)); + return HttpServerResponse.stream(file.stream, { + contentType: file.contentType, + contentLength: file.size, + headers: { + "content-disposition": contentDisposition(shownInPlace ? "inline" : "attachment", file.name), + "x-content-type-options": "nosniff", + "content-security-policy": "sandbox", + "cache-control": "private, max-age=31536000, immutable", + }, + }); +} + +/** `image/png` from `image/png; charset=binary`. */ +function essenceOf(mediaType: string): string { + return (mediaType.split(";")[0] ?? "").trim().toLowerCase(); +} + +/** + * RFC 6266: an ASCII `filename` for old browsers, and the exact name, in + * UTF-8, as `filename*` for the rest. + */ +function contentDisposition(type: "inline" | "attachment", name: string | undefined): string { + if (name === undefined) return type; + const ascii = name.replace(/[^\x20-\x7e]|["\\]/g, "_"); + const encoded = encodeURIComponent(name).replace( + /['()*]/g, + (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, + ); + return `${type}; filename="${ascii}"; filename*=UTF-8''${encoded}`; +} diff --git a/packages/server/src/routes/chats/files.test.ts b/packages/server/src/routes/chats/files.test.ts new file mode 100644 index 00000000..62b5bdda --- /dev/null +++ b/packages/server/src/routes/chats/files.test.ts @@ -0,0 +1,166 @@ +import { MAX_ATTACHMENT_BYTES } from "@sugabots/contracts"; +import { ChatFiles } from "@sugabots/core/conversations/chats/chat-files"; +import { ThreadFiles } from "@sugabots/core/conversations/thread-files/thread-files"; +import { unimplemented } from "@sugabots/core/testing"; +import { Effect, Layer, Stream } from "effect"; +import { describe, expect, it } from "vitest"; +import { createTestApp, identifiedBy, type UserResolver } from "../../http/app.test-support.ts"; + +/** + * The routes that upload a chat's files and serve them back, over doubles of + * `ChatFiles`. Who may reach a chat's files is decided in core. + */ + +const CHAT = "0199a3a0-0000-7000-8000-000000000401"; +const FILE = "0199a3a0-0000-7000-8000-000000000501"; + +const resolveUser: UserResolver = async () => ({ + id: "0199a3a0-0000-7000-8000-000000000002", + name: "Sam", + email: "sam@example.com", + image: null, +}); + +const app = (files: Partial) => + createTestApp(Layer.merge(identifiedBy(resolveUser), unimplemented(ChatFiles.Service, files))); + +const signedIn = { authorization: "Bearer member" }; + +/** A `read` that finds one file, with `bytes` as its content. */ +const serving = (file: { name: string; contentType: string; bytes: Uint8Array }) => + app({ + read: ({ fileId }) => + fileId === FILE + ? Effect.succeed({ + id: FILE, + name: file.name, + contentType: file.contentType, + size: file.bytes.byteLength, + stream: Stream.succeed(file.bytes), + }) + : Effect.fail(new ThreadFiles.FileNotFound({ fileId })), + }); + +describe("uploading a chat file", () => { + it("keeps the body as the named file, with the type it names", async () => { + let kept: unknown; + const name = encodeURIComponent("notes été.txt"); + const type = encodeURIComponent("text/plain; charset=utf-8"); + const response = await app({ + upload: ({ chatId, name, type, stream }) => + Effect.gen(function* () { + const chunks = yield* Stream.runCollect(stream).pipe(Effect.orDie); + kept = { chatId, name, type, text: Buffer.concat(chunks).toString() }; + return { id: FILE, name, contentType: type ?? "", size: 5 }; + }), + }).request(`/chats/${CHAT}/files?name=${name}&type=${type}`, { + method: "POST", + headers: signedIn, + body: "hello", + }); + + expect(response.status).toBe(201); + expect(kept).toEqual({ + chatId: CHAT, + name: "notes été.txt", + type: "text/plain; charset=utf-8", + text: "hello", + }); + expect(await response.json()).toEqual({ + id: FILE, + name: "notes été.txt", + contentType: "text/plain; charset=utf-8", + size: 5, + }); + }); + + it("leaves the type to the name when the uploader names none", async () => { + let type: string | undefined = "not called"; + const response = await app({ + upload: (input) => { + type = input.type; + return Effect.succeed({ + id: FILE, + name: input.name, + contentType: "text/markdown", + size: 0, + }); + }, + }).request(`/chats/${CHAT}/files?name=notes.md`, { + method: "POST", + headers: signedIn, + body: "# notes", + }); + + expect(response.status).toBe(201); + expect(type).toBeUndefined(); + }); + + it("refuses a type that could break out of the file's header", async () => { + const type = encodeURIComponent("text/plain\r\nset-cookie: a=b"); + const response = await app({}).request(`/chats/${CHAT}/files?name=a.txt&type=${type}`, { + method: "POST", + headers: signedIn, + body: "a", + }); + + expect(response.status).toBe(400); + }); + + it("refuses a file that says it is over the limit before reading it", async () => { + const response = await app({}).request(`/chats/${CHAT}/files?name=huge.zip`, { + method: "POST", + headers: { + ...signedIn, + "content-type": "application/zip", + "content-length": String(MAX_ATTACHMENT_BYTES + 1), + }, + body: new Uint8Array(1), + }); + + expect(response.status).toBe(413); + expect(await response.json()).toMatchObject({ _tag: "PayloadTooLarge" }); + }); +}); + +describe("serving a chat file", () => { + it("shows an image in place, as its own type and never sniffed for another", async () => { + const response = await serving({ + name: "chart.png", + contentType: "image/png", + bytes: new Uint8Array([137, 80, 78, 71]), + }).request(`/chats/${CHAT}/files/${FILE}`, { headers: signedIn }); + + expect(response.status).toBe(200); + expect(response.headers.get("content-type")).toBe("image/png"); + expect(response.headers.get("content-disposition")).toBe( + `inline; filename="chart.png"; filename*=UTF-8''chart.png`, + ); + expect(response.headers.get("x-content-type-options")).toBe("nosniff"); + expect(response.headers.get("content-security-policy")).toBe("sandbox"); + expect(new Uint8Array(await response.arrayBuffer())).toEqual(new Uint8Array([137, 80, 78, 71])); + }); + + it("offers a page as a download under its name, never as a page of the app", async () => { + const response = await serving({ + name: "résumé.html", + contentType: "text/html", + bytes: new TextEncoder().encode(""), + }).request(`/chats/${CHAT}/files/${FILE}`, { headers: signedIn }); + + expect(response.headers.get("content-disposition")).toBe( + `attachment; filename="r_sum_.html"; filename*=UTF-8''r%C3%A9sum%C3%A9.html`, + ); + expect(response.headers.get("content-security-policy")).toBe("sandbox"); + }); + + it("answers not found for a file the chat does not have", async () => { + const response = await serving({ + name: "chart.png", + contentType: "image/png", + bytes: new Uint8Array(), + }).request(`/chats/${CHAT}/files/0199a3a0-0000-7000-8000-000000000599`, { headers: signedIn }); + + expect(response.status).toBe(404); + }); +}); diff --git a/packages/server/src/routes/chats/routes.ts b/packages/server/src/routes/chats/routes.ts index 92a7dd98..9b7477fe 100644 --- a/packages/server/src/routes/chats/routes.ts +++ b/packages/server/src/routes/chats/routes.ts @@ -1,16 +1,22 @@ -import { BadRequest, Conflict } from "@sugabots/contracts/http"; +import { MAX_ATTACHMENT_BYTES } from "@sugabots/contracts"; +import { BadRequest, Conflict, NotFound, PayloadTooLarge } from "@sugabots/contracts/http"; +import { ChatFiles } from "@sugabots/core/conversations/chats/chat-files"; import { ChatView } from "@sugabots/core/conversations/chats/chat-view"; import { Chats } from "@sugabots/core/conversations/chats/chats"; -import { Effect } from "effect"; +import { ThreadFiles } from "@sugabots/core/conversations/thread-files/thread-files"; +import { userText } from "@sugabots/errors"; +import { Effect, Stream } from "effect"; import { HttpApiBuilder } from "effect/unstable/httpapi"; import { asSessionUser } from "../../auth/middleware.ts"; import { ServerApi } from "../../http/api.ts"; import { asHttpError, refusals } from "../../http/errors.ts"; +import { fileResponse } from "./file-response.ts"; export const chatRoutes = HttpApiBuilder.group(ServerApi, "chats", (handlers) => Effect.gen(function* () { const chats = yield* Chats.Service; const view = yield* ChatView.Service; + const files = yield* ChatFiles.Service; return handlers .handle("list", ({ params, query }) => view @@ -50,10 +56,49 @@ export const chatRoutes = HttpApiBuilder.group(ServerApi, "chats", (handlers) => replyTo: payload.replyTo, }) .pipe(asSessionUser, asHttpError(chatErrors)), + ) + .handleRaw("uploadFile", ({ params, query, request }) => + // Raw, so the file streams into the blob store rather than being read + // into memory first. + Effect.gen(function* () { + // Refused before any of it is read, when it says how large it is. + if (Number(request.headers["content-length"]) > MAX_ATTACHMENT_BYTES) { + return yield* new ThreadFiles.FileTooLarge({ maxSize: MAX_ATTACHMENT_BYTES }); + } + return yield* files.upload({ + chatId: params.chatId, + name: query.name, + type: query.type, + stream: request.stream.pipe( + Stream.mapError( + () => new BadRequest({ message: userText`The upload was interrupted` }), + ), + ), + }); + }).pipe(Effect.catchTags(storeOutagesAsDefects), asSessionUser, asHttpError(fileErrors)), + ) + .handle("file", ({ params }) => + files + .read({ chatId: params.chatId, fileId: params.fileId }) + .pipe( + Effect.map(fileResponse), + Effect.catchTags(storeOutagesAsDefects), + asSessionUser, + asHttpError(fileErrors), + ), ); }), ); +/** + * A blob store that cannot be reached, or refuses, is the installation's + * fault, not the caller's: logged, and answered as a server error. + */ +const storeOutagesAsDefects = { + BlobStoreUnavailable: Effect.die, + BlobStoreRejected: Effect.die, +} as const; + const chatErrors = { ...refusals, ChatPlacementRejected: BadRequest, @@ -62,3 +107,10 @@ const chatErrors = { ChatAgentHasNoModel: Conflict, InvalidChatCursor: BadRequest, }; + +const fileErrors = { + ...refusals, + ThreadFileTooLarge: PayloadTooLarge, + TooManyUnsentUploads: Conflict, + ThreadFileNotFound: NotFound, +};