1import * as Effect from "effect/Effect";
2import * as SqlClient from "effect/sql/SqlClient";
3
4export default Effect.gen(function* () {
5 const sql = yield* SqlClient.SqlClient;
6
7 yield* sql`
8 ALTER TABLE projection_projects
9 ADD COLUMN default_model_selection_json TEXT
10 `;
11
12 yield* sql`
13 UPDATE projection_projects
14 SET default_model_selection_json = CASE
15 WHEN default_model IS NULL THEN NULL
16 ELSE json_object(
17 'provider',
18 CASE
19 WHEN lower(default_model) LIKE '%claude%' THEN 'claudeAgent'
20 ELSE 'codex'
21 END,
22 'model',
23 default_model
24 )
25 END
26 WHERE default_model_selection_json IS NULL
27 `;
28
29 yield* sql`
30 ALTER TABLE projection_threads
31 ADD COLUMN model_selection_json TEXT
32 `;
33
34 yield* sql`
35 UPDATE projection_threads
36 SET model_selection_json = json_object(
37 'provider',
38 COALESCE(
39 (
40 SELECT provider_name
41 FROM projection_thread_sessions
42 WHERE projection_thread_sessions.thread_id = projection_threads.thread_id
43 ),
44 CASE
45 WHEN lower(model) LIKE '%claude%' THEN 'claudeAgent'
46 ELSE 'codex'
47 END,
48 'codex'
49 ),
50 'model',
51 model
52 )
53 WHERE model_selection_json IS NULL
54 `;
55
56 yield* sql`
57 ALTER TABLE projection_projects
58 DROP COLUMN default_model
59 `;
60
61 yield* sql`
62 ALTER TABLE projection_threads
63 DROP COLUMN model
64 `;
65
66 yield* sql`
67 UPDATE orchestration_events
68 SET payload_json = CASE
69 WHEN json_type(payload_json, '$.defaultModel') = 'null' THEN json_remove(
70 json_set(payload_json, '$.defaultModelSelection', json('null')),
71 '$.defaultProvider',
72 '$.defaultModel',
73 '$.defaultModelOptions'
74 )
75 ELSE json_remove(
76 json_set(
77 payload_json,
78 '$.defaultModelSelection',
79 json_patch(
80 json_object(
81 'provider',
82 CASE
83 WHEN json_extract(payload_json, '$.defaultProvider') IS NOT NULL
84 THEN json_extract(payload_json, '$.defaultProvider')
85 WHEN lower(json_extract(payload_json, '$.defaultModel')) LIKE '%claude%'
86 THEN 'claudeAgent'
87 ELSE 'codex'
88 END,
89 'model',
90 json_extract(payload_json, '$.defaultModel')
91 ),
92 CASE
93 WHEN json_type(payload_json, '$.defaultModelOptions') IS NULL THEN '{}'
94 WHEN json_type(payload_json, '$.defaultModelOptions.codex') IS NOT NULL
95 OR json_type(payload_json, '$.defaultModelOptions.claudeAgent') IS NOT NULL
96 THEN CASE
97 WHEN (
98 CASE
99 WHEN json_extract(payload_json, '$.defaultProvider') IS NOT NULL
100 THEN json_extract(payload_json, '$.defaultProvider')
101 WHEN lower(json_extract(payload_json, '$.defaultModel')) LIKE '%claude%'
102 THEN 'claudeAgent'
103 ELSE 'codex'
104 END
105 ) = 'claudeAgent'
106 THEN CASE
107 WHEN json_type(payload_json, '$.defaultModelOptions.claudeAgent') IS NOT NULL
108 THEN json_object(
109 'options',
110 json(json_extract(payload_json, '$.defaultModelOptions.claudeAgent'))
111 )
112 WHEN json_type(payload_json, '$.defaultModelOptions.codex') IS NOT NULL
113 THEN json_object(
114 'options',
115 json(json_extract(payload_json, '$.defaultModelOptions.codex'))
116 )
117 ELSE '{}'
118 END
119 ELSE CASE
120 WHEN json_type(payload_json, '$.defaultModelOptions.codex') IS NOT NULL
121 THEN json_object(
122 'options',
123 json(json_extract(payload_json, '$.defaultModelOptions.codex'))
124 )
125 WHEN json_type(payload_json, '$.defaultModelOptions.claudeAgent') IS NOT NULL
126 THEN json_object(
127 'options',
128 json(json_extract(payload_json, '$.defaultModelOptions.claudeAgent'))
129 )
130 ELSE '{}'
131 END
132 END
133 ELSE json_object(
134 'options',
135 json(json_extract(payload_json, '$.defaultModelOptions'))
136 )
137 END
138 )
139 ),
140 '$.defaultProvider',
141 '$.defaultModel',
142 '$.defaultModelOptions'
143 )
144 END
145 WHERE event_type IN ('project.created', 'project.meta-updated')
146 AND json_type(payload_json, '$.defaultModelSelection') IS NULL
147 AND json_type(payload_json, '$.defaultModel') IS NOT NULL
148 `;
149
150 yield* sql`
151 UPDATE orchestration_events
152 SET payload_json = json_remove(
153 json_set(
154 payload_json,
155 '$.modelSelection',
156 json_patch(
157 json_object(
158 'provider',
159 CASE
160 WHEN json_extract(payload_json, '$.provider') IS NOT NULL
161 THEN json_extract(payload_json, '$.provider')
162 WHEN lower(json_extract(payload_json, '$.model')) LIKE '%claude%'
163 THEN 'claudeAgent'
164 ELSE 'codex'
165 END,
166 'model',
167 json_extract(payload_json, '$.model')
168 ),
169 CASE
170 WHEN json_type(payload_json, '$.modelOptions') IS NULL THEN '{}'
171 WHEN json_type(payload_json, '$.modelOptions.codex') IS NOT NULL
172 OR json_type(payload_json, '$.modelOptions.claudeAgent') IS NOT NULL
173 THEN CASE
174 WHEN (
175 CASE
176 WHEN json_extract(payload_json, '$.provider') IS NOT NULL
177 THEN json_extract(payload_json, '$.provider')
178 WHEN lower(json_extract(payload_json, '$.model')) LIKE '%claude%'
179 THEN 'claudeAgent'
180 ELSE 'codex'
181 END
182 ) = 'claudeAgent'
183 THEN CASE
184 WHEN json_type(payload_json, '$.modelOptions.claudeAgent') IS NOT NULL
185 THEN json_object(
186 'options',
187 json(json_extract(payload_json, '$.modelOptions.claudeAgent'))
188 )
189 WHEN json_type(payload_json, '$.modelOptions.codex') IS NOT NULL
190 THEN json_object(
191 'options',
192 json(json_extract(payload_json, '$.modelOptions.codex'))
193 )
194 ELSE '{}'
195 END
196 ELSE CASE
197 WHEN json_type(payload_json, '$.modelOptions.codex') IS NOT NULL
198 THEN json_object(
199 'options',
200 json(json_extract(payload_json, '$.modelOptions.codex'))
201 )
202 WHEN json_type(payload_json, '$.modelOptions.claudeAgent') IS NOT NULL
203 THEN json_object(
204 'options',
205 json(json_extract(payload_json, '$.modelOptions.claudeAgent'))
206 )
207 ELSE '{}'
208 END
209 END
210 ELSE json_object('options', json(json_extract(payload_json, '$.modelOptions')))
211 END
212 )
213 ),
214 '$.provider',
215 '$.model',
216 '$.modelOptions'
217 )
218 WHERE event_type IN ('thread.created', 'thread.meta-updated', 'thread.turn-start-requested')
219 AND json_type(payload_json, '$.modelSelection') IS NULL
220 AND json_type(payload_json, '$.model') IS NOT NULL
221 `;
222
223 // Backfill thread.created events that predate the model field entirely
224 yield* sql`
225 UPDATE orchestration_events
226 SET payload_json = json_set(
227 payload_json,
228 '$.modelSelection',
229 json(json_object('provider', 'codex', 'model', 'gpt-5.4'))
230 )
231 WHERE event_type = 'thread.created'
232 AND json_type(payload_json, '$.modelSelection') IS NULL
233 AND json_type(payload_json, '$.model') IS NULL
234 `;
235});