mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd/database): add chat_context_resources table (#26430)
Adds chat_context_resources: a per-chat pinned copy of the agent context resources a chat is hydrated against. The agent-side table (workspace_agent_context_resources) is last-writer-wins with no history, so a chat copies its resources at hydration/refresh to keep a stable view while the agent drifts. Schema foundation only (no queries/dbauthz/prepareGeneration/SDK yet). chat_id FK ON DELETE CASCADE for cleanup parity; no agent FK so the pin survives agent replacement; PK (chat_id, source); reuses the 000522 enum types.
This commit is contained in:
Generated
+38
@@ -1748,6 +1748,38 @@ COMMENT ON COLUMN boundary_usage_stats.window_start IS 'Start of the time window
|
||||
|
||||
COMMENT ON COLUMN boundary_usage_stats.updated_at IS 'Timestamp of the last update to this row.';
|
||||
|
||||
CREATE TABLE chat_context_resources (
|
||||
chat_id uuid NOT NULL,
|
||||
source text NOT NULL,
|
||||
body_kind workspace_agent_context_body_kind NOT NULL,
|
||||
body jsonb NOT NULL,
|
||||
content_hash bytea NOT NULL,
|
||||
size_bytes bigint NOT NULL,
|
||||
status workspace_agent_context_resource_status NOT NULL,
|
||||
error text DEFAULT ''::text NOT NULL,
|
||||
source_path text DEFAULT ''::text NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat_context_resources IS 'Per-chat pinned copy of the agent context resources a chat is hydrated against. Copied from workspace_agent_context_resources at chat hydration and context refresh; survives agent replacement and workspace rebuilds.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.source IS 'Resource locator: canonical file path for file-backed kinds, or the MCP server name for mcp_server resources.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.body_kind IS 'Discriminator for the body JSON shape. Matches the proto oneof variant: instruction_file, skill, mcp_config, mcp_server. PLUGIN/HOOK/SUBAGENT/COMMAND are reserved for the Claude Code plugin RFC.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.body IS 'protojson-encoded variant body matching body_kind. Always populated; non-OK statuses use the variant zero value so the wire kind is still attributable.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.content_hash IS 'sha256 over the resource''s original bytes (or transport-encoded server tool list).';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.size_bytes IS 'Original payload size in bytes; populated regardless of status.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.status IS 'Per-resource status. ok carries a populated body; oversize, unreadable, invalid, and excluded carry an empty body plus an error string.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.error IS 'Per-resource error or warning string. Populated whenever status is non-ok; may also carry a non-fatal warning when status is ok.';
|
||||
|
||||
COMMENT ON COLUMN chat_context_resources.source_path IS 'User-declared scan root that produced this resource. Empty for built-in scan roots.';
|
||||
|
||||
CREATE TABLE chat_debug_runs (
|
||||
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
||||
chat_id uuid NOT NULL,
|
||||
@@ -4096,6 +4128,9 @@ ALTER TABLE ONLY boundary_sessions
|
||||
ALTER TABLE ONLY boundary_usage_stats
|
||||
ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
|
||||
|
||||
ALTER TABLE ONLY chat_context_resources
|
||||
ADD CONSTRAINT chat_context_resources_pkey PRIMARY KEY (chat_id, source);
|
||||
|
||||
ALTER TABLE ONLY chat_debug_runs
|
||||
ADD CONSTRAINT chat_debug_runs_pkey PRIMARY KEY (id);
|
||||
|
||||
@@ -4906,6 +4941,9 @@ ALTER TABLE ONLY boundary_sessions
|
||||
ALTER TABLE ONLY boundary_sessions
|
||||
ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
|
||||
|
||||
ALTER TABLE ONLY chat_context_resources
|
||||
ADD CONSTRAINT chat_context_resources_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY chat_debug_runs
|
||||
ADD CONSTRAINT chat_debug_runs_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
|
||||
+1
@@ -14,6 +14,7 @@ const (
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyBoundarySessionsOwnerID ForeignKeyConstraint = "boundary_sessions_owner_id_fkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_owner_id_fkey FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE SET NULL;
|
||||
ForeignKeyBoundarySessionsWorkspaceAgentID ForeignKeyConstraint = "boundary_sessions_workspace_agent_id_fkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id);
|
||||
ForeignKeyChatContextResourcesChatID ForeignKeyConstraint = "chat_context_resources_chat_id_fkey" // ALTER TABLE ONLY chat_context_resources ADD CONSTRAINT chat_context_resources_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatDebugRunsChatID ForeignKeyConstraint = "chat_debug_runs_chat_id_fkey" // ALTER TABLE ONLY chat_debug_runs ADD CONSTRAINT chat_debug_runs_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatDebugStepsChatID ForeignKeyConstraint = "chat_debug_steps_chat_id_fkey" // ALTER TABLE ONLY chat_debug_steps ADD CONSTRAINT chat_debug_steps_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
ForeignKeyChatDiffStatusesChatID ForeignKeyConstraint = "chat_diff_statuses_chat_id_fkey" // ALTER TABLE ONLY chat_diff_statuses ADD CONSTRAINT chat_diff_statuses_chat_id_fkey FOREIGN KEY (chat_id) REFERENCES chats(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- The workspace_agent_context_* enum types are owned by migration
|
||||
-- 000522 and are still in use by workspace_agent_context_resources, so
|
||||
-- they are intentionally left in place here.
|
||||
DROP TABLE IF EXISTS chat_context_resources;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Creates chat_context_resources: a per-chat pinned copy of
|
||||
-- workspace_agent_context_resources (semantics in COMMENT ON TABLE
|
||||
-- below). Migration-specific notes: there is deliberately no FK to
|
||||
-- workspace_agents so the pin survives agent replacement and workspace
|
||||
-- rebuilds, and the body_kind/status enum types are reused from 000522
|
||||
-- and must not be recreated here.
|
||||
CREATE TABLE chat_context_resources (
|
||||
chat_id UUID NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
||||
source TEXT NOT NULL,
|
||||
body_kind workspace_agent_context_body_kind NOT NULL,
|
||||
body JSONB NOT NULL,
|
||||
content_hash BYTEA NOT NULL,
|
||||
size_bytes BIGINT NOT NULL,
|
||||
status workspace_agent_context_resource_status NOT NULL,
|
||||
error TEXT NOT NULL DEFAULT '',
|
||||
source_path TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (chat_id, source)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE chat_context_resources IS 'Per-chat pinned copy of the agent context resources a chat is hydrated against. Copied from workspace_agent_context_resources at chat hydration and context refresh; survives agent replacement and workspace rebuilds.';
|
||||
COMMENT ON COLUMN chat_context_resources.source IS 'Resource locator: canonical file path for file-backed kinds, or the MCP server name for mcp_server resources.';
|
||||
COMMENT ON COLUMN chat_context_resources.body_kind IS 'Discriminator for the body JSON shape. Matches the proto oneof variant: instruction_file, skill, mcp_config, mcp_server. PLUGIN/HOOK/SUBAGENT/COMMAND are reserved for the Claude Code plugin RFC.';
|
||||
COMMENT ON COLUMN chat_context_resources.body IS 'protojson-encoded variant body matching body_kind. Always populated; non-OK statuses use the variant zero value so the wire kind is still attributable.';
|
||||
COMMENT ON COLUMN chat_context_resources.content_hash IS 'sha256 over the resource''s original bytes (or transport-encoded server tool list).';
|
||||
COMMENT ON COLUMN chat_context_resources.size_bytes IS 'Original payload size in bytes; populated regardless of status.';
|
||||
COMMENT ON COLUMN chat_context_resources.status IS 'Per-resource status. ok carries a populated body; oversize, unreadable, invalid, and excluded carry an empty body plus an error string.';
|
||||
COMMENT ON COLUMN chat_context_resources.error IS 'Per-resource error or warning string. Populated whenever status is non-ok; may also carry a non-fatal warning when status is ok.';
|
||||
COMMENT ON COLUMN chat_context_resources.source_path IS 'User-declared scan root that produced this resource. Empty for built-in scan roots.';
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
-- Pinned context resources covering each non-reserved body kind plus a
|
||||
-- non-OK status. The earlier chat fixtures already insert at least one row
|
||||
-- into chats; we attach the resources to the first such chat (ordered
|
||||
-- deterministically) so migration tests see a non-empty
|
||||
-- chat_context_resources table without hard-coding a specific chat ID.
|
||||
INSERT INTO chat_context_resources (
|
||||
chat_id,
|
||||
source,
|
||||
body_kind,
|
||||
body,
|
||||
content_hash,
|
||||
size_bytes,
|
||||
status,
|
||||
error,
|
||||
source_path
|
||||
)
|
||||
SELECT
|
||||
c.id,
|
||||
v.source,
|
||||
v.body_kind::workspace_agent_context_body_kind,
|
||||
v.body::jsonb,
|
||||
decode(v.content_hash, 'hex'),
|
||||
v.size_bytes,
|
||||
v.status::workspace_agent_context_resource_status,
|
||||
v.error,
|
||||
v.source_path
|
||||
FROM (
|
||||
SELECT id FROM chats ORDER BY created_at, id LIMIT 1
|
||||
) AS c
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
(
|
||||
'/home/coder/workspace/AGENTS.md',
|
||||
'instruction_file',
|
||||
'{"content":"aGVsbG8="}',
|
||||
'1111111111111111111111111111111111111111111111111111111111111111',
|
||||
5::bigint,
|
||||
'ok',
|
||||
'',
|
||||
''
|
||||
),
|
||||
(
|
||||
'/home/coder/workspace/.agents/skills/example/SKILL.md',
|
||||
'skill',
|
||||
'{"meta":"LS0tCm5hbWU6IGV4YW1wbGUKLS0tCmJvZHk=","name":"example","description":"Example skill"}',
|
||||
'2222222222222222222222222222222222222222222222222222222222222222',
|
||||
32::bigint,
|
||||
'ok',
|
||||
'',
|
||||
'/home/coder/workspace'
|
||||
),
|
||||
(
|
||||
'/home/coder/workspace/.mcp.json',
|
||||
'mcp_config',
|
||||
'{}',
|
||||
'3333333333333333333333333333333333333333333333333333333333333333',
|
||||
128::bigint,
|
||||
'ok',
|
||||
'',
|
||||
''
|
||||
),
|
||||
(
|
||||
'mcp:echo',
|
||||
'mcp_server',
|
||||
'{"server_name":"echo","description":"echoes input"}',
|
||||
'4444444444444444444444444444444444444444444444444444444444444444',
|
||||
256::bigint,
|
||||
'ok',
|
||||
'',
|
||||
'/home/coder/workspace/.mcp.json'
|
||||
),
|
||||
(
|
||||
'/home/coder/workspace/big.md',
|
||||
'instruction_file',
|
||||
'{}',
|
||||
'5555555555555555555555555555555555555555555555555555555555555555',
|
||||
99999::bigint,
|
||||
'oversize',
|
||||
'file exceeds 64KiB per-resource cap',
|
||||
''
|
||||
)
|
||||
) AS v(source, body_kind, body, content_hash, size_bytes, status, error, source_path);
|
||||
Generated
+23
@@ -4809,6 +4809,29 @@ type Chat struct {
|
||||
ContextError string `db:"context_error" json:"context_error"`
|
||||
}
|
||||
|
||||
// Per-chat pinned copy of the agent context resources a chat is hydrated against. Copied from workspace_agent_context_resources at chat hydration and context refresh; survives agent replacement and workspace rebuilds.
|
||||
type ChatContextResource struct {
|
||||
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
|
||||
// Resource locator: canonical file path for file-backed kinds, or the MCP server name for mcp_server resources.
|
||||
Source string `db:"source" json:"source"`
|
||||
// Discriminator for the body JSON shape. Matches the proto oneof variant: instruction_file, skill, mcp_config, mcp_server. PLUGIN/HOOK/SUBAGENT/COMMAND are reserved for the Claude Code plugin RFC.
|
||||
BodyKind WorkspaceAgentContextBodyKind `db:"body_kind" json:"body_kind"`
|
||||
// protojson-encoded variant body matching body_kind. Always populated; non-OK statuses use the variant zero value so the wire kind is still attributable.
|
||||
Body json.RawMessage `db:"body" json:"body"`
|
||||
// sha256 over the resource's original bytes (or transport-encoded server tool list).
|
||||
ContentHash []byte `db:"content_hash" json:"content_hash"`
|
||||
// Original payload size in bytes; populated regardless of status.
|
||||
SizeBytes int64 `db:"size_bytes" json:"size_bytes"`
|
||||
// Per-resource status. ok carries a populated body; oversize, unreadable, invalid, and excluded carry an empty body plus an error string.
|
||||
Status WorkspaceAgentContextResourceStatus `db:"status" json:"status"`
|
||||
// Per-resource error or warning string. Populated whenever status is non-ok; may also carry a non-fatal warning when status is ok.
|
||||
Error string `db:"error" json:"error"`
|
||||
// User-declared scan root that produced this resource. Empty for built-in scan roots.
|
||||
SourcePath string `db:"source_path" json:"source_path"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
}
|
||||
|
||||
type ChatDebugRun struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
ChatID uuid.UUID `db:"chat_id" json:"chat_id"`
|
||||
|
||||
Generated
+1
-2
@@ -200,8 +200,7 @@ type sqlcQuerier interface {
|
||||
DeleteOldChatFiles(ctx context.Context, arg DeleteOldChatFilesParams) (int64, error)
|
||||
// Deletes chats that have been archived for longer than the given
|
||||
// threshold. Active (non-archived) chats are never deleted.
|
||||
// Related chat_messages, chat_diff_statuses, and
|
||||
// chat_queued_messages are removed via ON DELETE CASCADE.
|
||||
// All chat-scoped child tables are removed via ON DELETE CASCADE.
|
||||
// Parent/root references on child chats are SET NULL.
|
||||
DeleteOldChats(ctx context.Context, arg DeleteOldChatsParams) (int64, error)
|
||||
DeleteOldConnectionLogs(ctx context.Context, arg DeleteOldConnectionLogsParams) (int64, error)
|
||||
|
||||
Generated
+1
-2
@@ -6697,8 +6697,7 @@ type DeleteOldChatsParams struct {
|
||||
|
||||
// Deletes chats that have been archived for longer than the given
|
||||
// threshold. Active (non-archived) chats are never deleted.
|
||||
// Related chat_messages, chat_diff_statuses, and
|
||||
// chat_queued_messages are removed via ON DELETE CASCADE.
|
||||
// All chat-scoped child tables are removed via ON DELETE CASCADE.
|
||||
// Parent/root references on child chats are SET NULL.
|
||||
func (q *sqlQuerier) DeleteOldChats(ctx context.Context, arg DeleteOldChatsParams) (int64, error) {
|
||||
result, err := q.db.ExecContext(ctx, deleteOldChats, arg.BeforeTime, arg.LimitCount)
|
||||
|
||||
@@ -2551,8 +2551,7 @@ WHERE id = @id::uuid;
|
||||
-- name: DeleteOldChats :execrows
|
||||
-- Deletes chats that have been archived for longer than the given
|
||||
-- threshold. Active (non-archived) chats are never deleted.
|
||||
-- Related chat_messages, chat_diff_statuses, and
|
||||
-- chat_queued_messages are removed via ON DELETE CASCADE.
|
||||
-- All chat-scoped child tables are removed via ON DELETE CASCADE.
|
||||
-- Parent/root references on child chats are SET NULL.
|
||||
WITH deletable AS (
|
||||
SELECT id
|
||||
|
||||
Generated
+1
@@ -21,6 +21,7 @@ const (
|
||||
UniqueBoundaryLogsPkey UniqueConstraint = "boundary_logs_pkey" // ALTER TABLE ONLY boundary_logs ADD CONSTRAINT boundary_logs_pkey PRIMARY KEY (id);
|
||||
UniqueBoundarySessionsPkey UniqueConstraint = "boundary_sessions_pkey" // ALTER TABLE ONLY boundary_sessions ADD CONSTRAINT boundary_sessions_pkey PRIMARY KEY (id);
|
||||
UniqueBoundaryUsageStatsPkey UniqueConstraint = "boundary_usage_stats_pkey" // ALTER TABLE ONLY boundary_usage_stats ADD CONSTRAINT boundary_usage_stats_pkey PRIMARY KEY (replica_id);
|
||||
UniqueChatContextResourcesPkey UniqueConstraint = "chat_context_resources_pkey" // ALTER TABLE ONLY chat_context_resources ADD CONSTRAINT chat_context_resources_pkey PRIMARY KEY (chat_id, source);
|
||||
UniqueChatDebugRunsPkey UniqueConstraint = "chat_debug_runs_pkey" // ALTER TABLE ONLY chat_debug_runs ADD CONSTRAINT chat_debug_runs_pkey PRIMARY KEY (id);
|
||||
UniqueChatDebugStepsPkey UniqueConstraint = "chat_debug_steps_pkey" // ALTER TABLE ONLY chat_debug_steps ADD CONSTRAINT chat_debug_steps_pkey PRIMARY KEY (id);
|
||||
UniqueChatDiffStatusesPkey UniqueConstraint = "chat_diff_statuses_pkey" // ALTER TABLE ONLY chat_diff_statuses ADD CONSTRAINT chat_diff_statuses_pkey PRIMARY KEY (chat_id);
|
||||
|
||||
Reference in New Issue
Block a user