Compare commits

...

16 Commits

Author SHA1 Message Date
Dax Raad 406fda11dc Stabilize runner cancel defect test 2026-05-11 00:38:15 -04:00
Dax Raad d7b74c575b Optimize session usage migration aggregation 2026-05-10 23:47:03 -04:00
Dax Raad 0454c8b442 Merge remote-tracking branch 'origin/dev' into opencode/neon-pixel 2026-05-10 23:25:50 -04:00
Dax Raad 373ede732a Update opencode workspace permissions 2026-05-10 23:25:49 -04:00
Dax Raad e260669e3e Trace session usage migration pages 2026-05-10 23:14:52 -04:00
Dax Raad a1bc014415 Remove session usage migration benchmark 2026-05-10 23:12:04 -04:00
Dax Raad 995fc17ec1 Add session usage migration benchmark 2026-05-10 23:09:33 -04:00
Dax Raad 474ecdca2a Throttle session usage data migration 2026-05-10 21:10:46 -04:00
Dax Raad 988d406ff4 Preserve message row union types 2026-05-10 20:31:51 -04:00
Dax Raad e2c00f8794 Backfill session usage from messages 2026-05-10 20:31:08 -04:00
Dax Raad 35089ce236 Remove TUI session usage projection 2026-05-10 20:27:11 -04:00
Dax Raad 1819fe2711 Move session usage backfill to data migration 2026-05-10 20:23:40 -04:00
Dax Raad 2eaceaa845 Merge remote-tracking branch 'origin/dev' into opencode/neon-pixel 2026-05-10 20:19:03 -04:00
Dax Raad 9854dc5978 Optimize session usage migration 2026-05-10 00:30:51 -04:00
Dax Raad 4a0c53df08 Use session usage totals in TUI 2026-05-10 00:04:55 -04:00
Dax Raad 3d6e04d18b Track session usage totals 2026-05-09 23:58:46 -04:00
20 changed files with 1882 additions and 26 deletions
+1 -5
View File
@@ -1,11 +1,7 @@
{
"$schema": "https://opencode.ai/config.json",
"provider": {},
"permission": {
"edit": {
"packages/opencode/migration/*": "ask",
},
},
"permission": {},
"mcp": {},
"tools": {
"github-triage": false,
@@ -0,0 +1,6 @@
ALTER TABLE `session` ADD `cost` real DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE `session` ADD `tokens_input` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE `session` ADD `tokens_output` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE `session` ADD `tokens_reasoning` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE `session` ADD `tokens_cache_read` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
ALTER TABLE `session` ADD `tokens_cache_write` integer DEFAULT 0 NOT NULL;
@@ -0,0 +1,1591 @@
{
"version": "7",
"dialect": "sqlite",
"id": "be5eae31-b7f8-4292-8827-c36a524abd1b",
"prevIds": [
"630a93f2-c6c6-4191-a351-868d8f3a05d4"
],
"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_message",
"entityType": "tables"
},
{
"name": "session",
"entityType": "tables"
},
{
"name": "todo",
"entityType": "tables"
},
{
"name": "session_share",
"entityType": "tables"
},
{
"name": "event_sequence",
"entityType": "tables"
},
{
"name": "event",
"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": true,
"autoincrement": false,
"default": "''",
"generated": null,
"name": "name",
"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": "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": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_used",
"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_url_override",
"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_message"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "session_id",
"entityType": "columns",
"table": "session_message"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "type",
"entityType": "columns",
"table": "session_message"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_created",
"entityType": "columns",
"table": "session_message"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "time_updated",
"entityType": "columns",
"table": "session_message"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "data",
"entityType": "columns",
"table": "session_message"
},
{
"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": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "path",
"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": "real",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "cost",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "tokens_input",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "tokens_output",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "tokens_reasoning",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "tokens_cache_read",
"entityType": "columns",
"table": "session"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": "0",
"generated": null,
"name": "tokens_cache_write",
"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": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "agent",
"entityType": "columns",
"table": "session"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "model",
"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"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "aggregate_id",
"entityType": "columns",
"table": "event_sequence"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "seq",
"entityType": "columns",
"table": "event_sequence"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "owner_id",
"entityType": "columns",
"table": "event_sequence"
},
{
"type": "text",
"notNull": false,
"autoincrement": false,
"default": null,
"generated": null,
"name": "id",
"entityType": "columns",
"table": "event"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "aggregate_id",
"entityType": "columns",
"table": "event"
},
{
"type": "integer",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "seq",
"entityType": "columns",
"table": "event"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "type",
"entityType": "columns",
"table": "event"
},
{
"type": "text",
"notNull": true,
"autoincrement": false,
"default": null,
"generated": null,
"name": "data",
"entityType": "columns",
"table": "event"
},
{
"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": [
"session_id"
],
"tableTo": "session",
"columnsTo": [
"id"
],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_session_message_session_id_session_id_fk",
"entityType": "fks",
"table": "session_message"
},
{
"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": [
"aggregate_id"
],
"tableTo": "event_sequence",
"columnsTo": [
"aggregate_id"
],
"onUpdate": "NO ACTION",
"onDelete": "CASCADE",
"nameExplicit": false,
"name": "fk_event_aggregate_id_event_sequence_aggregate_id_fk",
"entityType": "fks",
"table": "event"
},
{
"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_message_pk",
"table": "session_message",
"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": [
"aggregate_id"
],
"nameExplicit": false,
"name": "event_sequence_pk",
"table": "event_sequence",
"entityType": "pks"
},
{
"columns": [
"id"
],
"nameExplicit": false,
"name": "event_pk",
"table": "event",
"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": "session_id",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "session_message_session_idx",
"entityType": "indexes",
"table": "session_message"
},
{
"columns": [
{
"value": "session_id",
"isExpression": false
},
{
"value": "type",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "session_message_session_type_idx",
"entityType": "indexes",
"table": "session_message"
},
{
"columns": [
{
"value": "time_created",
"isExpression": false
}
],
"isUnique": false,
"where": null,
"origin": "manual",
"name": "session_message_time_created_idx",
"entityType": "indexes",
"table": "session_message"
},
{
"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": []
}
+2 -10
View File
@@ -164,8 +164,8 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
Effect.gen(function* () {
const messages = yield* svc.messages({ sessionID: session.id })
let sessionCost = 0
let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
const sessionCost = session.cost ?? 0
const sessionTokens = session.tokens ?? { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
let sessionToolUsage: Record<string, number> = {}
let sessionModelUsage: Record<
string,
@@ -178,8 +178,6 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
for (const message of messages) {
if (message.info.role === "assistant") {
sessionCost += message.info.cost || 0
const modelKey = `${message.info.providerID}/${message.info.modelID}`
if (!sessionModelUsage[modelKey]) {
sessionModelUsage[modelKey] = {
@@ -192,12 +190,6 @@ const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
sessionModelUsage[modelKey].cost += message.info.cost || 0
if (message.info.tokens) {
sessionTokens.input += message.info.tokens.input || 0
sessionTokens.output += message.info.tokens.output || 0
sessionTokens.reasoning += message.info.tokens.reasoning || 0
sessionTokens.cache.read += message.info.tokens.cache?.read || 0
sessionTokens.cache.write += message.info.tokens.cache?.write || 0
sessionModelUsage[modelKey].tokens.input += message.info.tokens.input || 0
sessionModelUsage[modelKey].tokens.output +=
(message.info.tokens.output || 0) + (message.info.tokens.reasoning || 0)
@@ -337,6 +337,7 @@ export function Prompt(props: PromptProps) {
const usage = createMemo(() => {
if (!props.sessionID) return
const session = sync.session.get(props.sessionID)
const msg = sync.data.message[props.sessionID] ?? []
const last = msg.findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
if (!last) return
@@ -347,7 +348,7 @@ export function Prompt(props: PromptProps) {
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
const cost = session?.cost ?? 0
return {
context: pct ? `${Locale.number(tokens)} (${pct})` : Locale.number(tokens),
cost: cost > 0 ? money.format(cost) : undefined,
@@ -346,7 +346,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
case "message.part.removed": {
const parts = store.part[event.properties.messageID]
const result = Binary.search(parts, event.properties.partID, (p) => p.id)
if (result.found)
if (result.found) {
setStore(
"part",
event.properties.messageID,
@@ -354,6 +354,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
draft.splice(result.index, 1)
}),
)
}
break
}
@@ -13,7 +13,8 @@ const money = new Intl.NumberFormat("en-US", {
function View(props: { api: TuiPluginApi; session_id: string }) {
const theme = () => props.api.theme.current
const msg = createMemo(() => props.api.state.session.messages(props.session_id))
const cost = createMemo(() => msg().reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0))
const session = createMemo(() => props.api.state.session.get(props.session_id))
const cost = createMemo(() => session()?.cost ?? 0)
const state = createMemo(() => {
const last = msg().findLast((item): item is AssistantMessage => item.role === "assistant" && item.tokens.output > 0)
@@ -147,6 +147,9 @@ function stateApi(sync: ReturnType<typeof useSync>): TuiPluginApi["state"] {
count() {
return sync.data.session.length
},
get(sessionID) {
return sync.session.get(sessionID)
},
diff(sessionID) {
return (sync.data.session_diff[sessionID] ?? []).flatMap((item) =>
item.file === undefined ? [] : [{ ...item, file: item.file }],
@@ -42,7 +42,7 @@ export function SubagentFooter() {
const model = sync.data.provider.find((item) => item.id === last.providerID)?.models[last.modelID]
const pct = model?.limit.context ? `${Math.round((tokens / model.limit.context) * 100)}%` : undefined
const cost = msg.reduce((sum, item) => sum + (item.role === "assistant" ? item.cost : 0), 0)
const cost = session()?.cost ?? 0
const money = new Intl.NumberFormat("en-US", {
style: "currency",
+98 -2
View File
@@ -2,7 +2,9 @@ import { Context, Effect, Layer } from "effect"
import { Database } from "./storage/db"
import { DataMigrationTable } from "./data-migration.sql"
import * as Log from "@opencode-ai/core/util/log"
import { eq } from "drizzle-orm"
import { and, asc, eq, gt, inArray, sql } from "drizzle-orm"
import { MessageTable, SessionTable } from "./session/session.sql"
import type { SessionID } from "./session/schema"
export type Migration<R = never> = {
name: string
@@ -18,7 +20,101 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Da
export const layer = Layer.effect(
Service,
Effect.gen(function* () {
const migrations: Migration[] = []
const migrations: Migration[] = [
{
name: "session_usage_from_messages",
run: Effect.gen(function* () {
type Usage = {
cost: number
tokens: { input: number; output: number; reasoning: number; cache: { read: number; write: number } }
}
for (let cursor: SessionID | undefined, page = 1; ; page++) {
const next = yield* Effect.gen(function* () {
const sessions = yield* Effect.sync(() =>
Database.use((db) =>
db
.select({ id: SessionTable.id })
.from(SessionTable)
.where(cursor ? gt(SessionTable.id, cursor) : undefined)
.orderBy(asc(SessionTable.id))
.limit(100)
.all(),
),
)
if (sessions.length === 0) return
yield* Effect.sync(() =>
Database.transaction((db) => {
const usageBySession = new Map<SessionID, Usage>(
sessions.map((session) => [
session.id,
{ cost: 0, tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } } },
]),
)
for (const row of db
.select({
session_id: MessageTable.session_id,
cost: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.cost'), 0)), 0)`,
tokens_input: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.input'), 0)), 0)`,
tokens_output: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.output'), 0)), 0)`,
tokens_reasoning: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.reasoning'), 0)), 0)`,
tokens_cache_read: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.cache.read'), 0)), 0)`,
tokens_cache_write: sql<number>`coalesce(sum(coalesce(json_extract(${MessageTable.data}, '$.tokens.cache.write'), 0)), 0)`,
})
.from(MessageTable)
.where(
and(
inArray(MessageTable.session_id, sessions.map((session) => session.id)),
sql`json_extract(${MessageTable.data}, '$.role') = 'assistant'`,
),
)
.groupBy(MessageTable.session_id)
.all()) {
const current = usageBySession.get(row.session_id)
if (!current) continue
current.cost = row.cost
current.tokens.input = row.tokens_input
current.tokens.output = row.tokens_output
current.tokens.reasoning = row.tokens_reasoning
current.tokens.cache.read = row.tokens_cache_read
current.tokens.cache.write = row.tokens_cache_write
}
for (const [sessionID, value] of usageBySession) {
db.update(SessionTable)
.set({
cost: value.cost,
tokens_input: value.tokens.input,
tokens_output: value.tokens.output,
tokens_reasoning: value.tokens.reasoning,
tokens_cache_read: value.tokens.cache.read,
tokens_cache_write: value.tokens.cache.write,
})
.where(eq(SessionTable.id, sessionID))
.run()
}
}),
)
return sessions.at(-1)?.id
}).pipe(
Effect.withSpan("DataMigration.sessionUsage.page", {
attributes: {
"data_migration.name": "session_usage_from_messages",
"data_migration.page": page,
"data_migration.cursor": cursor ?? "",
},
}),
)
if (!next) return
cursor = next
yield* Effect.sleep("10 millis")
}
}),
},
]
yield* Effect.gen(function* () {
if (migrations.length === 0) return
@@ -1,6 +1,8 @@
import { NotFoundError } from "@/storage/storage"
import { eq } from "drizzle-orm"
import { and } from "drizzle-orm"
import { sql } from "drizzle-orm"
import type { TxOrDb } from "@/storage/db"
import { SyncEvent } from "@/sync"
import * as Session from "./session"
import { MessageV2 } from "./message-v2"
@@ -19,6 +21,28 @@ function foreign(err: unknown) {
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> | null } : T
type Usage = Pick<MessageV2.StepFinishPart, "cost" | "tokens">
function usage(part: MessageV2.Part | (typeof PartTable.$inferSelect)["data"]): Usage | undefined {
if (part.type !== "step-finish") return undefined
if (!("cost" in part) || !("tokens" in part)) return undefined
return { cost: part.cost, tokens: part.tokens }
}
function applyUsage(db: TxOrDb, sessionID: Session.Info["id"], value: Usage, sign = 1) {
db.update(SessionTable)
.set({
cost: sql`${SessionTable.cost} + ${value.cost * sign}`,
tokens_input: sql`${SessionTable.tokens_input} + ${value.tokens.input * sign}`,
tokens_output: sql`${SessionTable.tokens_output} + ${value.tokens.output * sign}`,
tokens_reasoning: sql`${SessionTable.tokens_reasoning} + ${value.tokens.reasoning * sign}`,
tokens_cache_read: sql`${SessionTable.tokens_cache_read} + ${value.tokens.cache.read * sign}`,
tokens_cache_write: sql`${SessionTable.tokens_cache_write} + ${value.tokens.cache.write * sign}`,
})
.where(eq(SessionTable.id, sessionID))
.run()
}
function grab<T extends object, K1 extends keyof T, X>(
obj: T,
field1: K1,
@@ -54,6 +78,12 @@ export function toPartialRow(info: DeepPartial<Session.Info>) {
summary_deletions: grab(info, "summary", (v) => grab(v, "deletions")),
summary_files: grab(info, "summary", (v) => grab(v, "files")),
summary_diffs: grab(info, "summary", (v) => grab(v, "diffs")),
cost: grab(info, "cost"),
tokens_input: grab(info, "tokens", (v) => grab(v, "input")),
tokens_output: grab(info, "tokens", (v) => grab(v, "output")),
tokens_reasoning: grab(info, "tokens", (v) => grab(v, "reasoning")),
tokens_cache_read: grab(info, "tokens", (v) => grab(v, "cache", (cache) => grab(cache, "read"))),
tokens_cache_write: grab(info, "tokens", (v) => grab(v, "cache", (cache) => grab(cache, "write"))),
revert: grab(info, "revert"),
permission: grab(info, "permission"),
time_created: grab(info, "time", (v) => grab(v, "created")),
@@ -112,12 +142,28 @@ export default [
}),
SyncEvent.project(MessageV2.Event.Removed, (db, data) => {
for (const row of db
.select()
.from(PartTable)
.where(and(eq(PartTable.message_id, data.messageID), eq(PartTable.session_id, data.sessionID)))
.all()) {
const previous = usage(row.data)
if (previous) applyUsage(db, data.sessionID, previous, -1)
}
db.delete(MessageTable)
.where(and(eq(MessageTable.id, data.messageID), eq(MessageTable.session_id, data.sessionID)))
.run()
}),
SyncEvent.project(MessageV2.Event.PartRemoved, (db, data) => {
const row = db
.select()
.from(PartTable)
.where(and(eq(PartTable.id, data.partID), eq(PartTable.session_id, data.sessionID)))
.get()
const previous = row && usage(row.data)
if (previous) applyUsage(db, data.sessionID, previous, -1)
db.delete(PartTable)
.where(and(eq(PartTable.id, data.partID), eq(PartTable.session_id, data.sessionID)))
.run()
@@ -125,6 +171,7 @@ export default [
SyncEvent.project(MessageV2.Event.PartUpdated, (db, data) => {
const { id, messageID, sessionID, ...rest } = data.part
const row = db.select().from(PartTable).where(eq(PartTable.id, id)).get()
try {
db.insert(PartTable)
@@ -137,6 +184,10 @@ export default [
})
.onConflictDoUpdate({ target: PartTable.id, set: { data: rest } })
.run()
const previous = row && usage(row.data)
const next = usage(data.part)
if (previous) applyUsage(db, row.session_id, previous, -1)
if (next) applyUsage(db, sessionID, next)
} catch (err) {
if (!foreign(err)) throw err
log.warn("ignored late part update", { partID: id, messageID, sessionID })
+8 -2
View File
@@ -1,4 +1,4 @@
import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core"
import { sqliteTable, text, integer, index, primaryKey, real } from "drizzle-orm/sqlite-core"
import { ProjectTable } from "../project/project.sql"
import type { MessageV2 } from "./message-v2"
import type { SessionMessage } from "../v2/session-message"
@@ -10,7 +10,7 @@ import type { WorkspaceID } from "../control-plane/schema"
import { Timestamps } from "../storage/schema.sql"
type PartData = Omit<MessageV2.Part, "id" | "sessionID" | "messageID">
type InfoData = Omit<MessageV2.Info, "id" | "sessionID">
type InfoData<T extends MessageV2.Info = MessageV2.Info> = T extends unknown ? Omit<T, "id" | "sessionID"> : never
type SessionMessageData = Omit<(typeof SessionMessage.Message)["Encoded"], "type" | "id">
export const SessionTable = sqliteTable(
@@ -33,6 +33,12 @@ export const SessionTable = sqliteTable(
summary_deletions: integer(),
summary_files: integer(),
summary_diffs: text({ mode: "json" }).$type<Snapshot.FileDiff[]>(),
cost: real().notNull().default(0),
tokens_input: integer().notNull().default(0),
tokens_output: integer().notNull().default(0),
tokens_reasoning: integer().notNull().default(0),
tokens_cache_read: integer().notNull().default(0),
tokens_cache_write: integer().notNull().default(0),
revert: text({ mode: "json" }).$type<{ messageID: MessageID; partID?: PartID; snapshot?: string; diff?: string }>(),
permission: text({ mode: "json" }).$type<Permission.Ruleset>(),
agent: text(),
+34
View File
@@ -88,6 +88,16 @@ export function fromRow(row: SessionRow): Info {
: undefined,
version: row.version,
summary,
cost: row.cost,
tokens: {
input: row.tokens_input,
output: row.tokens_output,
reasoning: row.tokens_reasoning,
cache: {
read: row.tokens_cache_read,
write: row.tokens_cache_write,
},
},
share,
revert,
permission: row.permission ?? undefined,
@@ -118,6 +128,12 @@ export function toRow(info: Info) {
summary_deletions: info.summary?.deletions,
summary_files: info.summary?.files,
summary_diffs: info.summary?.diffs,
cost: info.cost ?? 0,
tokens_input: (info.tokens ?? EmptyTokens).input,
tokens_output: (info.tokens ?? EmptyTokens).output,
tokens_reasoning: (info.tokens ?? EmptyTokens).reasoning,
tokens_cache_read: (info.tokens ?? EmptyTokens).cache.read,
tokens_cache_write: (info.tokens ?? EmptyTokens).cache.write,
revert: info.revert ?? null,
permission: info.permission,
time_created: info.time.created,
@@ -148,6 +164,18 @@ const Summary = Schema.Struct({
diffs: optionalOmitUndefined(Schema.Array(Snapshot.FileDiff)),
})
const Tokens = Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
})
const EmptyTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
const Share = Schema.Struct({
url: Schema.String,
})
@@ -185,6 +213,8 @@ export const Info = Schema.Struct({
path: optionalOmitUndefined(Schema.String),
parentID: optionalOmitUndefined(SessionID),
summary: optionalOmitUndefined(Summary),
cost: optionalOmitUndefined(Schema.Finite),
tokens: optionalOmitUndefined(Tokens),
share: optionalOmitUndefined(Share),
title: Schema.String,
agent: optionalOmitUndefined(Schema.String),
@@ -290,6 +320,8 @@ const UpdatedInfo = Schema.Struct({
path: Schema.optional(Schema.NullOr(Schema.String)),
parentID: Schema.optional(Schema.NullOr(SessionID)),
summary: Schema.optional(Schema.NullOr(Summary)),
cost: Schema.optional(Schema.Finite),
tokens: Schema.optional(Tokens),
share: Schema.optional(UpdatedShare),
title: Schema.optional(Schema.NullOr(Schema.String)),
agent: Schema.optional(Schema.NullOr(Schema.String)),
@@ -512,6 +544,8 @@ export const layer: Layer.Layer<Service, never, Bus.Service | Storage.Service |
agent: input.agent,
model: input.model,
permission: input.permission,
cost: 0,
tokens: EmptyTokens,
time: {
created: Date.now(),
updated: Date.now(),
@@ -216,6 +216,12 @@ export async function run(db: SQLiteBunDatabase<any, any> | NodeSQLiteDatabase<a
summary_deletions: data.summary?.deletions ?? null,
summary_files: data.summary?.files ?? null,
summary_diffs: data.summary?.diffs ?? null,
cost: 0,
tokens_input: 0,
tokens_output: 0,
tokens_reasoning: 0,
tokens_cache_read: 0,
tokens_cache_write: 0,
revert: data.revert ?? null,
permission: data.permission ?? null,
time_created: data.time?.created ?? now,
+20
View File
@@ -29,6 +29,16 @@ export class Info extends Schema.Class<Info>("Session.Info")({
path: optionalOmitUndefined(Schema.String),
agent: optionalOmitUndefined(Schema.String),
model: Modelv2.Ref.pipe(optionalOmitUndefined),
cost: Schema.Finite,
tokens: Schema.Struct({
input: Schema.Finite,
output: Schema.Finite,
reasoning: Schema.Finite,
cache: Schema.Struct({
read: Schema.Finite,
write: Schema.Finite,
}),
}),
time: Schema.Struct({
created: V2Schema.DateTimeUtcFromMillis,
updated: V2Schema.DateTimeUtcFromMillis,
@@ -136,6 +146,16 @@ export const layer = Layer.effect(
variant: Modelv2.VariantID.make(row.model.variant ?? "default"),
}
: undefined,
cost: row.cost,
tokens: {
input: row.tokens_input,
output: row.tokens_output,
reasoning: row.tokens_reasoning,
cache: {
read: row.tokens_cache_read,
write: row.tokens_cache_write,
},
},
time: {
created: DateTime.makeUnsafe(row.time_created),
updated: DateTime.makeUnsafe(row.time_updated),
+10 -3
View File
@@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { Deferred, Effect, Exit, Fiber, Ref, Scope } from "effect"
import { Deferred, Effect, Exit, Fiber, Latch, Ref, Scope } from "effect"
import { Runner } from "@/effect/runner"
import { it } from "../lib/effect"
@@ -352,11 +352,18 @@ describe("Runner", () => {
Effect.gen(function* () {
const s = yield* Scope.Scope
const runner = Runner.make<string>(s, { onInterrupt: Effect.succeed("interrupted") })
const ready = yield* Latch.make()
const sh = yield* runner
.startShell(Effect.never.pipe(Effect.ensuring(Effect.die("boom")), Effect.as("ignored")))
.startShell(
Effect.gen(function* () {
yield* ready.open
return yield* Effect.never.pipe(Effect.as("ignored"))
}).pipe(Effect.ensuring(Effect.die("boom"))),
ready,
)
.pipe(Effect.forkChild)
yield* Effect.sleep("10 millis")
yield* ready.await.pipe(Effect.timeout("250 millis"))
yield* runner.cancel
expect(Exit.isFailure(yield* Fiber.await(sh))).toBe(true)
@@ -292,6 +292,7 @@ export function createTuiPluginApi(opts: Opts = {}): HostPluginApi {
},
session: {
count: opts.state?.session?.count ?? (() => 0),
get: opts.state?.session?.get ?? (() => undefined),
diff: opts.state?.session?.diff ?? (() => []),
todo: opts.state?.session?.todo ?? (() => []),
messages: opts.state?.session?.messages ?? (() => []),
@@ -12,6 +12,8 @@ const info = {
directory: "/tmp/opencode",
parentID: undefined,
summary: undefined,
cost: 0,
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
share: undefined,
title: "Test session",
version: "1.0.0",
+2
View File
@@ -11,6 +11,7 @@ import type {
Provider,
PermissionRequest,
QuestionRequest,
Session,
SessionStatus,
TextPart,
Config as SdkConfig,
@@ -310,6 +311,7 @@ export type TuiState = {
readonly vcs: { branch?: string } | undefined
session: {
count: () => number
get: (sessionID: string) => Session | undefined
diff: (sessionID: string) => ReadonlyArray<TuiSidebarFileItem>
todo: (sessionID: string) => ReadonlyArray<TuiSidebarTodoItem>
messages: (sessionID: string) => ReadonlyArray<Message>
+40
View File
@@ -741,6 +741,16 @@ export type Session = {
files: number
diffs?: Array<SnapshotFileDiff>
}
cost?: number
tokens?: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
share?: {
url: string
}
@@ -1429,6 +1439,16 @@ export type GlobalSession = {
files: number
diffs?: Array<SnapshotFileDiff>
}
cost?: number
tokens?: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
share?: {
url: string
}
@@ -1892,6 +1912,16 @@ export type SyncEventSessionUpdated = {
files: number
diffs?: Array<SnapshotFileDiff>
} | null
cost?: number | null
tokens?: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
} | null
share?: {
url?: string | null
}
@@ -3072,6 +3102,16 @@ export type SessionInfo = {
providerID: string
variant: string
}
cost: number
tokens: {
input: number
output: number
reasoning: number
cache: {
read: number
write: number
}
}
time: {
created: number
updated: number