File size: 4,803 Bytes
4dc70fb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
-- OpenCode Tables for Supabase
-- Run this in Supabase SQL Editor

-- Sessions table
CREATE TABLE IF NOT EXISTS opencode_sessions (
  id TEXT PRIMARY KEY,
  user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  title TEXT NOT NULL,
  agent_id TEXT DEFAULT 'build',
  provider_id TEXT,
  model_id TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_opencode_sessions_user_id ON opencode_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_opencode_sessions_updated_at ON opencode_sessions(updated_at DESC);

-- Messages table
CREATE TABLE IF NOT EXISTS opencode_messages (
  id TEXT PRIMARY KEY,
  session_id TEXT NOT NULL REFERENCES opencode_sessions(id) ON DELETE CASCADE,
  role TEXT NOT NULL CHECK (role IN ('user', 'assistant')),
  content TEXT,
  provider_id TEXT,
  model_id TEXT,
  input_tokens INTEGER,
  output_tokens INTEGER,
  error TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_opencode_messages_session_id ON opencode_messages(session_id);
CREATE INDEX IF NOT EXISTS idx_opencode_messages_created_at ON opencode_messages(session_id, created_at);

-- Message parts (text, tool_call, tool_result)
CREATE TABLE IF NOT EXISTS opencode_message_parts (
  id TEXT PRIMARY KEY,
  message_id TEXT NOT NULL REFERENCES opencode_messages(id) ON DELETE CASCADE,
  type TEXT NOT NULL CHECK (type IN ('text', 'tool_call', 'tool_result')),
  content TEXT,
  tool_call_id TEXT,
  tool_name TEXT,
  tool_args JSONB,
  tool_output TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_opencode_message_parts_message_id ON opencode_message_parts(message_id);

-- Usage tracking (replaces sandbox_usage)
CREATE TABLE IF NOT EXISTS opencode_usage (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  usage_date DATE NOT NULL DEFAULT CURRENT_DATE,
  input_tokens INTEGER DEFAULT 0,
  output_tokens INTEGER DEFAULT 0,
  request_count INTEGER DEFAULT 0,
  UNIQUE(user_id, usage_date)
);

CREATE INDEX IF NOT EXISTS idx_opencode_usage_user_date ON opencode_usage(user_id, usage_date);

-- Row Level Security
ALTER TABLE opencode_sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE opencode_messages ENABLE ROW LEVEL SECURITY;
ALTER TABLE opencode_message_parts ENABLE ROW LEVEL SECURITY;
ALTER TABLE opencode_usage ENABLE ROW LEVEL SECURITY;

-- RLS Policies: Users can only access their own data
CREATE POLICY "Users can CRUD their own sessions"
  ON opencode_sessions FOR ALL
  USING (auth.uid() = user_id);

CREATE POLICY "Users can CRUD messages in their sessions"
  ON opencode_messages FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM opencode_sessions
      WHERE opencode_sessions.id = opencode_messages.session_id
      AND opencode_sessions.user_id = auth.uid()
    )
  );

CREATE POLICY "Users can CRUD parts in their messages"
  ON opencode_message_parts FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM opencode_messages
      JOIN opencode_sessions ON opencode_sessions.id = opencode_messages.session_id
      WHERE opencode_messages.id = opencode_message_parts.message_id
      AND opencode_sessions.user_id = auth.uid()
    )
  );

CREATE POLICY "Users can access their own usage"
  ON opencode_usage FOR ALL
  USING (auth.uid() = user_id);

-- Function to increment usage (atomic)
CREATE OR REPLACE FUNCTION increment_opencode_usage(
  p_user_id UUID,
  p_input_tokens INTEGER DEFAULT 0,
  p_output_tokens INTEGER DEFAULT 0
)
RETURNS void AS $$
BEGIN
  INSERT INTO opencode_usage (user_id, usage_date, input_tokens, output_tokens, request_count)
  VALUES (p_user_id, CURRENT_DATE, p_input_tokens, p_output_tokens, 1)
  ON CONFLICT (user_id, usage_date)
  DO UPDATE SET
    input_tokens = opencode_usage.input_tokens + EXCLUDED.input_tokens,
    output_tokens = opencode_usage.output_tokens + EXCLUDED.output_tokens,
    request_count = opencode_usage.request_count + 1;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Function to get daily usage
CREATE OR REPLACE FUNCTION get_opencode_usage(p_user_id UUID)
RETURNS TABLE(input_tokens INTEGER, output_tokens INTEGER, request_count INTEGER) AS $$
BEGIN
  RETURN QUERY
  SELECT 
    COALESCE(u.input_tokens, 0)::INTEGER,
    COALESCE(u.output_tokens, 0)::INTEGER,
    COALESCE(u.request_count, 0)::INTEGER
  FROM opencode_usage u
  WHERE u.user_id = p_user_id AND u.usage_date = CURRENT_DATE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Update timestamp trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER opencode_sessions_updated_at
  BEFORE UPDATE ON opencode_sessions
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();