diff --git a/packages/opencode/migration/20260312043431_session_message_cursor/migration.sql b/packages/opencode/migration/20260312043431_session_message_cursor/migration.sql new file mode 100644 index 000000000..e2bd08137 --- /dev/null +++ b/packages/opencode/migration/20260312043431_session_message_cursor/migration.sql @@ -0,0 +1,4 @@ +DROP INDEX IF EXISTS `message_session_idx`;--> statement-breakpoint +DROP INDEX IF EXISTS `part_message_idx`;--> statement-breakpoint +CREATE INDEX `message_session_time_created_id_idx` ON `message` (`session_id`,`time_created`,`id`);--> statement-breakpoint +CREATE INDEX `part_message_id_id_idx` ON `part` (`message_id`,`id`); \ No newline at end of file diff --git a/packages/opencode/migration/20260312043431_session_message_cursor/snapshot.json b/packages/opencode/migration/20260312043431_session_message_cursor/snapshot.json new file mode 100644 index 000000000..3e067163d --- /dev/null +++ b/packages/opencode/migration/20260312043431_session_message_cursor/snapshot.json @@ -0,0 +1,1226 @@ +{ + "version": "7", + "dialect": "sqlite", + "id": "37e1554d-af4c-43f2-aa7c-307fb49a315e", + "prevIds": [ + "fb311f30-9948-4131-b15c-7d308478a878" + ], + "ddl": [ + { + "name": "account_state", + "entityType": "tables" + }, + { + "name": "account", + "entityType": "tables" + }, + { + "name": "control_account", + "entityType": "tables" + }, + { + "name": "workspace", + "entityType": "tables" + }, + { + "name": "project", + "entityType": "tables" + }, + { + "name": "message", + "entityType": "tables" + }, + { + "name": "part", + "entityType": "tables" + }, + { + "name": "permission", + "entityType": "tables" + }, + { + "name": "session", + "entityType": "tables" + }, + { + "name": "todo", + "entityType": "tables" + }, + { + "name": "session_share", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_account_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active_org_id", + "entityType": "columns", + "table": "account_state" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "email", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_expiry", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "active", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "control_account" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "type", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "branch", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "extra", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "workspace" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "worktree", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "vcs", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_url", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "icon_color", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "project" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_initialized", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "sandboxes", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "commands", + "entityType": "columns", + "table": "project" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "message" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "message" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "message_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "part" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "part" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "permission" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "data", + "entityType": "columns", + "table": "permission" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "project_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "workspace_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "parent_id", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "slug", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "directory", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "title", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "version", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "share_url", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_additions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_deletions", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_files", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "summary_diffs", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "revert", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "permission", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_compacting", + "entityType": "columns", + "table": "session" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_archived", + "entityType": "columns", + "table": "session" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "content", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "status", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "priority", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "position", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "todo" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "todo" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "session_id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "secret", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "url", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "session_share" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "session_share" + }, + { + "columns": [ + "active_account_id" + ], + "tableTo": "account", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "SET NULL", + "nameExplicit": false, + "name": "fk_account_state_active_account_id_account_id_fk", + "entityType": "fks", + "table": "account_state" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_workspace_project_id_project_id_fk", + "entityType": "fks", + "table": "workspace" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_message_session_id_session_id_fk", + "entityType": "fks", + "table": "message" + }, + { + "columns": [ + "message_id" + ], + "tableTo": "message", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_part_message_id_message_id_fk", + "entityType": "fks", + "table": "part" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_permission_project_id_project_id_fk", + "entityType": "fks", + "table": "permission" + }, + { + "columns": [ + "project_id" + ], + "tableTo": "project", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_project_id_project_id_fk", + "entityType": "fks", + "table": "session" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_todo_session_id_session_id_fk", + "entityType": "fks", + "table": "todo" + }, + { + "columns": [ + "session_id" + ], + "tableTo": "session", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "CASCADE", + "nameExplicit": false, + "name": "fk_session_share_session_id_session_id_fk", + "entityType": "fks", + "table": "session_share" + }, + { + "columns": [ + "email", + "url" + ], + "nameExplicit": false, + "name": "control_account_pk", + "entityType": "pks", + "table": "control_account" + }, + { + "columns": [ + "session_id", + "position" + ], + "nameExplicit": false, + "name": "todo_pk", + "entityType": "pks", + "table": "todo" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_state_pk", + "table": "account_state", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "account_pk", + "table": "account", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "workspace_pk", + "table": "workspace", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "project_pk", + "table": "project", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "message_pk", + "table": "message", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "part_pk", + "table": "part", + "entityType": "pks" + }, + { + "columns": [ + "project_id" + ], + "nameExplicit": false, + "name": "permission_pk", + "table": "permission", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "session_pk", + "table": "session", + "entityType": "pks" + }, + { + "columns": [ + "session_id" + ], + "nameExplicit": false, + "name": "session_share_pk", + "table": "session_share", + "entityType": "pks" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + }, + { + "value": "time_created", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "message_session_time_created_id_idx", + "entityType": "indexes", + "table": "message" + }, + { + "columns": [ + { + "value": "message_id", + "isExpression": false + }, + { + "value": "id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_message_id_id_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "part_session_idx", + "entityType": "indexes", + "table": "part" + }, + { + "columns": [ + { + "value": "project_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_project_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "workspace_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_workspace_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "parent_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "session_parent_idx", + "entityType": "indexes", + "table": "session" + }, + { + "columns": [ + { + "value": "session_id", + "isExpression": false + } + ], + "isUnique": false, + "where": null, + "origin": "manual", + "name": "todo_session_idx", + "entityType": "indexes", + "table": "todo" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index f74237374..b8fafd336 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -569,17 +569,65 @@ export const SessionRoutes = lazy(() => ), validator( "query", - z.object({ - limit: z.coerce.number().optional(), - }), + z + .object({ + limit: z.coerce + .number() + .int() + .min(0) + .optional() + .meta({ description: "Maximum number of messages to return" }), + before: z + .string() + .optional() + .meta({ description: "Opaque cursor for loading older messages" }) + .refine( + (value) => { + if (!value) return true + try { + MessageV2.cursor.decode(value) + return true + } catch { + return false + } + }, + { message: "Invalid cursor" }, + ), + }) + .refine((value) => !value.before || value.limit !== undefined, { + message: "before requires limit", + path: ["before"], + }), ), async (c) => { const query = c.req.valid("query") - const messages = await Session.messages({ - sessionID: c.req.valid("param").sessionID, + const sessionID = c.req.valid("param").sessionID + if (query.limit === undefined) { + await Session.get(sessionID) + const messages = await Session.messages({ sessionID }) + return c.json(messages) + } + + if (query.limit === 0) { + await Session.get(sessionID) + const messages = await Session.messages({ sessionID }) + return c.json(messages) + } + + const page = await MessageV2.page({ + sessionID, limit: query.limit, + before: query.before, }) - return c.json(messages) + if (page.cursor) { + const url = new URL(c.req.url) + url.searchParams.set("limit", query.limit.toString()) + url.searchParams.set("before", page.cursor) + c.header("Access-Control-Expose-Headers", "Link, X-Next-Cursor") + c.header("Link", `<${url.toString()}>; rel=\"next\"`) + c.header("X-Next-Cursor", page.cursor) + } + return c.json(page.items) }, ) .get( diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts index 03ccb44c1..8e4babd61 100644 --- a/packages/opencode/src/session/message-v2.ts +++ b/packages/opencode/src/session/message-v2.ts @@ -6,8 +6,8 @@ import { APICallError, convertToModelMessages, LoadAPIKeyError, type ModelMessag import { LSP } from "../lsp" import { Snapshot } from "@/snapshot" import { fn } from "@/util/fn" -import { Database, eq, desc, inArray } from "@/storage/db" -import { MessageTable, PartTable } from "./session.sql" +import { Database, NotFoundError, and, desc, eq, inArray, lt, or } from "@/storage/db" +import { MessageTable, PartTable, SessionTable } from "./session.sql" import { ProviderTransform } from "@/provider/transform" import { STATUS_CODES } from "http" import { Storage } from "@/storage/storage" @@ -494,6 +494,68 @@ export namespace MessageV2 { }) export type WithParts = z.infer + const Cursor = z.object({ + id: MessageID.zod, + time: z.number(), + }) + type Cursor = z.infer + + export const cursor = { + encode(input: Cursor) { + return Buffer.from(JSON.stringify(input)).toString("base64url") + }, + decode(input: string) { + return Cursor.parse(JSON.parse(Buffer.from(input, "base64url").toString("utf8"))) + }, + } + + const info = (row: typeof MessageTable.$inferSelect) => + ({ + ...row.data, + id: row.id, + sessionID: row.session_id, + }) as MessageV2.Info + + const part = (row: typeof PartTable.$inferSelect) => + ({ + ...row.data, + id: row.id, + sessionID: row.session_id, + messageID: row.message_id, + }) as MessageV2.Part + + const older = (row: Cursor) => + or( + lt(MessageTable.time_created, row.time), + and(eq(MessageTable.time_created, row.time), lt(MessageTable.id, row.id)), + ) + + async function hydrate(rows: (typeof MessageTable.$inferSelect)[]) { + const ids = rows.map((row) => row.id) + const partByMessage = new Map() + if (ids.length > 0) { + const partRows = Database.use((db) => + db + .select() + .from(PartTable) + .where(inArray(PartTable.message_id, ids)) + .orderBy(PartTable.message_id, PartTable.id) + .all(), + ) + for (const row of partRows) { + const next = part(row) + const list = partByMessage.get(row.message_id) + if (list) list.push(next) + else partByMessage.set(row.message_id, [next]) + } + } + + return rows.map((row) => ({ + info: info(row), + parts: partByMessage.get(row.id) ?? [], + })) + } + export function toModelMessages( input: WithParts[], model: Provider.Model, @@ -729,56 +791,61 @@ export namespace MessageV2 { ) } - export const stream = fn(SessionID.zod, async function* (sessionID) { - const size = 50 - let offset = 0 - while (true) { + export const page = fn( + z.object({ + sessionID: SessionID.zod, + limit: z.number().int().positive(), + before: z.string().optional(), + }), + async (input) => { + const before = input.before ? cursor.decode(input.before) : undefined + const where = before + ? and(eq(MessageTable.session_id, input.sessionID), older(before)) + : eq(MessageTable.session_id, input.sessionID) const rows = Database.use((db) => db .select() .from(MessageTable) - .where(eq(MessageTable.session_id, sessionID)) - .orderBy(desc(MessageTable.time_created)) - .limit(size) - .offset(offset) + .where(where) + .orderBy(desc(MessageTable.time_created), desc(MessageTable.id)) + .limit(input.limit + 1) .all(), ) - if (rows.length === 0) break - - const ids = rows.map((row) => row.id) - const partsByMessage = new Map() - if (ids.length > 0) { - const partRows = Database.use((db) => - db - .select() - .from(PartTable) - .where(inArray(PartTable.message_id, ids)) - .orderBy(PartTable.message_id, PartTable.id) - .all(), + if (rows.length === 0) { + const row = Database.use((db) => + db.select({ id: SessionTable.id }).from(SessionTable).where(eq(SessionTable.id, input.sessionID)).get(), ) - for (const row of partRows) { - const part = { - ...row.data, - id: row.id, - sessionID: row.session_id, - messageID: row.message_id, - } as MessageV2.Part - const list = partsByMessage.get(row.message_id) - if (list) list.push(part) - else partsByMessage.set(row.message_id, [part]) + if (!row) throw new NotFoundError({ message: `Session not found: ${input.sessionID}` }) + return { + items: [] as MessageV2.WithParts[], + more: false, } } - for (const row of rows) { - const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info - yield { - info, - parts: partsByMessage.get(row.id) ?? [], - } + const more = rows.length > input.limit + const page = more ? rows.slice(0, input.limit) : rows + const items = await hydrate(page) + items.reverse() + const tail = page.at(-1) + return { + items, + more, + cursor: more && tail ? cursor.encode({ id: tail.id, time: tail.time_created }) : undefined, } + }, + ) - offset += rows.length - if (rows.length < size) break + export const stream = fn(SessionID.zod, async function* (sessionID) { + const size = 50 + let before: string | undefined + while (true) { + const next = await page({ sessionID, limit: size, before }) + if (next.items.length === 0) break + for (let i = next.items.length - 1; i >= 0; i--) { + yield next.items[i] + } + if (!next.more || !next.cursor) break + before = next.cursor } }) @@ -797,11 +864,16 @@ export namespace MessageV2 { messageID: MessageID.zod, }), async (input): Promise => { - const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get()) - if (!row) throw new Error(`Message not found: ${input.messageID}`) - const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info + const row = Database.use((db) => + db + .select() + .from(MessageTable) + .where(and(eq(MessageTable.id, input.messageID), eq(MessageTable.session_id, input.sessionID))) + .get(), + ) + if (!row) throw new NotFoundError({ message: `Message not found: ${input.messageID}` }) return { - info, + info: info(row), parts: await parts(input.messageID), } }, diff --git a/packages/opencode/src/session/session.sql.ts b/packages/opencode/src/session/session.sql.ts index 71abc8516..b3229edd1 100644 --- a/packages/opencode/src/session/session.sql.ts +++ b/packages/opencode/src/session/session.sql.ts @@ -54,7 +54,7 @@ export const MessageTable = sqliteTable( ...Timestamps, data: text({ mode: "json" }).notNull().$type(), }, - (table) => [index("message_session_idx").on(table.session_id)], + (table) => [index("message_session_time_created_id_idx").on(table.session_id, table.time_created, table.id)], ) export const PartTable = sqliteTable( @@ -69,7 +69,10 @@ export const PartTable = sqliteTable( ...Timestamps, data: text({ mode: "json" }).notNull().$type(), }, - (table) => [index("part_message_idx").on(table.message_id), index("part_session_idx").on(table.session_id)], + (table) => [ + index("part_message_id_id_idx").on(table.message_id, table.id), + index("part_session_idx").on(table.session_id), + ], ) export const TodoTable = sqliteTable( diff --git a/packages/opencode/test/server/session-messages.test.ts b/packages/opencode/test/server/session-messages.test.ts new file mode 100644 index 000000000..ee4c51646 --- /dev/null +++ b/packages/opencode/test/server/session-messages.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { Instance } from "../../src/project/instance" +import { Server } from "../../src/server/server" +import { Session } from "../../src/session" +import { MessageV2 } from "../../src/session/message-v2" +import { MessageID, PartID, type SessionID } from "../../src/session/schema" +import { Log } from "../../src/util/log" + +const root = path.join(__dirname, "../..") +Log.init({ print: false }) + +async function fill(sessionID: SessionID, count: number, time = (i: number) => Date.now() + i) { + const ids = [] as MessageID[] + for (let i = 0; i < count; i++) { + const id = MessageID.ascending() + ids.push(id) + await Session.updateMessage({ + id, + sessionID, + role: "user", + time: { created: time(i) }, + agent: "test", + model: { providerID: "test", modelID: "test" }, + tools: {}, + mode: "", + } as unknown as MessageV2.Info) + await Session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: `m${i}`, + }) + } + return ids +} + +describe("session messages endpoint", () => { + test("returns cursor headers for older pages", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const ids = await fill(session.id, 5) + const app = Server.Default() + + const a = await app.request(`/session/${session.id}/message?limit=2`) + expect(a.status).toBe(200) + const aBody = (await a.json()) as MessageV2.WithParts[] + expect(aBody.map((item) => item.info.id)).toEqual(ids.slice(-2)) + const cursor = a.headers.get("x-next-cursor") + expect(cursor).toBeTruthy() + expect(a.headers.get("link")).toContain('rel="next"') + + const b = await app.request(`/session/${session.id}/message?limit=2&before=${encodeURIComponent(cursor!)}`) + expect(b.status).toBe(200) + const bBody = (await b.json()) as MessageV2.WithParts[] + expect(bBody.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) + + await Session.remove(session.id) + }, + }) + }) + + test("keeps full-history responses when limit is omitted", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const ids = await fill(session.id, 3) + const app = Server.Default() + + const res = await app.request(`/session/${session.id}/message`) + expect(res.status).toBe(200) + const body = (await res.json()) as MessageV2.WithParts[] + expect(body.map((item) => item.info.id)).toEqual(ids) + + await Session.remove(session.id) + }, + }) + }) + + test("rejects invalid cursors and missing sessions", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const app = Server.Default() + + const bad = await app.request(`/session/${session.id}/message?limit=2&before=bad`) + expect(bad.status).toBe(400) + + const miss = await app.request(`/session/ses_missing/message?limit=2`) + expect(miss.status).toBe(404) + + await Session.remove(session.id) + }, + }) + }) + + test("does not truncate large legacy limit requests", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + await fill(session.id, 520) + const app = Server.Default() + + const res = await app.request(`/session/${session.id}/message?limit=510`) + expect(res.status).toBe(200) + const body = (await res.json()) as MessageV2.WithParts[] + expect(body).toHaveLength(510) + + await Session.remove(session.id) + }, + }) + }) +}) diff --git a/packages/opencode/test/session/messages-pagination.test.ts b/packages/opencode/test/session/messages-pagination.test.ts new file mode 100644 index 000000000..3614b17d0 --- /dev/null +++ b/packages/opencode/test/session/messages-pagination.test.ts @@ -0,0 +1,115 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { Instance } from "../../src/project/instance" +import { Session } from "../../src/session" +import { MessageV2 } from "../../src/session/message-v2" +import { MessageID, PartID, type SessionID } from "../../src/session/schema" +import { Log } from "../../src/util/log" + +const root = path.join(__dirname, "../..") +Log.init({ print: false }) + +async function fill(sessionID: SessionID, count: number, time = (i: number) => Date.now() + i) { + const ids = [] as MessageID[] + for (let i = 0; i < count; i++) { + const id = MessageID.ascending() + ids.push(id) + await Session.updateMessage({ + id, + sessionID, + role: "user", + time: { created: time(i) }, + agent: "test", + model: { providerID: "test", modelID: "test" }, + tools: {}, + mode: "", + } as unknown as MessageV2.Info) + await Session.updatePart({ + id: PartID.ascending(), + sessionID, + messageID: id, + type: "text", + text: `m${i}`, + }) + } + return ids +} + +describe("session message pagination", () => { + test("pages backward with opaque cursors", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const ids = await fill(session.id, 6) + + const a = await MessageV2.page({ sessionID: session.id, limit: 2 }) + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(a.items.every((item) => item.parts.length === 1)).toBe(true) + expect(a.more).toBe(true) + expect(a.cursor).toBeTruthy() + + const b = await MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! }) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(-4, -2)) + expect(b.more).toBe(true) + expect(b.cursor).toBeTruthy() + + const c = await MessageV2.page({ sessionID: session.id, limit: 2, before: b.cursor! }) + expect(c.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + expect(c.more).toBe(false) + expect(c.cursor).toBeUndefined() + + await Session.remove(session.id) + }, + }) + }) + + test("keeps stream order newest first", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const ids = await fill(session.id, 5) + + const items = await Array.fromAsync(MessageV2.stream(session.id)) + expect(items.map((item) => item.info.id)).toEqual(ids.slice().reverse()) + + await Session.remove(session.id) + }, + }) + }) + + test("accepts cursors generated from fractional timestamps", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const session = await Session.create({}) + const ids = await fill(session.id, 4, (i) => 1000.5 + i) + + const a = await MessageV2.page({ sessionID: session.id, limit: 2 }) + const b = await MessageV2.page({ sessionID: session.id, limit: 2, before: a.cursor! }) + + expect(a.items.map((item) => item.info.id)).toEqual(ids.slice(-2)) + expect(b.items.map((item) => item.info.id)).toEqual(ids.slice(0, 2)) + + await Session.remove(session.id) + }, + }) + }) + + test("scopes get by session id", async () => { + await Instance.provide({ + directory: root, + fn: async () => { + const a = await Session.create({}) + const b = await Session.create({}) + const [id] = await fill(a.id, 1) + + await expect(MessageV2.get({ sessionID: b.id, messageID: id })).rejects.toMatchObject({ name: "NotFoundError" }) + + await Session.remove(a.id) + await Session.remove(b.id) + }, + }) + }) +}) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 2bb2edcd1..27c188838 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -1793,6 +1793,7 @@ export class Session2 extends HeyApiClient { directory?: string workspace?: string limit?: number + before?: string }, options?: Options, ) { @@ -1805,6 +1806,7 @@ export class Session2 extends HeyApiClient { { in: "query", key: "directory" }, { in: "query", key: "workspace" }, { in: "query", key: "limit" }, + { in: "query", key: "before" }, ], }, ], diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 30568c96d..2629015eb 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -3232,7 +3232,11 @@ export type SessionMessagesData = { query?: { directory?: string workspace?: string + /** + * Maximum number of messages to return + */ limit?: number + before?: string } url: "/session/{sessionID}/message" } diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index a5e10f0d7..569de3966 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -27,7 +27,10 @@ "type": "string" } }, - "required": ["healthy", "version"] + "required": [ + "healthy", + "version" + ] } } } @@ -744,7 +747,10 @@ "type": "number" } }, - "required": ["rows", "cols"] + "required": [ + "rows", + "cols" + ] } } } @@ -1021,7 +1027,10 @@ } } }, - "required": ["providers", "default"] + "required": [ + "providers", + "default" + ] } } } @@ -1228,7 +1237,11 @@ ] } }, - "required": ["type", "branch", "extra"] + "required": [ + "type", + "branch", + "extra" + ] } } } @@ -1941,7 +1954,9 @@ ], "summary": "Get session", "description": "Retrieve detailed information about a specific OpenCode session.", - "tags": ["Session"], + "tags": [ + "Session" + ], "responses": { "200": { "description": "Get session", @@ -2169,7 +2184,9 @@ } ], "summary": "Get session children", - "tags": ["Session"], + "tags": [ + "Session" + ], "description": "Retrieve all child sessions that were forked from the specified parent session.", "responses": { "200": { @@ -2366,7 +2383,11 @@ "pattern": "^msg.*" } }, - "required": ["modelID", "providerID", "messageID"] + "required": [ + "modelID", + "providerID", + "messageID" + ] } } } @@ -2792,7 +2813,10 @@ "type": "boolean" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] } } } @@ -2836,7 +2860,17 @@ "in": "query", "name": "limit", "schema": { - "type": "number" + "type": "integer", + "minimum": 0, + "maximum": 9007199254740991 + }, + "description": "Maximum number of messages to return" + }, + { + "in": "query", + "name": "before", + "schema": { + "type": "string" } } ], @@ -2862,7 +2896,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2943,7 +2980,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -2989,7 +3029,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -3036,7 +3079,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -3106,7 +3151,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -3473,7 +3521,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "agent": { "type": "string" @@ -3520,7 +3571,9 @@ } } }, - "required": ["parts"] + "required": [ + "parts" + ] } } } @@ -3581,7 +3634,10 @@ } } }, - "required": ["info", "parts"] + "required": [ + "info", + "parts" + ] } } } @@ -3660,13 +3716,20 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["type", "mime", "url"] + "required": [ + "type", + "mime", + "url" + ] } ] } } }, - "required": ["arguments", "command"] + "required": [ + "arguments", + "command" + ] } } } @@ -3760,13 +3823,19 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["agent", "command"] + "required": [ + "agent", + "command" + ] } } } @@ -3856,7 +3925,9 @@ "pattern": "^prt.*" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } } } @@ -4019,10 +4090,16 @@ "properties": { "response": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] } }, - "required": ["response"] + "required": [ + "response" + ] } } } @@ -4105,13 +4182,19 @@ "properties": { "reply": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] }, "message": { "type": "string" } }, - "required": ["reply"] + "required": [ + "reply" + ] } } } @@ -4288,7 +4371,9 @@ } } }, - "required": ["answers"] + "required": [ + "answers" + ] } } } @@ -4466,10 +4551,15 @@ "properties": { "field": { "type": "string", - "enum": ["reasoning_content", "reasoning_details"] + "enum": [ + "reasoning_content", + "reasoning_details" + ] } }, - "required": ["field"], + "required": [ + "field" + ], "additionalProperties": false } ] @@ -4505,10 +4595,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -4523,7 +4619,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -4532,25 +4631,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -4607,7 +4725,12 @@ } } }, - "required": ["name", "env", "id", "models"] + "required": [ + "name", + "env", + "id", + "models" + ] } }, "default": { @@ -4626,7 +4749,11 @@ } } }, - "required": ["all", "default", "connected"] + "required": [ + "all", + "default", + "connected" + ] } } } @@ -4753,7 +4880,9 @@ "type": "number" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -4833,7 +4962,9 @@ "type": "string" } }, - "required": ["method"] + "required": [ + "method" + ] } } } @@ -4892,7 +5023,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "lines": { "type": "object", @@ -4901,7 +5034,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "line_number": { "type": "number" @@ -4921,7 +5056,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] }, "start": { "type": "number" @@ -4930,11 +5067,21 @@ "type": "number" } }, - "required": ["match", "start", "end"] + "required": [ + "match", + "start", + "end" + ] } } }, - "required": ["path", "lines", "line_number", "absolute_offset", "submatches"] + "required": [ + "path", + "lines", + "line_number", + "absolute_offset", + "submatches" + ] } } } @@ -4980,7 +5127,10 @@ "name": "dirs", "schema": { "type": "string", - "enum": ["true", "false"] + "enum": [ + "true", + "false" + ] } }, { @@ -4988,7 +5138,10 @@ "name": "type", "schema": { "type": "string", - "enum": ["file", "directory"] + "enum": [ + "file", + "directory" + ] } }, { @@ -5337,7 +5490,10 @@ ] } }, - "required": ["name", "config"] + "required": [ + "name", + "config" + ] } } } @@ -5392,7 +5548,9 @@ "type": "string" } }, - "required": ["authorizationUrl"] + "required": [ + "authorizationUrl" + ] } } } @@ -5466,7 +5624,9 @@ "const": true } }, - "required": ["success"] + "required": [ + "success" + ] } } } @@ -5562,7 +5722,9 @@ "type": "string" } }, - "required": ["code"] + "required": [ + "code" + ] } } } @@ -5793,7 +5955,9 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } } } @@ -6105,7 +6269,9 @@ "type": "string" } }, - "required": ["command"] + "required": [ + "command" + ] } } } @@ -6165,7 +6331,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -6173,7 +6344,10 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } } } @@ -6324,7 +6498,9 @@ "pattern": "^ses.*" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } } } @@ -6371,7 +6547,10 @@ }, "body": {} }, - "required": ["path", "body"] + "required": [ + "path", + "body" + ] } } } @@ -6656,7 +6835,12 @@ "level": { "description": "Log level", "type": "string", - "enum": ["debug", "info", "error", "warn"] + "enum": [ + "debug", + "info", + "error", + "warn" + ] }, "message": { "description": "Log message", @@ -6671,7 +6855,11 @@ "additionalProperties": {} } }, - "required": ["service", "level", "message"] + "required": [ + "service", + "level", + "message" + ] } } } @@ -6772,7 +6960,12 @@ "type": "string" } }, - "required": ["name", "description", "location", "content"] + "required": [ + "name", + "description", + "location", + "content" + ] } } } @@ -6933,10 +7126,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.installation.update-available": { "type": "object", @@ -6952,10 +7150,15 @@ "type": "string" } }, - "required": ["version"] + "required": [ + "version" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Project": { "type": "object", @@ -7009,7 +7212,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "sandboxes": { "type": "array", @@ -7018,7 +7224,12 @@ } } }, - "required": ["id", "worktree", "time", "sandboxes"] + "required": [ + "id", + "worktree", + "time", + "sandboxes" + ] }, "Event.project.updated": { "type": "object", @@ -7031,7 +7242,10 @@ "$ref": "#/components/schemas/Project" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.server.instance.disposed": { "type": "object", @@ -7047,10 +7261,15 @@ "type": "string" } }, - "required": ["directory"] + "required": [ + "directory" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.server.connected": { "type": "object", @@ -7064,7 +7283,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.global.disposed": { "type": "object", @@ -7078,7 +7300,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.client.diagnostics": { "type": "object", @@ -7097,10 +7322,16 @@ "type": "string" } }, - "required": ["serverID", "path"] + "required": [ + "serverID", + "path" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.lsp.updated": { "type": "object", @@ -7114,7 +7345,10 @@ "properties": {} } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.edited": { "type": "object", @@ -7130,10 +7364,15 @@ "type": "string" } }, - "required": ["file"] + "required": [ + "file" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "OutputFormatText": { "type": "object", @@ -7143,7 +7382,9 @@ "const": "text" } }, - "required": ["type"] + "required": [ + "type" + ] }, "JSONSchema": { "type": "object", @@ -7169,7 +7410,10 @@ "maximum": 9007199254740991 } }, - "required": ["type", "schema"] + "required": [ + "type", + "schema" + ] }, "OutputFormat": { "anyOf": [ @@ -7201,10 +7445,20 @@ }, "status": { "type": "string", - "enum": ["added", "deleted", "modified"] + "enum": [ + "added", + "deleted", + "modified" + ] } }, - "required": ["file", "before", "after", "additions", "deletions"] + "required": [ + "file", + "before", + "after", + "additions", + "deletions" + ] }, "UserMessage": { "type": "object", @@ -7228,7 +7482,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "format": { "$ref": "#/components/schemas/OutputFormat" @@ -7249,7 +7505,9 @@ } } }, - "required": ["diffs"] + "required": [ + "diffs" + ] }, "agent": { "type": "string" @@ -7264,7 +7522,10 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "system": { "type": "string" @@ -7282,7 +7543,14 @@ "type": "string" } }, - "required": ["id", "sessionID", "role", "time", "agent", "model"] + "required": [ + "id", + "sessionID", + "role", + "time", + "agent", + "model" + ] }, "ProviderAuthError": { "type": "object", @@ -7301,10 +7569,16 @@ "type": "string" } }, - "required": ["providerID", "message"] + "required": [ + "providerID", + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "UnknownError": { "type": "object", @@ -7320,10 +7594,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageOutputLengthError": { "type": "object", @@ -7337,7 +7616,10 @@ "properties": {} } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "MessageAbortedError": { "type": "object", @@ -7353,10 +7635,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "StructuredOutputError": { "type": "object", @@ -7375,10 +7662,16 @@ "type": "number" } }, - "required": ["message", "retries"] + "required": [ + "message", + "retries" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "ContextOverflowError": { "type": "object", @@ -7397,10 +7690,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "APIError": { "type": "object", @@ -7443,10 +7741,16 @@ } } }, - "required": ["message", "isRetryable"] + "required": [ + "message", + "isRetryable" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "AssistantMessage": { "type": "object", @@ -7473,7 +7777,9 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] }, "error": { "anyOf": [ @@ -7526,7 +7832,10 @@ "type": "string" } }, - "required": ["cwd", "root"] + "required": [ + "cwd", + "root" + ] }, "summary": { "type": "boolean" @@ -7559,10 +7868,18 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] }, "structured": {}, "variant": { @@ -7611,10 +7928,15 @@ "$ref": "#/components/schemas/Message" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.removed": { "type": "object", @@ -7635,10 +7957,16 @@ "pattern": "^msg.*" } }, - "required": ["sessionID", "messageID"] + "required": [ + "sessionID", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "TextPart": { "type": "object", @@ -7678,7 +8006,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -7688,7 +8018,13 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "text"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text" + ] }, "SubtaskPart": { "type": "object", @@ -7728,13 +8064,24 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "prompt", + "description", + "agent" + ] }, "ReasoningPart": { "type": "object", @@ -7775,10 +8122,19 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "text", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "text", + "time" + ] }, "FilePartSourceText": { "type": "object", @@ -7797,7 +8153,11 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] }, "FileSource": { "type": "object", @@ -7813,7 +8173,11 @@ "type": "string" } }, - "required": ["text", "type", "path"] + "required": [ + "text", + "type", + "path" + ] }, "Range": { "type": "object", @@ -7828,7 +8192,10 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] }, "end": { "type": "object", @@ -7840,10 +8207,16 @@ "type": "number" } }, - "required": ["line", "character"] + "required": [ + "line", + "character" + ] } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "SymbolSource": { "type": "object", @@ -7870,7 +8243,14 @@ "maximum": 9007199254740991 } }, - "required": ["text", "type", "path", "range", "name", "kind"] + "required": [ + "text", + "type", + "path", + "range", + "name", + "kind" + ] }, "ResourceSource": { "type": "object", @@ -7889,7 +8269,12 @@ "type": "string" } }, - "required": ["text", "type", "clientName", "uri"] + "required": [ + "text", + "type", + "clientName", + "uri" + ] }, "FilePartSource": { "anyOf": [ @@ -7936,7 +8321,14 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["id", "sessionID", "messageID", "type", "mime", "url"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "mime", + "url" + ] }, "ToolStatePending": { "type": "object", @@ -7956,7 +8348,11 @@ "type": "string" } }, - "required": ["status", "input", "raw"] + "required": [ + "status", + "input", + "raw" + ] }, "ToolStateRunning": { "type": "object", @@ -7989,10 +8385,16 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] } }, - "required": ["status", "input", "time"] + "required": [ + "status", + "input", + "time" + ] }, "ToolStateCompleted": { "type": "object", @@ -8034,7 +8436,10 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] }, "attachments": { "type": "array", @@ -8043,7 +8448,14 @@ } } }, - "required": ["status", "input", "output", "title", "metadata", "time"] + "required": [ + "status", + "input", + "output", + "title", + "metadata", + "time" + ] }, "ToolStateError": { "type": "object", @@ -8079,10 +8491,18 @@ "type": "number" } }, - "required": ["start", "end"] + "required": [ + "start", + "end" + ] } }, - "required": ["status", "input", "error", "time"] + "required": [ + "status", + "input", + "error", + "time" + ] }, "ToolState": { "anyOf": [ @@ -8136,7 +8556,15 @@ "additionalProperties": {} } }, - "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "callID", + "tool", + "state" + ] }, "StepStartPart": { "type": "object", @@ -8161,7 +8589,12 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type"] + "required": [ + "id", + "sessionID", + "messageID", + "type" + ] }, "StepFinishPart": { "type": "object", @@ -8216,13 +8649,29 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "reasoning", "cache"] + "required": [ + "input", + "output", + "reasoning", + "cache" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "reason", + "cost", + "tokens" + ] }, "SnapshotPart": { "type": "object", @@ -8247,7 +8696,13 @@ "type": "string" } }, - "required": ["id", "sessionID", "messageID", "type", "snapshot"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "snapshot" + ] }, "PatchPart": { "type": "object", @@ -8278,7 +8733,14 @@ } } }, - "required": ["id", "sessionID", "messageID", "type", "hash", "files"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "hash", + "files" + ] }, "AgentPart": { "type": "object", @@ -8319,10 +8781,20 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "name"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "name" + ] }, "RetryPart": { "type": "object", @@ -8356,10 +8828,20 @@ "type": "number" } }, - "required": ["created"] + "required": [ + "created" + ] } }, - "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "attempt", + "error", + "time" + ] }, "CompactionPart": { "type": "object", @@ -8387,7 +8869,13 @@ "type": "boolean" } }, - "required": ["id", "sessionID", "messageID", "type", "auto"] + "required": [ + "id", + "sessionID", + "messageID", + "type", + "auto" + ] }, "Part": { "anyOf": [ @@ -8443,10 +8931,15 @@ "$ref": "#/components/schemas/Part" } }, - "required": ["part"] + "required": [ + "part" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.part.delta": { "type": "object", @@ -8477,10 +8970,19 @@ "type": "string" } }, - "required": ["sessionID", "messageID", "partID", "field", "delta"] + "required": [ + "sessionID", + "messageID", + "partID", + "field", + "delta" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.message.part.removed": { "type": "object", @@ -8505,10 +9007,17 @@ "pattern": "^prt.*" } }, - "required": ["sessionID", "messageID", "partID"] + "required": [ + "sessionID", + "messageID", + "partID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "PermissionRequest": { "type": "object", @@ -8554,10 +9063,20 @@ "type": "string" } }, - "required": ["messageID", "callID"] + "required": [ + "messageID", + "callID" + ] } }, - "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"] + "required": [ + "id", + "sessionID", + "permission", + "patterns", + "metadata", + "always" + ] }, "Event.permission.asked": { "type": "object", @@ -8570,7 +9089,10 @@ "$ref": "#/components/schemas/PermissionRequest" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.permission.replied": { "type": "object", @@ -8592,13 +9114,24 @@ }, "reply": { "type": "string", - "enum": ["once", "always", "reject"] + "enum": [ + "once", + "always", + "reject" + ] } }, - "required": ["sessionID", "requestID", "reply"] + "required": [ + "sessionID", + "requestID", + "reply" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "SessionStatus": { "anyOf": [ @@ -8610,7 +9143,9 @@ "const": "idle" } }, - "required": ["type"] + "required": [ + "type" + ] }, { "type": "object", @@ -8629,7 +9164,12 @@ "type": "number" } }, - "required": ["type", "attempt", "message", "next"] + "required": [ + "type", + "attempt", + "message", + "next" + ] }, { "type": "object", @@ -8639,7 +9179,9 @@ "const": "busy" } }, - "required": ["type"] + "required": [ + "type" + ] } ] }, @@ -8661,10 +9203,16 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": ["sessionID", "status"] + "required": [ + "sessionID", + "status" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.idle": { "type": "object", @@ -8681,10 +9229,15 @@ "pattern": "^ses.*" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "QuestionOption": { "type": "object", @@ -8698,7 +9251,10 @@ "type": "string" } }, - "required": ["label", "description"] + "required": [ + "label", + "description" + ] }, "QuestionInfo": { "type": "object", @@ -8727,7 +9283,11 @@ "type": "boolean" } }, - "required": ["question", "header", "options"] + "required": [ + "question", + "header", + "options" + ] }, "QuestionRequest": { "type": "object", @@ -8758,10 +9318,17 @@ "type": "string" } }, - "required": ["messageID", "callID"] + "required": [ + "messageID", + "callID" + ] } }, - "required": ["id", "sessionID", "questions"] + "required": [ + "id", + "sessionID", + "questions" + ] }, "Event.question.asked": { "type": "object", @@ -8774,7 +9341,10 @@ "$ref": "#/components/schemas/QuestionRequest" } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "QuestionAnswer": { "type": "array", @@ -8807,10 +9377,17 @@ } } }, - "required": ["sessionID", "requestID", "answers"] + "required": [ + "sessionID", + "requestID", + "answers" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.question.rejected": { "type": "object", @@ -8831,10 +9408,16 @@ "pattern": "^que.*" } }, - "required": ["sessionID", "requestID"] + "required": [ + "sessionID", + "requestID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.compacted": { "type": "object", @@ -8851,10 +9434,15 @@ "pattern": "^ses.*" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.file.watcher.updated": { "type": "object", @@ -8886,10 +9474,16 @@ ] } }, - "required": ["file", "event"] + "required": [ + "file", + "event" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Todo": { "type": "object", @@ -8907,7 +9501,11 @@ "type": "string" } }, - "required": ["content", "status", "priority"] + "required": [ + "content", + "status", + "priority" + ] }, "Event.todo.updated": { "type": "object", @@ -8930,10 +9528,16 @@ } } }, - "required": ["sessionID", "todos"] + "required": [ + "sessionID", + "todos" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.prompt.append": { "type": "object", @@ -8949,10 +9553,15 @@ "type": "string" } }, - "required": ["text"] + "required": [ + "text" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.command.execute": { "type": "object", @@ -8993,10 +9602,15 @@ ] } }, - "required": ["command"] + "required": [ + "command" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.toast.show": { "type": "object", @@ -9016,7 +9630,12 @@ }, "variant": { "type": "string", - "enum": ["info", "success", "warning", "error"] + "enum": [ + "info", + "success", + "warning", + "error" + ] }, "duration": { "description": "Duration in milliseconds", @@ -9024,10 +9643,16 @@ "type": "number" } }, - "required": ["message", "variant"] + "required": [ + "message", + "variant" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.tui.session.select": { "type": "object", @@ -9045,10 +9670,15 @@ "pattern": "^ses.*" } }, - "required": ["sessionID"] + "required": [ + "sessionID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.mcp.tools.changed": { "type": "object", @@ -9064,10 +9694,15 @@ "type": "string" } }, - "required": ["server"] + "required": [ + "server" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.mcp.browser.open.failed": { "type": "object", @@ -9086,10 +9721,16 @@ "type": "string" } }, - "required": ["mcpName", "url"] + "required": [ + "mcpName", + "url" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.command.executed": { "type": "object", @@ -9116,14 +9757,26 @@ "pattern": "^msg.*" } }, - "required": ["name", "sessionID", "arguments", "messageID"] + "required": [ + "name", + "sessionID", + "arguments", + "messageID" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "PermissionAction": { "type": "string", - "enum": ["allow", "deny", "ask"] + "enum": [ + "allow", + "deny", + "ask" + ] }, "PermissionRule": { "type": "object", @@ -9138,7 +9791,11 @@ "$ref": "#/components/schemas/PermissionAction" } }, - "required": ["permission", "pattern", "action"] + "required": [ + "permission", + "pattern", + "action" + ] }, "PermissionRuleset": { "type": "array", @@ -9189,7 +9846,11 @@ } } }, - "required": ["additions", "deletions", "files"] + "required": [ + "additions", + "deletions", + "files" + ] }, "share": { "type": "object", @@ -9198,7 +9859,9 @@ "type": "string" } }, - "required": ["url"] + "required": [ + "url" + ] }, "title": { "type": "string" @@ -9222,7 +9885,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -9245,10 +9911,20 @@ "type": "string" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] } }, - "required": ["id", "slug", "projectID", "directory", "title", "version", "time"] + "required": [ + "id", + "slug", + "projectID", + "directory", + "title", + "version", + "time" + ] }, "Event.session.created": { "type": "object", @@ -9264,10 +9940,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.updated": { "type": "object", @@ -9283,10 +9964,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.deleted": { "type": "object", @@ -9302,10 +9988,15 @@ "$ref": "#/components/schemas/Session" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.diff": { "type": "object", @@ -9328,10 +10019,16 @@ } } }, - "required": ["sessionID", "diff"] + "required": [ + "sessionID", + "diff" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.session.error": { "type": "object", @@ -9375,7 +10072,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.vcs.branch.updated": { "type": "object", @@ -9393,7 +10093,10 @@ } } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.workspace.ready": { "type": "object", @@ -9409,10 +10112,15 @@ "type": "string" } }, - "required": ["name"] + "required": [ + "name" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.workspace.failed": { "type": "object", @@ -9428,10 +10136,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Pty": { "type": "object", @@ -9457,13 +10170,24 @@ }, "status": { "type": "string", - "enum": ["running", "exited"] + "enum": [ + "running", + "exited" + ] }, "pid": { "type": "number" } }, - "required": ["id", "title", "command", "args", "cwd", "status", "pid"] + "required": [ + "id", + "title", + "command", + "args", + "cwd", + "status", + "pid" + ] }, "Event.pty.created": { "type": "object", @@ -9479,10 +10203,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.updated": { "type": "object", @@ -9498,10 +10227,15 @@ "$ref": "#/components/schemas/Pty" } }, - "required": ["info"] + "required": [ + "info" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.exited": { "type": "object", @@ -9521,10 +10255,16 @@ "type": "number" } }, - "required": ["id", "exitCode"] + "required": [ + "id", + "exitCode" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.pty.deleted": { "type": "object", @@ -9541,10 +10281,15 @@ "pattern": "^pty.*" } }, - "required": ["id"] + "required": [ + "id" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.worktree.ready": { "type": "object", @@ -9563,10 +10308,16 @@ "type": "string" } }, - "required": ["name", "branch"] + "required": [ + "name", + "branch" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event.worktree.failed": { "type": "object", @@ -9582,10 +10333,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["type", "properties"] + "required": [ + "type", + "properties" + ] }, "Event": { "anyOf": [ @@ -9736,12 +10492,20 @@ "$ref": "#/components/schemas/Event" } }, - "required": ["directory", "payload"] + "required": [ + "directory", + "payload" + ] }, "LogLevel": { "description": "Log level", "type": "string", - "enum": ["DEBUG", "INFO", "WARN", "ERROR"] + "enum": [ + "DEBUG", + "INFO", + "WARN", + "ERROR" + ] }, "ServerConfig": { "description": "Server configuration for opencode serve and web commands", @@ -9777,7 +10541,11 @@ }, "PermissionActionConfig": { "type": "string", - "enum": ["ask", "allow", "deny"] + "enum": [ + "ask", + "allow", + "deny" + ] }, "PermissionObjectConfig": { "type": "object", @@ -9908,7 +10676,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "hidden": { "description": "Hide this subagent from the @ autocomplete menu (default: false, only applies to mode: subagent)", @@ -9930,7 +10702,15 @@ }, { "type": "string", - "enum": ["primary", "secondary", "accent", "success", "warning", "error", "info"] + "enum": [ + "primary", + "secondary", + "accent", + "success", + "warning", + "error", + "info" + ] } ] }, @@ -10016,10 +10796,15 @@ "properties": { "field": { "type": "string", - "enum": ["reasoning_content", "reasoning_details"] + "enum": [ + "reasoning_content", + "reasoning_details" + ] } }, - "required": ["field"], + "required": [ + "field" + ], "additionalProperties": false } ] @@ -10055,10 +10840,16 @@ "type": "number" } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "limit": { "type": "object", @@ -10073,7 +10864,10 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "modalities": { "type": "object", @@ -10082,25 +10876,44 @@ "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } }, "output": { "type": "array", "items": { "type": "string", - "enum": ["text", "audio", "image", "video", "pdf"] + "enum": [ + "text", + "audio", + "image", + "video", + "pdf" + ] } } }, - "required": ["input", "output"] + "required": [ + "input", + "output" + ] }, "experimental": { "type": "boolean" }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated"] + "enum": [ + "alpha", + "beta", + "deprecated" + ] }, "options": { "type": "object", @@ -10242,7 +11055,10 @@ "maximum": 9007199254740991 } }, - "required": ["type", "command"], + "required": [ + "type", + "command" + ], "additionalProperties": false }, "McpOAuthConfig": { @@ -10308,13 +11124,19 @@ "maximum": 9007199254740991 } }, - "required": ["type", "url"], + "required": [ + "type", + "url" + ], "additionalProperties": false }, "LayoutConfig": { "description": "@deprecated Always uses stretch layout.", "type": "string", - "enum": ["auto", "stretch"] + "enum": [ + "auto", + "stretch" + ] }, "Config": { "type": "object", @@ -10354,7 +11176,9 @@ "type": "boolean" } }, - "required": ["template"] + "required": [ + "template" + ] } }, "skills": { @@ -10400,7 +11224,11 @@ "share": { "description": "Control sharing behavior:'manual' allows manual sharing via commands, 'auto' enables automatic sharing, 'disabled' disables all sharing", "type": "string", - "enum": ["manual", "auto", "disabled"] + "enum": [ + "manual", + "auto", + "disabled" + ] }, "autoshare": { "description": "@deprecated Use 'share' field instead. Share newly created sessions automatically", @@ -10528,7 +11356,9 @@ "type": "boolean" } }, - "required": ["enabled"], + "required": [ + "enabled" + ], "additionalProperties": false } ] @@ -10598,7 +11428,9 @@ "const": true } }, - "required": ["disabled"] + "required": [ + "disabled" + ] }, { "type": "object", @@ -10635,7 +11467,9 @@ "additionalProperties": {} } }, - "required": ["command"] + "required": [ + "command" + ] } ] } @@ -10747,7 +11581,11 @@ "const": false } }, - "required": ["data", "errors", "success"] + "required": [ + "data", + "errors", + "success" + ] }, "OAuth": { "type": "object", @@ -10772,7 +11610,12 @@ "type": "string" } }, - "required": ["type", "refresh", "access", "expires"] + "required": [ + "type", + "refresh", + "access", + "expires" + ] }, "ApiAuth": { "type": "object", @@ -10785,7 +11628,10 @@ "type": "string" } }, - "required": ["type", "key"] + "required": [ + "type", + "key" + ] }, "WellKnownAuth": { "type": "object", @@ -10801,7 +11647,11 @@ "type": "string" } }, - "required": ["type", "key", "token"] + "required": [ + "type", + "key", + "token" + ] }, "Auth": { "anyOf": [ @@ -10830,10 +11680,15 @@ "type": "string" } }, - "required": ["message"] + "required": [ + "message" + ] } }, - "required": ["name", "data"] + "required": [ + "name", + "data" + ] }, "Model": { "type": "object", @@ -10857,7 +11712,11 @@ "type": "string" } }, - "required": ["id", "url", "npm"] + "required": [ + "id", + "url", + "npm" + ] }, "name": { "type": "string" @@ -10899,7 +11758,13 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] }, "output": { "type": "object", @@ -10920,7 +11785,13 @@ "type": "boolean" } }, - "required": ["text", "audio", "image", "video", "pdf"] + "required": [ + "text", + "audio", + "image", + "video", + "pdf" + ] }, "interleaved": { "anyOf": [ @@ -10932,15 +11803,28 @@ "properties": { "field": { "type": "string", - "enum": ["reasoning_content", "reasoning_details"] + "enum": [ + "reasoning_content", + "reasoning_details" + ] } }, - "required": ["field"] + "required": [ + "field" + ] } ] } }, - "required": ["temperature", "reasoning", "attachment", "toolcall", "input", "output", "interleaved"] + "required": [ + "temperature", + "reasoning", + "attachment", + "toolcall", + "input", + "output", + "interleaved" + ] }, "cost": { "type": "object", @@ -10961,7 +11845,10 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] }, "experimentalOver200K": { "type": "object", @@ -10982,13 +11869,24 @@ "type": "number" } }, - "required": ["read", "write"] + "required": [ + "read", + "write" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] } }, - "required": ["input", "output", "cache"] + "required": [ + "input", + "output", + "cache" + ] }, "limit": { "type": "object", @@ -11003,11 +11901,19 @@ "type": "number" } }, - "required": ["context", "output"] + "required": [ + "context", + "output" + ] }, "status": { "type": "string", - "enum": ["alpha", "beta", "deprecated", "active"] + "enum": [ + "alpha", + "beta", + "deprecated", + "active" + ] }, "options": { "type": "object", @@ -11067,7 +11973,12 @@ }, "source": { "type": "string", - "enum": ["env", "config", "custom", "api"] + "enum": [ + "env", + "config", + "custom", + "api" + ] }, "env": { "type": "array", @@ -11095,7 +12006,14 @@ } } }, - "required": ["id", "name", "source", "env", "options", "models"] + "required": [ + "id", + "name", + "source", + "env", + "options", + "models" + ] }, "ToolIDs": { "type": "array", @@ -11114,7 +12032,11 @@ }, "parameters": {} }, - "required": ["id", "description", "parameters"] + "required": [ + "id", + "description", + "parameters" + ] }, "ToolList": { "type": "array", @@ -11174,7 +12096,15 @@ "type": "string" } }, - "required": ["id", "type", "branch", "name", "directory", "extra", "projectID"] + "required": [ + "id", + "type", + "branch", + "name", + "directory", + "extra", + "projectID" + ] }, "Worktree": { "type": "object", @@ -11189,7 +12119,11 @@ "type": "string" } }, - "required": ["name", "branch", "directory"] + "required": [ + "name", + "branch", + "directory" + ] }, "WorktreeCreateInput": { "type": "object", @@ -11210,7 +12144,9 @@ "type": "string" } }, - "required": ["directory"] + "required": [ + "directory" + ] }, "WorktreeResetInput": { "type": "object", @@ -11219,7 +12155,9 @@ "type": "string" } }, - "required": ["directory"] + "required": [ + "directory" + ] }, "ProjectSummary": { "type": "object", @@ -11234,7 +12172,10 @@ "type": "string" } }, - "required": ["id", "worktree"] + "required": [ + "id", + "worktree" + ] }, "GlobalSession": { "type": "object", @@ -11279,7 +12220,11 @@ } } }, - "required": ["additions", "deletions", "files"] + "required": [ + "additions", + "deletions", + "files" + ] }, "share": { "type": "object", @@ -11288,7 +12233,9 @@ "type": "string" } }, - "required": ["url"] + "required": [ + "url" + ] }, "title": { "type": "string" @@ -11312,7 +12259,10 @@ "type": "number" } }, - "required": ["created", "updated"] + "required": [ + "created", + "updated" + ] }, "permission": { "$ref": "#/components/schemas/PermissionRuleset" @@ -11335,7 +12285,9 @@ "type": "string" } }, - "required": ["messageID"] + "required": [ + "messageID" + ] }, "project": { "anyOf": [ @@ -11348,7 +12300,16 @@ ] } }, - "required": ["id", "slug", "projectID", "directory", "title", "version", "time", "project"] + "required": [ + "id", + "slug", + "projectID", + "directory", + "title", + "version", + "time", + "project" + ] }, "McpResource": { "type": "object", @@ -11369,7 +12330,11 @@ "type": "string" } }, - "required": ["name", "uri", "client"] + "required": [ + "name", + "uri", + "client" + ] }, "TextPartInput": { "type": "object", @@ -11401,7 +12366,9 @@ "type": "number" } }, - "required": ["start"] + "required": [ + "start" + ] }, "metadata": { "type": "object", @@ -11411,7 +12378,10 @@ "additionalProperties": {} } }, - "required": ["type", "text"] + "required": [ + "type", + "text" + ] }, "FilePartInput": { "type": "object", @@ -11437,7 +12407,11 @@ "$ref": "#/components/schemas/FilePartSource" } }, - "required": ["type", "mime", "url"] + "required": [ + "type", + "mime", + "url" + ] }, "AgentPartInput": { "type": "object", @@ -11470,10 +12444,17 @@ "maximum": 9007199254740991 } }, - "required": ["value", "start", "end"] + "required": [ + "value", + "start", + "end" + ] } }, - "required": ["type", "name"] + "required": [ + "type", + "name" + ] }, "SubtaskPartInput": { "type": "object", @@ -11505,13 +12486,21 @@ "type": "string" } }, - "required": ["providerID", "modelID"] + "required": [ + "providerID", + "modelID" + ] }, "command": { "type": "string" } }, - "required": ["type", "prompt", "description", "agent"] + "required": [ + "type", + "prompt", + "description", + "agent" + ] }, "ProviderAuthMethod": { "type": "object", @@ -11532,7 +12521,10 @@ "type": "string" } }, - "required": ["type", "label"] + "required": [ + "type", + "label" + ] }, "ProviderAuthAuthorization": { "type": "object", @@ -11556,7 +12548,11 @@ "type": "string" } }, - "required": ["url", "method", "instructions"] + "required": [ + "url", + "method", + "instructions" + ] }, "Symbol": { "type": "object", @@ -11577,10 +12573,17 @@ "$ref": "#/components/schemas/Range" } }, - "required": ["uri", "range"] + "required": [ + "uri", + "range" + ] } }, - "required": ["name", "kind", "location"] + "required": [ + "name", + "kind", + "location" + ] }, "FileNode": { "type": "object", @@ -11596,20 +12599,32 @@ }, "type": { "type": "string", - "enum": ["file", "directory"] + "enum": [ + "file", + "directory" + ] }, "ignored": { "type": "boolean" } }, - "required": ["name", "path", "absolute", "type", "ignored"] + "required": [ + "name", + "path", + "absolute", + "type", + "ignored" + ] }, "FileContent": { "type": "object", "properties": { "type": { "type": "string", - "enum": ["text", "binary"] + "enum": [ + "text", + "binary" + ] }, "content": { "type": "string" @@ -11656,14 +12671,24 @@ } } }, - "required": ["oldStart", "oldLines", "newStart", "newLines", "lines"] + "required": [ + "oldStart", + "oldLines", + "newStart", + "newLines", + "lines" + ] } }, "index": { "type": "string" } }, - "required": ["oldFileName", "newFileName", "hunks"] + "required": [ + "oldFileName", + "newFileName", + "hunks" + ] }, "encoding": { "type": "string", @@ -11673,7 +12698,10 @@ "type": "string" } }, - "required": ["type", "content"] + "required": [ + "type", + "content" + ] }, "File": { "type": "object", @@ -11693,10 +12721,19 @@ }, "status": { "type": "string", - "enum": ["added", "deleted", "modified"] + "enum": [ + "added", + "deleted", + "modified" + ] } }, - "required": ["path", "added", "removed", "status"] + "required": [ + "path", + "added", + "removed", + "status" + ] }, "MCPStatusConnected": { "type": "object", @@ -11706,7 +12743,9 @@ "const": "connected" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusDisabled": { "type": "object", @@ -11716,7 +12755,9 @@ "const": "disabled" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusFailed": { "type": "object", @@ -11729,7 +12770,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatusNeedsAuth": { "type": "object", @@ -11739,7 +12783,9 @@ "const": "needs_auth" } }, - "required": ["status"] + "required": [ + "status" + ] }, "MCPStatusNeedsClientRegistration": { "type": "object", @@ -11752,7 +12798,10 @@ "type": "string" } }, - "required": ["status", "error"] + "required": [ + "status", + "error" + ] }, "MCPStatus": { "anyOf": [ @@ -11792,7 +12841,13 @@ "type": "string" } }, - "required": ["home", "state", "config", "worktree", "directory"] + "required": [ + "home", + "state", + "config", + "worktree", + "directory" + ] }, "VcsInfo": { "type": "object", @@ -11801,7 +12856,9 @@ "type": "string" } }, - "required": ["branch"] + "required": [ + "branch" + ] }, "Command": { "type": "object", @@ -11820,7 +12877,11 @@ }, "source": { "type": "string", - "enum": ["command", "mcp", "skill"] + "enum": [ + "command", + "mcp", + "skill" + ] }, "template": { "anyOf": [ @@ -11842,7 +12903,11 @@ } } }, - "required": ["name", "template", "hints"] + "required": [ + "name", + "template", + "hints" + ] }, "Agent": { "type": "object", @@ -11855,7 +12920,11 @@ }, "mode": { "type": "string", - "enum": ["subagent", "primary", "all"] + "enum": [ + "subagent", + "primary", + "all" + ] }, "native": { "type": "boolean" @@ -11885,7 +12954,10 @@ "type": "string" } }, - "required": ["modelID", "providerID"] + "required": [ + "modelID", + "providerID" + ] }, "variant": { "type": "string" @@ -11906,7 +12978,12 @@ "maximum": 9007199254740991 } }, - "required": ["name", "mode", "permission", "options"] + "required": [ + "name", + "mode", + "permission", + "options" + ] }, "LSPStatus": { "type": "object", @@ -11933,7 +13010,12 @@ ] } }, - "required": ["id", "name", "root", "status"] + "required": [ + "id", + "name", + "root", + "status" + ] }, "FormatterStatus": { "type": "object", @@ -11951,8 +13033,12 @@ "type": "boolean" } }, - "required": ["name", "extensions", "enabled"] + "required": [ + "name", + "extensions", + "enabled" + ] } } } -} +} \ No newline at end of file