summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-05-13 13:08:43 -0500
committeradamdottv <[email protected]>2025-05-13 13:08:43 -0500
commit01b6bf5bb7307246cb2cca7f1cbc8aba693941cc (patch)
treef47f5aa2b323ce8fea55f6999308486ff790d26e
parentd8f3b606258a5655d73acc94d6cb37b421350817 (diff)
downloadopencode-01b6bf5bb7307246cb2cca7f1cbc8aba693941cc.tar.gz
opencode-01b6bf5bb7307246cb2cca7f1cbc8aba693941cc.zip
chore: refactor db
-rw-r--r--internal/db/files.sql.go32
-rw-r--r--internal/db/logs.sql.go17
-rw-r--r--internal/db/messages.sql.go16
-rw-r--r--internal/db/migrations/20250502063010_add_summary_to_sessions.sql11
-rw-r--r--internal/db/migrations/20250508122310_create_logs_table.sql16
-rw-r--r--internal/db/migrations/20250513000000_initial.sql (renamed from internal/db/migrations/20250424200609_initial.sql)53
-rw-r--r--internal/db/models.go32
-rw-r--r--internal/db/sessions.sql.go36
-rw-r--r--internal/db/sql/files.sql8
-rw-r--r--internal/db/sql/logs.sql4
-rw-r--r--internal/db/sql/messages.sql8
-rw-r--r--internal/db/sql/sessions.sql8
-rw-r--r--internal/history/history.go25
-rw-r--r--internal/llm/agent/agent.go2
-rw-r--r--internal/logging/logging.go23
-rw-r--r--internal/message/message.go35
-rw-r--r--internal/session/session.go18
-rw-r--r--internal/tui/components/chat/message.go2
-rw-r--r--internal/tui/components/logs/table.go3
19 files changed, 193 insertions, 156 deletions
diff --git a/internal/db/files.sql.go b/internal/db/files.sql.go
index 28abaa55d..39426a73b 100644
--- a/internal/db/files.sql.go
+++ b/internal/db/files.sql.go
@@ -15,13 +15,11 @@ INSERT INTO files (
session_id,
path,
content,
- version,
- created_at,
- updated_at
+ version
) VALUES (
- ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?
)
-RETURNING id, session_id, path, content, version, created_at, updated_at
+RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
`
type CreateFileParams struct {
@@ -47,6 +45,7 @@ func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (File, e
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
)
@@ -74,7 +73,7 @@ func (q *Queries) DeleteSessionFiles(ctx context.Context, sessionID string) erro
}
const getFile = `-- name: GetFile :one
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files
WHERE id = ? LIMIT 1
`
@@ -88,6 +87,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
)
@@ -95,7 +95,7 @@ func (q *Queries) GetFile(ctx context.Context, id string) (File, error) {
}
const getFileByPathAndSession = `-- name: GetFileByPathAndSession :one
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files
WHERE path = ? AND session_id = ?
ORDER BY created_at DESC
@@ -116,6 +116,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
)
@@ -123,7 +124,7 @@ func (q *Queries) GetFileByPathAndSession(ctx context.Context, arg GetFileByPath
}
const listFilesByPath = `-- name: ListFilesByPath :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files
WHERE path = ?
ORDER BY created_at DESC
@@ -144,6 +145,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -161,7 +163,7 @@ func (q *Queries) ListFilesByPath(ctx context.Context, path string) ([]File, err
}
const listFilesBySession = `-- name: ListFilesBySession :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files
WHERE session_id = ?
ORDER BY created_at ASC
@@ -182,6 +184,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -199,7 +202,7 @@ func (q *Queries) ListFilesBySession(ctx context.Context, sessionID string) ([]F
}
const listLatestSessionFiles = `-- name: ListLatestSessionFiles :many
-SELECT f.id, f.session_id, f.path, f.content, f.version, f.created_at, f.updated_at
+SELECT f.id, f.session_id, f.path, f.content, f.version, f.is_new, f.created_at, f.updated_at
FROM files f
INNER JOIN (
SELECT path, MAX(created_at) as max_created_at
@@ -225,6 +228,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -242,7 +246,7 @@ func (q *Queries) ListLatestSessionFiles(ctx context.Context, sessionID string)
}
const listNewFiles = `-- name: ListNewFiles :many
-SELECT id, session_id, path, content, version, created_at, updated_at
+SELECT id, session_id, path, content, version, is_new, created_at, updated_at
FROM files
WHERE is_new = 1
ORDER BY created_at DESC
@@ -263,6 +267,7 @@ func (q *Queries) ListNewFiles(ctx context.Context) ([]File, error) {
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
@@ -284,9 +289,9 @@ UPDATE files
SET
content = ?,
version = ?,
- updated_at = strftime('%s', 'now')
+ updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ?
-RETURNING id, session_id, path, content, version, created_at, updated_at
+RETURNING id, session_id, path, content, version, is_new, created_at, updated_at
`
type UpdateFileParams struct {
@@ -304,6 +309,7 @@ func (q *Queries) UpdateFile(ctx context.Context, arg UpdateFileParams) (File, e
&i.Path,
&i.Content,
&i.Version,
+ &i.IsNew,
&i.CreatedAt,
&i.UpdatedAt,
)
diff --git a/internal/db/logs.sql.go b/internal/db/logs.sql.go
index aa89d7d22..5c6ea6722 100644
--- a/internal/db/logs.sql.go
+++ b/internal/db/logs.sql.go
@@ -17,27 +17,24 @@ INSERT INTO logs (
timestamp,
level,
message,
- attributes,
- created_at
+ attributes
) VALUES (
?,
?,
?,
?,
?,
- ?,
?
-) RETURNING id, session_id, timestamp, level, message, attributes, created_at
+) RETURNING id, session_id, timestamp, level, message, attributes, created_at, updated_at
`
type CreateLogParams struct {
ID string `json:"id"`
SessionID sql.NullString `json:"session_id"`
- Timestamp int64 `json:"timestamp"`
+ Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Attributes sql.NullString `json:"attributes"`
- CreatedAt int64 `json:"created_at"`
}
func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, error) {
@@ -48,7 +45,6 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
arg.Level,
arg.Message,
arg.Attributes,
- arg.CreatedAt,
)
var i Log
err := row.Scan(
@@ -59,12 +55,13 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro
&i.Message,
&i.Attributes,
&i.CreatedAt,
+ &i.UpdatedAt,
)
return i, err
}
const listAllLogs = `-- name: ListAllLogs :many
-SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs
+SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
ORDER BY timestamp DESC
LIMIT ?
`
@@ -86,6 +83,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
&i.Message,
&i.Attributes,
&i.CreatedAt,
+ &i.UpdatedAt,
); err != nil {
return nil, err
}
@@ -101,7 +99,7 @@ func (q *Queries) ListAllLogs(ctx context.Context, limit int64) ([]Log, error) {
}
const listLogsBySession = `-- name: ListLogsBySession :many
-SELECT id, session_id, timestamp, level, message, attributes, created_at FROM logs
+SELECT id, session_id, timestamp, level, message, attributes, created_at, updated_at FROM logs
WHERE session_id = ?
ORDER BY timestamp ASC
`
@@ -123,6 +121,7 @@ func (q *Queries) ListLogsBySession(ctx context.Context, sessionID sql.NullStrin
&i.Message,
&i.Attributes,
&i.CreatedAt,
+ &i.UpdatedAt,
); err != nil {
return nil, err
}
diff --git a/internal/db/messages.sql.go b/internal/db/messages.sql.go
index 15ef7695b..57d940872 100644
--- a/internal/db/messages.sql.go
+++ b/internal/db/messages.sql.go
@@ -16,11 +16,9 @@ INSERT INTO messages (
session_id,
role,
parts,
- model,
- created_at,
- updated_at
+ model
) VALUES (
- ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?
)
RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at
`
@@ -145,7 +143,7 @@ ORDER BY created_at ASC
type ListMessagesBySessionAfterParams struct {
SessionID string `json:"session_id"`
- CreatedAt int64 `json:"created_at"`
+ CreatedAt string `json:"created_at"`
}
func (q *Queries) ListMessagesBySessionAfter(ctx context.Context, arg ListMessagesBySessionAfterParams) ([]Message, error) {
@@ -185,14 +183,14 @@ UPDATE messages
SET
parts = ?,
finished_at = ?,
- updated_at = strftime('%s', 'now')
+ updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ?
`
type UpdateMessageParams struct {
- Parts string `json:"parts"`
- FinishedAt sql.NullInt64 `json:"finished_at"`
- ID string `json:"id"`
+ Parts string `json:"parts"`
+ FinishedAt sql.NullString `json:"finished_at"`
+ ID string `json:"id"`
}
func (q *Queries) UpdateMessage(ctx context.Context, arg UpdateMessageParams) error {
diff --git a/internal/db/migrations/20250502063010_add_summary_to_sessions.sql b/internal/db/migrations/20250502063010_add_summary_to_sessions.sql
deleted file mode 100644
index 9cecfcedb..000000000
--- a/internal/db/migrations/20250502063010_add_summary_to_sessions.sql
+++ /dev/null
@@ -1,11 +0,0 @@
--- +goose Up
--- +goose StatementBegin
-ALTER TABLE sessions ADD COLUMN summary TEXT;
-ALTER TABLE sessions ADD COLUMN summarized_at INTEGER;
--- +goose StatementEnd
-
--- +goose Down
--- +goose StatementBegin
-ALTER TABLE sessions DROP COLUMN summarized_at;
-ALTER TABLE sessions DROP COLUMN summary;
--- +goose StatementEnd \ No newline at end of file
diff --git a/internal/db/migrations/20250508122310_create_logs_table.sql b/internal/db/migrations/20250508122310_create_logs_table.sql
deleted file mode 100644
index f4876fbae..000000000
--- a/internal/db/migrations/20250508122310_create_logs_table.sql
+++ /dev/null
@@ -1,16 +0,0 @@
--- +goose Up
-CREATE TABLE logs (
- id TEXT PRIMARY KEY,
- session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
- timestamp INTEGER NOT NULL,
- level TEXT NOT NULL,
- message TEXT NOT NULL,
- attributes TEXT,
- created_at INTEGER NOT NULL
-);
-
-CREATE INDEX logs_session_id_idx ON logs(session_id);
-CREATE INDEX logs_timestamp_idx ON logs(timestamp);
-
--- +goose Down
-DROP TABLE logs; \ No newline at end of file
diff --git a/internal/db/migrations/20250424200609_initial.sql b/internal/db/migrations/20250513000000_initial.sql
index 02caecf0c..ad97a4ad3 100644
--- a/internal/db/migrations/20250424200609_initial.sql
+++ b/internal/db/migrations/20250513000000_initial.sql
@@ -6,17 +6,19 @@ CREATE TABLE IF NOT EXISTS sessions (
parent_session_id TEXT,
title TEXT NOT NULL,
message_count INTEGER NOT NULL DEFAULT 0 CHECK (message_count >= 0),
- prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0),
- completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens>= 0),
+ prompt_tokens INTEGER NOT NULL DEFAULT 0 CHECK (prompt_tokens >= 0),
+ completion_tokens INTEGER NOT NULL DEFAULT 0 CHECK (completion_tokens >= 0),
cost REAL NOT NULL DEFAULT 0.0 CHECK (cost >= 0.0),
- updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
- created_at INTEGER NOT NULL -- Unix timestamp in milliseconds
+ summary TEXT,
+ summarized_at TEXT,
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
);
CREATE TRIGGER IF NOT EXISTS update_sessions_updated_at
AFTER UPDATE ON sessions
BEGIN
-UPDATE sessions SET updated_at = strftime('%s', 'now')
+UPDATE sessions SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id;
END;
@@ -27,8 +29,9 @@ CREATE TABLE IF NOT EXISTS files (
path TEXT NOT NULL,
content TEXT NOT NULL,
version TEXT NOT NULL,
- created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
- updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
+ is_new INTEGER DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE,
UNIQUE(path, session_id, version)
);
@@ -39,7 +42,7 @@ CREATE INDEX IF NOT EXISTS idx_files_path ON files (path);
CREATE TRIGGER IF NOT EXISTS update_files_updated_at
AFTER UPDATE ON files
BEGIN
-UPDATE files SET updated_at = strftime('%s', 'now')
+UPDATE files SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id;
END;
@@ -50,9 +53,9 @@ CREATE TABLE IF NOT EXISTS messages (
role TEXT NOT NULL,
parts TEXT NOT NULL default '[]',
model TEXT,
- created_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
- updated_at INTEGER NOT NULL, -- Unix timestamp in milliseconds
- finished_at INTEGER, -- Unix timestamp in milliseconds
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+ finished_at TEXT,
FOREIGN KEY (session_id) REFERENCES sessions (id) ON DELETE CASCADE
);
@@ -61,7 +64,7 @@ CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages (session_id);
CREATE TRIGGER IF NOT EXISTS update_messages_updated_at
AFTER UPDATE ON messages
BEGIN
-UPDATE messages SET updated_at = strftime('%s', 'now')
+UPDATE messages SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = new.id;
END;
@@ -81,6 +84,28 @@ UPDATE sessions SET
WHERE id = old.session_id;
END;
+-- Logs
+CREATE TABLE IF NOT EXISTS logs (
+ id TEXT PRIMARY KEY,
+ session_id TEXT REFERENCES sessions(id) ON DELETE CASCADE,
+ timestamp TEXT NOT NULL,
+ level TEXT NOT NULL,
+ message TEXT NOT NULL,
+ attributes TEXT,
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')),
+ updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%f000Z', 'now'))
+);
+
+CREATE INDEX logs_session_id_idx ON logs(session_id);
+CREATE INDEX logs_timestamp_idx ON logs(timestamp);
+
+CREATE TRIGGER IF NOT EXISTS update_logs_updated_at
+AFTER UPDATE ON logs
+BEGIN
+UPDATE logs SET updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
+WHERE id = new.id;
+END;
+
-- +goose StatementEnd
-- +goose Down
@@ -88,11 +113,13 @@ END;
DROP TRIGGER IF EXISTS update_sessions_updated_at;
DROP TRIGGER IF EXISTS update_messages_updated_at;
DROP TRIGGER IF EXISTS update_files_updated_at;
+DROP TRIGGER IF EXISTS update_logs_updated_at;
DROP TRIGGER IF EXISTS update_session_message_count_on_delete;
DROP TRIGGER IF EXISTS update_session_message_count_on_insert;
-DROP TABLE IF EXISTS sessions;
+DROP TABLE IF EXISTS logs;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS files;
+DROP TABLE IF EXISTS sessions;
-- +goose StatementEnd
diff --git a/internal/db/models.go b/internal/db/models.go
index 473e18db6..e016ef79c 100644
--- a/internal/db/models.go
+++ b/internal/db/models.go
@@ -9,23 +9,25 @@ import (
)
type File struct {
- ID string `json:"id"`
- SessionID string `json:"session_id"`
- Path string `json:"path"`
- Content string `json:"content"`
- Version string `json:"version"`
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
+ ID string `json:"id"`
+ SessionID string `json:"session_id"`
+ Path string `json:"path"`
+ Content string `json:"content"`
+ Version string `json:"version"`
+ IsNew sql.NullInt64 `json:"is_new"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
}
type Log struct {
ID string `json:"id"`
SessionID sql.NullString `json:"session_id"`
- Timestamp int64 `json:"timestamp"`
+ Timestamp string `json:"timestamp"`
Level string `json:"level"`
Message string `json:"message"`
Attributes sql.NullString `json:"attributes"`
- CreatedAt int64 `json:"created_at"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
}
type Message struct {
@@ -34,9 +36,9 @@ type Message struct {
Role string `json:"role"`
Parts string `json:"parts"`
Model sql.NullString `json:"model"`
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
- FinishedAt sql.NullInt64 `json:"finished_at"`
+ CreatedAt string `json:"created_at"`
+ UpdatedAt string `json:"updated_at"`
+ FinishedAt sql.NullString `json:"finished_at"`
}
type Session struct {
@@ -47,8 +49,8 @@ type Session struct {
PromptTokens int64 `json:"prompt_tokens"`
CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"`
- UpdatedAt int64 `json:"updated_at"`
- CreatedAt int64 `json:"created_at"`
Summary sql.NullString `json:"summary"`
- SummarizedAt sql.NullInt64 `json:"summarized_at"`
+ SummarizedAt sql.NullString `json:"summarized_at"`
+ UpdatedAt string `json:"updated_at"`
+ CreatedAt string `json:"created_at"`
}
diff --git a/internal/db/sessions.sql.go b/internal/db/sessions.sql.go
index f6150b40f..8be220cc7 100644
--- a/internal/db/sessions.sql.go
+++ b/internal/db/sessions.sql.go
@@ -20,9 +20,7 @@ INSERT INTO sessions (
completion_tokens,
cost,
summary,
- summarized_at,
- updated_at,
- created_at
+ summarized_at
) VALUES (
?,
?,
@@ -32,10 +30,8 @@ INSERT INTO sessions (
?,
?,
?,
- ?,
- strftime('%s', 'now'),
- strftime('%s', 'now')
-) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+ ?
+) RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
`
type CreateSessionParams struct {
@@ -47,7 +43,7 @@ type CreateSessionParams struct {
CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"`
Summary sql.NullString `json:"summary"`
- SummarizedAt sql.NullInt64 `json:"summarized_at"`
+ SummarizedAt sql.NullString `json:"summarized_at"`
}
func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error) {
@@ -71,10 +67,10 @@ func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (S
&i.PromptTokens,
&i.CompletionTokens,
&i.Cost,
- &i.UpdatedAt,
- &i.CreatedAt,
&i.Summary,
&i.SummarizedAt,
+ &i.UpdatedAt,
+ &i.CreatedAt,
)
return i, err
}
@@ -90,7 +86,7 @@ func (q *Queries) DeleteSession(ctx context.Context, id string) error {
}
const getSessionByID = `-- name: GetSessionByID :one
-SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
FROM sessions
WHERE id = ? LIMIT 1
`
@@ -106,16 +102,16 @@ func (q *Queries) GetSessionByID(ctx context.Context, id string) (Session, error
&i.PromptTokens,
&i.CompletionTokens,
&i.Cost,
- &i.UpdatedAt,
- &i.CreatedAt,
&i.Summary,
&i.SummarizedAt,
+ &i.UpdatedAt,
+ &i.CreatedAt,
)
return i, err
}
const listSessions = `-- name: ListSessions :many
-SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+SELECT id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
FROM sessions
WHERE parent_session_id is NULL
ORDER BY created_at DESC
@@ -138,10 +134,10 @@ func (q *Queries) ListSessions(ctx context.Context) ([]Session, error) {
&i.PromptTokens,
&i.CompletionTokens,
&i.Cost,
- &i.UpdatedAt,
- &i.CreatedAt,
&i.Summary,
&i.SummarizedAt,
+ &i.UpdatedAt,
+ &i.CreatedAt,
); err != nil {
return nil, err
}
@@ -166,7 +162,7 @@ SET
summary = ?,
summarized_at = ?
WHERE id = ?
-RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, updated_at, created_at, summary, summarized_at
+RETURNING id, parent_session_id, title, message_count, prompt_tokens, completion_tokens, cost, summary, summarized_at, updated_at, created_at
`
type UpdateSessionParams struct {
@@ -175,7 +171,7 @@ type UpdateSessionParams struct {
CompletionTokens int64 `json:"completion_tokens"`
Cost float64 `json:"cost"`
Summary sql.NullString `json:"summary"`
- SummarizedAt sql.NullInt64 `json:"summarized_at"`
+ SummarizedAt sql.NullString `json:"summarized_at"`
ID string `json:"id"`
}
@@ -198,10 +194,10 @@ func (q *Queries) UpdateSession(ctx context.Context, arg UpdateSessionParams) (S
&i.PromptTokens,
&i.CompletionTokens,
&i.Cost,
- &i.UpdatedAt,
- &i.CreatedAt,
&i.Summary,
&i.SummarizedAt,
+ &i.UpdatedAt,
+ &i.CreatedAt,
)
return i, err
}
diff --git a/internal/db/sql/files.sql b/internal/db/sql/files.sql
index aba2a6111..560a6984f 100644
--- a/internal/db/sql/files.sql
+++ b/internal/db/sql/files.sql
@@ -28,11 +28,9 @@ INSERT INTO files (
session_id,
path,
content,
- version,
- created_at,
- updated_at
+ version
) VALUES (
- ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?
)
RETURNING *;
@@ -41,7 +39,7 @@ UPDATE files
SET
content = ?,
version = ?,
- updated_at = strftime('%s', 'now')
+ updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ?
RETURNING *;
diff --git a/internal/db/sql/logs.sql b/internal/db/sql/logs.sql
index 58414f88a..4fc640830 100644
--- a/internal/db/sql/logs.sql
+++ b/internal/db/sql/logs.sql
@@ -5,15 +5,13 @@ INSERT INTO logs (
timestamp,
level,
message,
- attributes,
- created_at
+ attributes
) VALUES (
?,
?,
?,
?,
?,
- ?,
?
) RETURNING *;
diff --git a/internal/db/sql/messages.sql b/internal/db/sql/messages.sql
index 475b23a86..6a3b69f6f 100644
--- a/internal/db/sql/messages.sql
+++ b/internal/db/sql/messages.sql
@@ -21,11 +21,9 @@ INSERT INTO messages (
session_id,
role,
parts,
- model,
- created_at,
- updated_at
+ model
) VALUES (
- ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?
)
RETURNING *;
@@ -34,7 +32,7 @@ UPDATE messages
SET
parts = ?,
finished_at = ?,
- updated_at = strftime('%s', 'now')
+ updated_at = strftime('%Y-%m-%dT%H:%M:%f000Z', 'now')
WHERE id = ?;
diff --git a/internal/db/sql/sessions.sql b/internal/db/sql/sessions.sql
index 81abebd3c..fd7ffe56c 100644
--- a/internal/db/sql/sessions.sql
+++ b/internal/db/sql/sessions.sql
@@ -8,9 +8,7 @@ INSERT INTO sessions (
completion_tokens,
cost,
summary,
- summarized_at,
- updated_at,
- created_at
+ summarized_at
) VALUES (
?,
?,
@@ -20,9 +18,7 @@ INSERT INTO sessions (
?,
?,
?,
- ?,
- strftime('%s', 'now'),
- strftime('%s', 'now')
+ ?
) RETURNING *;
-- name: GetSessionByID :one
diff --git a/internal/history/history.go b/internal/history/history.go
index 80fa09d9d..12c94a391 100644
--- a/internal/history/history.go
+++ b/internal/history/history.go
@@ -113,7 +113,13 @@ func (s *service) CreateVersion(ctx context.Context, sessionID, path, content st
if b.Version == InitialVersion && a.Version != InitialVersion {
return -1
}
- return int(b.CreatedAt - a.CreatedAt) // Fallback to timestamp
+ // Compare timestamps as strings (ISO format sorts correctly)
+ if b.CreatedAt > a.CreatedAt {
+ return 1
+ } else if a.CreatedAt > b.CreatedAt {
+ return -1
+ }
+ return 0 // Equal timestamps
})
latestFile := files[0]
@@ -362,14 +368,27 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[File] {
}
func (s *service) fromDBItem(item db.File) File {
+ // Parse timestamps from ISO strings
+ createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+ if err != nil {
+ slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+ createdAt = time.Now() // Fallback
+ }
+
+ updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+ if err != nil {
+ slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+ updatedAt = time.Now() // Fallback
+ }
+
return File{
ID: item.ID,
SessionID: item.SessionID,
Path: item.Path,
Content: item.Content,
Version: item.Version,
- CreatedAt: time.UnixMilli(item.CreatedAt * 1000),
- UpdatedAt: time.UnixMilli(item.UpdatedAt * 1000),
+ CreatedAt: createdAt,
+ UpdatedAt: updatedAt,
}
}
diff --git a/internal/llm/agent/agent.go b/internal/llm/agent/agent.go
index 4c838b731..8be04072c 100644
--- a/internal/llm/agent/agent.go
+++ b/internal/llm/agent/agent.go
@@ -209,7 +209,7 @@ func (a *agent) prepareMessageHistory(ctx context.Context, sessionID string) (se
var sessionMessages []message.Message
if currentSession.Summary != "" && !currentSession.SummarizedAt.IsZero() {
// If summary exists, only fetch messages after the summarization timestamp
- sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt.UnixMilli())
+ sessionMessages, err = a.messages.ListAfter(ctx, sessionID, currentSession.SummarizedAt)
if err != nil {
return currentSession, nil, fmt.Errorf("failed to list messages after summary: %w", err)
}
diff --git a/internal/logging/logging.go b/internal/logging/logging.go
index b42f7caf7..2ba426756 100644
--- a/internal/logging/logging.go
+++ b/internal/logging/logging.go
@@ -86,11 +86,10 @@ func (s *service) Create(ctx context.Context, timestamp time.Time, level, messag
dbLog, err := s.db.CreateLog(ctx, db.CreateLogParams{
ID: uuid.New().String(),
SessionID: sql.NullString{String: sessionID, Valid: sessionID != ""},
- Timestamp: timestamp.UnixMilli(),
+ Timestamp: timestamp.UTC().Format(time.RFC3339Nano),
Level: level,
Message: message,
Attributes: attributesJSON,
- CreatedAt: time.Now().UnixMilli(),
})
if err != nil {
@@ -135,10 +134,24 @@ func (s *service) fromDBItem(item db.Log) Log {
log := Log{
ID: item.ID,
SessionID: item.SessionID.String,
- Timestamp: time.UnixMilli(item.Timestamp),
Level: item.Level,
Message: item.Message,
- CreatedAt: time.UnixMilli(item.CreatedAt),
+ }
+
+ // Parse timestamp from ISO string
+ timestamp, err := time.Parse(time.RFC3339Nano, item.Timestamp)
+ if err == nil {
+ log.Timestamp = timestamp
+ } else {
+ log.Timestamp = time.Now() // Fallback
+ }
+
+ // Parse created_at from ISO string
+ createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+ if err == nil {
+ log.CreatedAt = createdAt
+ } else {
+ log.CreatedAt = time.Now() // Fallback
}
if item.Attributes.Valid && item.Attributes.String != "" {
@@ -195,7 +208,7 @@ func (sw *slogWriter) Write(p []byte) (n int, err error) {
parsedTime, timeErr = time.Parse(time.RFC3339, value)
if timeErr != nil {
slog.Error("Failed to parse time in slog writer", "value", value, "error", timeErr)
- timestamp = time.Now()
+ timestamp = time.Now().UTC()
hasTimestamp = true
continue
}
diff --git a/internal/message/message.go b/internal/message/message.go
index 35a414533..e20fba745 100644
--- a/internal/message/message.go
+++ b/internal/message/message.go
@@ -45,7 +45,7 @@ type Service interface {
Update(ctx context.Context, message Message) (Message, error)
Get(ctx context.Context, id string) (Message, error)
List(ctx context.Context, sessionID string) ([]Message, error)
- ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error)
+ ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error)
Delete(ctx context.Context, id string) error
DeleteSessionMessages(ctx context.Context, sessionID string) error
}
@@ -134,12 +134,12 @@ func (s *service) Update(ctx context.Context, message Message) (Message, error)
return Message{}, fmt.Errorf("failed to marshal message parts for update: %w", err)
}
- var dbFinishedAt sql.NullInt64
+ var dbFinishedAt sql.NullString
finishPart := message.FinishPart()
if finishPart != nil && !finishPart.Time.IsZero() {
- dbFinishedAt = sql.NullInt64{
- Int64: finishPart.Time.UnixMilli(),
- Valid: true,
+ dbFinishedAt = sql.NullString{
+ String: finishPart.Time.UTC().Format(time.RFC3339Nano),
+ Valid: true,
}
}
@@ -199,13 +199,13 @@ func (s *service) List(ctx context.Context, sessionID string) ([]Message, error)
return messages, nil
}
-func (s *service) ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) {
+func (s *service) ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
s.mu.RLock()
defer s.mu.RUnlock()
dbMessages, err := s.db.ListMessagesBySessionAfter(ctx, db.ListMessagesBySessionAfterParams{
SessionID: sessionID,
- CreatedAt: timestampMillis,
+ CreatedAt: timestamp.Format(time.RFC3339Nano),
})
if err != nil {
return nil, fmt.Errorf("db.ListMessagesBySessionAfter: %w", err)
@@ -294,14 +294,27 @@ func (s *service) fromDBItem(item db.Message) (Message, error) {
return Message{}, fmt.Errorf("unmarshallParts for message ID %s: %w. Raw parts: %s", item.ID, err, item.Parts)
}
+ // Parse timestamps from ISO strings
+ createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt)
+ if err != nil {
+ slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+ createdAt = time.Now() // Fallback
+ }
+
+ updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+ if err != nil {
+ slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err)
+ updatedAt = time.Now() // Fallback
+ }
+
msg := Message{
ID: item.ID,
SessionID: item.SessionID,
Role: MessageRole(item.Role),
Parts: parts,
Model: models.ModelID(item.Model.String),
- CreatedAt: time.UnixMilli(item.CreatedAt),
- UpdatedAt: time.UnixMilli(item.UpdatedAt),
+ CreatedAt: createdAt,
+ UpdatedAt: updatedAt,
}
return msg, nil
@@ -323,8 +336,8 @@ func List(ctx context.Context, sessionID string) ([]Message, error) {
return GetService().List(ctx, sessionID)
}
-func ListAfter(ctx context.Context, sessionID string, timestampMillis int64) ([]Message, error) {
- return GetService().ListAfter(ctx, sessionID, timestampMillis)
+func ListAfter(ctx context.Context, sessionID string, timestamp time.Time) ([]Message, error) {
+ return GetService().ListAfter(ctx, sessionID, timestamp)
}
func Delete(ctx context.Context, id string) error {
diff --git a/internal/session/session.go b/internal/session/session.go
index b961959fe..9df3c2b31 100644
--- a/internal/session/session.go
+++ b/internal/session/session.go
@@ -153,10 +153,6 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
if session.ID == "" {
return Session{}, fmt.Errorf("cannot update session with empty ID")
}
- var summarizedAt sql.NullInt64
- if !session.SummarizedAt.IsZero() {
- summarizedAt = sql.NullInt64{Int64: session.SummarizedAt.UnixMilli(), Valid: true}
- }
params := db.UpdateSessionParams{
ID: session.ID,
@@ -165,7 +161,7 @@ func (s *service) Update(ctx context.Context, session Session) (Session, error)
CompletionTokens: session.CompletionTokens,
Cost: session.Cost,
Summary: sql.NullString{String: session.Summary, Valid: session.Summary != ""},
- SummarizedAt: summarizedAt,
+ SummarizedAt: sql.NullString{String: session.SummarizedAt.UTC().Format(time.RFC3339Nano), Valid: !session.SummarizedAt.IsZero()},
}
dbSession, err := s.db.UpdateSession(ctx, params)
if err != nil {
@@ -206,9 +202,15 @@ func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[Session] {
func (s *service) fromDBItem(item db.Session) Session {
var summarizedAt time.Time
if item.SummarizedAt.Valid {
- summarizedAt = time.UnixMilli(item.SummarizedAt.Int64)
+ parsedTime, err := time.Parse(time.RFC3339Nano, item.SummarizedAt.String)
+ if err == nil {
+ summarizedAt = parsedTime
+ }
}
+ createdAt, _ := time.Parse(time.RFC3339Nano, item.CreatedAt)
+ updatedAt, _ := time.Parse(time.RFC3339Nano, item.UpdatedAt)
+
return Session{
ID: item.ID,
ParentSessionID: item.ParentSessionID.String,
@@ -219,8 +221,8 @@ func (s *service) fromDBItem(item db.Session) Session {
Cost: item.Cost,
Summary: item.Summary.String,
SummarizedAt: summarizedAt,
- CreatedAt: time.UnixMilli(item.CreatedAt),
- UpdatedAt: time.UnixMilli(item.UpdatedAt),
+ CreatedAt: createdAt,
+ UpdatedAt: updatedAt,
}
}
diff --git a/internal/tui/components/chat/message.go b/internal/tui/components/chat/message.go
index e7a6f7237..9c7ac2aa1 100644
--- a/internal/tui/components/chat/message.go
+++ b/internal/tui/components/chat/message.go
@@ -605,7 +605,7 @@ func renderToolMessage(
return toolMsg
}
- params := renderToolParams(width-2-lipgloss.Width(toolNameText), toolCall)
+ params := renderToolParams(width-1-lipgloss.Width(toolNameText), toolCall)
responseContent := ""
if response != nil {
responseContent = renderToolResponse(toolCall, *response, width-2)
diff --git a/internal/tui/components/logs/table.go b/internal/tui/components/logs/table.go
index dbfc9c9f9..f78e4c490 100644
--- a/internal/tui/components/logs/table.go
+++ b/internal/tui/components/logs/table.go
@@ -159,8 +159,7 @@ func (i *tableCmp) updateRows() {
rows := make([]table.Row, 0, len(i.logs))
for _, log := range i.logs {
- // Format timestamp as time
- timeStr := log.Timestamp.Format("15:04:05")
+ timeStr := log.Timestamp.Local().Format("15:04:05")
// Include ID as hidden first column for selection
row := table.Row{