clouds-coder 2026.9.20__tar.gz → 2026.9.22__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/Clouds_Coder.py +2336 -490
- {clouds_coder-2026.9.20/clouds_coder.egg-info → clouds_coder-2026.9.22}/PKG-INFO +1 -1
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22/clouds_coder.egg-info}/PKG-INFO +1 -1
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/pyproject.toml +1 -1
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_liquid_kernel.py +8 -14
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/LICENSE +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/README.md +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/clouds_coder.egg-info/SOURCES.txt +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/clouds_coder.egg-info/dependency_links.txt +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/clouds_coder.egg-info/entry_points.txt +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/clouds_coder.egg-info/requires.txt +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/clouds_coder.egg-info/top_level.txt +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/setup.cfg +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_admin_i18n.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_authoritative_prompt_delivery.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_collaboration.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_context_auto_compact.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_context_memory_recall.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_dynamic_agent_loop.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_http_incomplete_read.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_ide_v2.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_kimi_tool_history_compat.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_mcp_workspace_trust.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_model_configuration.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_plan_structure.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_plan_todo_progression.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_process_manager.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_rag_admin_auth.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_requirements_coverage.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_session_performance.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_shell_timeout_modes.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_single_no_plan_todo_bootstrap.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_skill_selection.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_skills_studio_v2.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_smoke.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_step_skill_runtime.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_sync_routing_scope.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_thinking_only_recovery.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_todo_tree_revision_guard.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_web_search_local_graph.py +0 -0
- {clouds_coder-2026.9.20 → clouds_coder-2026.9.22}/tests/test_web_search_public_discovery.py +0 -0
|
@@ -93,15 +93,25 @@ def _connect_sqlite(
|
|
|
93
93
|
) -> sqlite3.Connection:
|
|
94
94
|
# sqlite3.Connection's own context manager does not close the connection.
|
|
95
95
|
# Polling/request handlers must not rely on cyclic GC to release DB/WAL FDs.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
operation = "connect"
|
|
97
|
+
try:
|
|
98
|
+
conn = sqlite3.connect(
|
|
99
|
+
database, timeout=timeout, isolation_level=isolation_level,
|
|
100
|
+
factory=_ClosingSQLiteConnection,
|
|
101
|
+
)
|
|
102
|
+
except sqlite3.Error as exc:
|
|
103
|
+
exc.sqlite_database_path = database
|
|
104
|
+
exc.sqlite_operation = operation
|
|
105
|
+
raise
|
|
100
106
|
try:
|
|
101
107
|
conn.row_factory = row_factory
|
|
102
108
|
for statement in pragmas:
|
|
109
|
+
operation = statement
|
|
103
110
|
conn.execute(statement)
|
|
104
|
-
except BaseException:
|
|
111
|
+
except BaseException as exc:
|
|
112
|
+
if isinstance(exc, sqlite3.Error):
|
|
113
|
+
exc.sqlite_database_path = database
|
|
114
|
+
exc.sqlite_operation = operation
|
|
105
115
|
conn.close()
|
|
106
116
|
raise
|
|
107
117
|
return conn
|
|
@@ -113,256 +123,315 @@ except Exception:
|
|
|
113
123
|
_AESGCM = None
|
|
114
124
|
|
|
115
125
|
_EMBEDDED_LIQUID_KERNEL_PACKAGE_B64 = (
|
|
116
|
-
"
|
|
126
|
+
"UEsDBBQAAAAIAAAAIVzNzUY/oAAAAHYBAAALAAAAX19pbml0X18ucHl1jrsKwkAQRfv9iiWVgvgHVppCjEZ8NSLDYjZhYHZHh10Lv96QR2OSKe+5dzilsNPL"
|
|
117
127
|
"J/sgTBrdiyXomdL1pbc8u162+QH2+SY9L5oww3fEYmfFW1q3qyMZb4c0FWEZxqfoA7quX9jSRApgP0wxIHuoTUqsWupZnCH82hE+VwrAEAHolb439eRPOGm/"
|
|
118
|
-
"JFPKY7yRHgOddo+mxHs+
|
|
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
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"+
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
174
|
-
"
|
|
175
|
-
"
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
"
|
|
180
|
-
"
|
|
181
|
-
"
|
|
182
|
-
"
|
|
183
|
-
"
|
|
184
|
-
"
|
|
185
|
-
"
|
|
186
|
-
"
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"
|
|
200
|
-
"
|
|
201
|
-
"
|
|
202
|
-
"
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
208
|
-
"
|
|
209
|
-
"
|
|
210
|
-
"
|
|
211
|
-
"
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
"
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
"
|
|
221
|
-
"
|
|
222
|
-
"
|
|
223
|
-
"
|
|
224
|
-
"
|
|
225
|
-
"
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
"
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
234
|
-
"
|
|
235
|
-
"
|
|
236
|
-
"
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
"
|
|
246
|
-
"
|
|
247
|
-
"
|
|
248
|
-
"
|
|
249
|
-
"
|
|
250
|
-
"
|
|
251
|
-
"
|
|
252
|
-
"
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
"
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
"
|
|
267
|
-
"
|
|
268
|
-
"
|
|
269
|
-
"
|
|
270
|
-
"+
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
"
|
|
279
|
-
"
|
|
280
|
-
"
|
|
281
|
-
"
|
|
282
|
-
"
|
|
283
|
-
"
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
"
|
|
287
|
-
"
|
|
288
|
-
"
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
"
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"
|
|
295
|
-
"
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
301
|
-
"
|
|
302
|
-
"
|
|
303
|
-
"
|
|
304
|
-
"
|
|
305
|
-
"
|
|
306
|
-
"
|
|
307
|
-
"
|
|
308
|
-
"
|
|
309
|
-
"
|
|
310
|
-
"
|
|
311
|
-
"
|
|
312
|
-
"
|
|
313
|
-
"
|
|
314
|
-
"
|
|
315
|
-
"
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
"
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
"
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
"
|
|
329
|
-
"
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
"
|
|
334
|
-
"
|
|
335
|
-
"
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"
|
|
339
|
-
"
|
|
340
|
-
"
|
|
341
|
-
"
|
|
342
|
-
"
|
|
343
|
-
"
|
|
344
|
-
"
|
|
345
|
-
"
|
|
346
|
-
"
|
|
347
|
-
"
|
|
348
|
-
"
|
|
349
|
-
"
|
|
350
|
-
"/
|
|
351
|
-
"
|
|
352
|
-
"
|
|
353
|
-
"
|
|
354
|
-
"
|
|
355
|
-
"
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
359
|
-
"
|
|
360
|
-
"
|
|
361
|
-
"
|
|
362
|
-
"
|
|
363
|
-
"
|
|
364
|
-
"
|
|
365
|
-
"
|
|
128
|
+
"JFPKY7yRHgOddo+mxHs+rV43HuoHUEsDBBQAAAAIAAAAIVzfKqUoQVUAAM19AQAKAAAAY29udHJvbC5wedV9a3cbx5Hod/2KyeT4CFAgiNTDcbiGs7RMO9qV"
|
|
129
|
+
"SK9E595chmd2CAzIiQAMghmIYhj+99v16O7qxwwAUr6bCx+LwEw/qqurq6ur6zFdVfMky6brZr0qsiwp58tq1ST5YlE1eVNWi/rRI/2sbvTXq7y+mpUX5uc8"
|
|
130
|
+
"H+vv9Ee9HK6bcqaf/q2uFvr7PG+u9Peq1t9W+WJSzfWv+krWrv8+K5vihfm5vliuqnFRm8r1jfnaXK2KfFIuLs2Dcl7o7+t1OXk0hSGPq9msGOMA9Zgnxd/X"
|
|
131
|
+
"Bb2d5E0+nuV1Xdi3+pEpUUDL4jX+HmB//6gW3NJSDVYhQxf7GcaOL5qbpQJSPz9c3AyS1/lsll/MuCa0US6mlS7yf9TvN+r3o0ePjv588vaX0zcnx9m7kx+O"
|
|
132
|
+
"PiSjpJeeTKfpIElP1wvVLH67Khcf+fvh5eVKoav8VKT9R69Pjk/fn7zNPrz+09G7w+zPR+8/qKZUI/tew2+z14c/H37/5u2b07+IYs8f/efR+2N4Cw0dvj51"
|
|
133
|
+
"mvj+8MPR2zfHRxkX+nDyy/vXR+rV48ePp1sQW1fb/O748B00mL6eVetJnbyuJsUq+T6vi1m5KNJHhL7hVb5aqDGbTqZNscqaqpplChXrWVMPkotiWilAVtV6"
|
|
134
|
+
"MbG/1gtuQRHZfNlky2pWjm9MO+vLebFQT/Ell8Rm3XLTcqY7VKN6NCmmisDq8aq8KHr9g0eJ+qwKhYdFcos/4JMu8nmRHiRinAP7clwtmlU+brJPxapWyLIF"
|
|
135
|
+
"fWTJSvkyvyjV8imLWlU4SxEgoAkawFOCGh4wwp5eVdXHOj2nRu4eqXl7ZGf19ERRzs8nb9+8/svOU4tIkHjp4b+D5MkgWVWzYpQqMGCUxedmdKzI3cXTrKwb"
|
|
136
|
+
"qpFUq+TsvO+B9vP7k3c/nz4IOHdye/RnW/jqZsU1AL409eH706GarA8f7geZJc5eOwS3d15pIOxtyoeLo2cWyZONc8JF47PCJPrjm7fIqIjY04+ForXZcHmj"
|
|
137
|
+
"aDLOMYj8UrGyvNIhJXIVZ9l6lWI0wtWY/L0K7qQNHimMMf98e/SDPzg1m/8oFnXR9G7FCAfBKAYRIO/6j3785e3b7PCno+NTtaLfH8WafdLS+cCBX7X15t27"
|
|
138
|
+
"X04Pv397lGl2/1qN/ORYNY77BY05XzdXit7LMdJaqhEI/KVuisW40I+KT/ls7RZSA6jkg3w9KRv9oy4vaR/in2pzv6g+q599RXG4jSZvy7+r3fg/EU1Hq1W1"
|
|
139
|
+
"6r1fL2DzxB9MZkCcWVYuyibLeorBT4EYJ8UBLLZBMlfjzS/1r1qtmHV9kJSLRo3w5d7eQFVv8nKmnk3KcZP8MwECVu8EHcOnXqsR9/pD0xG327clVM9D6FhV"
|
|
140
|
+
"hmWOX2GRz3AQGU12VgDkqVeNwFIVFVw9/qGqKvi8ggysKgnQ9vRPVfb2rs8LNVtU171+8vS7ZDqr8sZZi4C7IfzTM6VB7OrB1CkcKREDKyr4nWpQZjhZz5c1"
|
|
141
|
+
"lRwkitaAG+X1uCxHP+azWj2rFVtSw7ypR6crKFMXy3yVN9WqHvXSAVD0QdoHhE9zxQtGqhMDBMhjBAkIQwcoAw2SqRJ2LvLxRwuY+kuANasbOzkSSjXkSY2t"
|
|
142
|
+
"DLFRYEo9RaYVyHujdN1Mn36T9gmtxedxoTjxEf5RZBq0qAHQYKrBzMtxCOgyv4F+LZxAPdQaQqLwoJbQcP5xUir+jz80korPahll1Uf8SWA1xXypZhhrXpfN"
|
|
143
|
+
"VQZbfm+aDm/xEfy6G96CnDqEf14qsrwqPt8Nm/kytS0Mr1dKIKbxi/ljUOMzWC4mCrTRc38u+1DcQ2E4DVU9HF/Nq0kPuh8ke9XXmn7bEb0EaZkrr4rlLB8X"
|
|
144
|
+
"XB3Gauijvsqfv/o6u7hpCrXt5NcHCX6NEisfPIZUB0ojfiblZaE2IrFKVvN8Vv6j0CtTSR1FrVdCdfE3Jfpj+7DSzph7rM6pq3KalHW5UEtVMUC9JoCgAxK6"
|
|
145
|
+
"dfYxLHmnm1AbeKQZ6E+2k5d1EeGEablQFSxjuVD7+KyAVUYPknzVlFMlCybzdd2o7V7N6Xo1LhKgCOAYOchZhTpqaapBBBx444X9mABW4oIC5jpbIsnDN26w"
|
|
146
|
+
"XNC4hore5nWvL2e3uWJ2qGuy3KNY3qpcKsrVk57+9a8A+zPBGhlDuhYSv0LVqqlhVfSgLLY2HKYAAyxFXJV9WG8ka6gy8ArXqml3F6xCzRhOQdzJ1cwpMSzh"
|
|
147
|
+
"KoROrGDHgDg9g4fnAhGMOBYBeayCUIgyFjwjm0mBgTXS/7xUBzk61vmAl3XCbxPbX1+uH+xUrxKxNjJYWGqFRMmkdaXoZUDs0l3IhIYhshZFAZo3I6URlVkK"
|
|
148
|
+
"A4ZUTKhzTWj9Ow9MWgMIaAeYPsNwgSLu3jrwfj+ANw4EMt7tgUj/uvjrIh3+rSoXitX/NhmNRoS0O/j618UtoeIu3R47lnvm0yIDXUGEu1k48mumUCyExHlY"
|
|
149
|
+
"l/mzD1f54vIqL+2ijbwLdwOti0AG7HNF9Wy7Hdjv5tEj0DooGf3ow9HpB4lZ+HZuTw+g6ziQ5+Z6fFVM1jM4O6cVKELkqbmaTarrRXalEApH4D3ndLyYlKC6"
|
|
150
|
+
"yWblXEmv7tt5/pk2jsjzMUBdTDJQOUTf53X0eVN9VJuz9wKEtmrdZHWhVvkkqLZuQCuUqS1drXIc40LhXw5SyfFVRhI5vKcdX7Sg5Nr5ep5dKq4GjQ+5+TsW"
|
|
151
|
+
"z1lp1IrS66L4OLvpxOrvn3eidb8Fra82oBWk+DhiX3zdhtmX32R7e93offH13gYMI7sFHeGkA88o5bWieX/4ykOz1si1Irr4VKxushfZJL+pO9H98ptOdD9v"
|
|
152
|
+
"Qfe+/8LH9/7zVoT/3q9rEK4qbcT4759/EYxvoOwXPmULxWcr0ifqmNVN3M9fdmL7RQu2X2xiGi/3WrGtcNqG7ucvN6N7/2X2chPCp+vZLFPH3EWjhIvVA/jJ"
|
|
153
|
+
"K4F12LX/dPj+h+z7X3746eg0e/vm3ZtTofhpxV4Ucxuw5mLsa/nUYOvFnsBWBFPP9zNkBlrcsEcHddopQB82bTk3sD7BHBpoa1UyGGoZ1DZKD9Ren6bB3gdl"
|
|
154
|
+
"tjgqQDOg+MFnqmd15CDBAMQ/RaLFipVJKMuUE/iFcMOXqeI1xWq5KhdNerfDocMM3LSmoJ4WK1AI0YGDJeSkWsxuWFIZJAiOOtsZYAYJVVZTnkhYtEQ6BbJQ"
|
|
155
|
+
"59ADK5kML9Vg1aMBCM/ecQKFI/UOZKLetmjomwOZ6u9M1zrXQvhtejmrLnJEFy4F+FJOivQOOodCWE20fO68oE7O74vcuoBLKHVaUwidlpfqzD4ROCvVOYcB"
|
|
156
|
+
"NgeJnjsMIC4eAVbBt4SQc8SfV/43XnkzEF3nYWSCrdRX5RKWAZezsNsZdkjTOVcrUOS7cym2eq+cg42qyQuYgPvbenKpuB0wtsjyFcqz6Gn/Qu0AfT3R4eue"
|
|
157
|
+
"6n9AjfRNMbjWHJb1FHSIhV6w/G4v+XbE7EB92d/b2wHNNBLFtJZrpE78TcuQx75Yz4tVOU5wtHVyUTRKYFuoTmGGVWfeERDAZvjMxRRq7bLiUzVbg6ieET32"
|
|
158
|
+
"DK9jfZdqvmi0glIK62cok7tTIrbbVfGp5CsrRxJURAM70YkrsovNmXocLqtlzz7ue1sfnH6gGfc4IQopsUptcoqLXCrqqUPBjd6j9jZbwd4El8j1lZICUFp2"
|
|
159
|
+
"pCYqq4aoBLVY2W+6BAk5HO+lHNRVWTeVap8v+rKJOkBdeWKdLpOP4SJc7+dppASqVTLYc6FUrESxmETfr+tiBWtoWeDV4RN9KUizBHKVAq7tveKmBWqIM+ae"
|
|
160
|
+
"futE2C0vbW3LZg5w1wxa6ChwAQUaR/SDD+45NBdn6vu589LZZXxxZSDllEFMPhlIqWTgCCODUP6wh+e7TmGNgQ3enLcLbbqK8/i8XYozXcjHsvxVvpoQR82u"
|
|
161
|
+
"i/LyCo/LQ0fIratpExb5/Z7fyqogsVyRjzl4D/fkYVAhPl/dBDOXKmK+pJvsV4Pkufpf8TcBpDOwcTVfzoqmgJV5puTp5JX6v7W0XqNnStqH81VYsl7jWssm"
|
|
162
|
+
"q2rZAjeWI0ZSLsarQlFBV8nlH15lsxxu2m5i5eUZ4E7fx/tiasC0UYVOmx3d3q5XcCkRvQRz+TuoL3FjMJdQoN/xtkj1SAunhToWaA0y66u4HvcJ+1/75tIX"
|
|
163
|
+
"FYfrJSyzngWB3s7tpRsXRPkBd44B7RwkJ+I3o3luymYG92DQhAIfW2GRzzOe2VHg0bKOUbyrfgcJKVDUXz7hDwAkx+gGOjDbp7NzQmvn+v2nslrXmRi0xOTt"
|
|
164
|
+
"XX/30UNZzaJUmyDZ6Db7KCBgZ0oudHrHqopRwXy2zp8upOeOZHkSc5iLDvSpaKHnmRWYMCvMZVULd6YtEqlR7pNwnNmNHl4JrSf0j0gxBTRdrS/UOlKrP9OC"
|
|
165
|
+
"g1DSE4lRRSNXxA4dw1l1DZfD2CTjzW8wOkfbt6pLZmYJ9QxNaqZsWrN7FZO1mVuYyx7QeGTgcIqMoGMUjMjuR7i2DXKt7IUFxHRZyPQy9YYDGu1gFHFMqCH5"
|
|
166
|
+
"bZpzWkX2bcClZ2hBwWobX2lmtJW7nHjlZBn06BVewQqnfhXjg14HiewTFzt36nIcOJ2hrtpAEqIMB2bR6Ymq53rJmpnwCgxQlu33/RaiwiyuqvxzT+1E++pw"
|
|
167
|
+
"Xi56+8M9Psj4PUQbGKA0jNSMX8J+o4LxLv1GG8B+v9H9fqP7NSK0WTma0jzh+jxYLS55+7K4HVXQEI+EhvH8ZfIElFVo2+FDA8A6GIqL9V6T6rCBrRnY4rWU"
|
|
168
|
+
"8NNv74EPBXpJBm3x+wEfGog90dfOhRm0b5YnHT7U3lg0OZjK7rT8QrDcJ2YpQi8Aq+klMnZx3GkdvygTY89ho+aE1NqkKdHaoHOwEEergX+WEgoRI43p/nBP"
|
|
169
|
+
"xaNWcJfu6ykUYwLzvL57OW5aPKNv5w5vgtMQvIMhwk7tKt/gibEIqKHfoNz52cErJZGjLSBAaVEZngcDXIZFumfHPUEGzbmvO+cFzDtpYgwMRtci50MNeZre"
|
|
170
|
+
"Quk70zDUE5Izajt1GXs21UvFlnTnBQYUqXbOkvqjLcrFVNcaGZEqRk5aLmelkgvosOyIHyzbR8SNFjmBD9wDkIO8c4Nfph87RRBIVEJeACuO6Fmr4ELAwySe"
|
|
171
|
+
"lwC14b1DaLWipVnWzSOrFZoAeZn9dzVHDQrNaLnnoskuRqdF5smmFSqsl5Vg89CkhV7hwvQn+TmjCqUA/CrEZF8TwNTfqikQzboqAS1ixBUGfIoQSoFwt3VV"
|
|
172
|
+
"Bhv2WqewlPvdRghbJCLE++lrCSIGHEoKe87eGKgvoBNQYAhhNtBfUJnf73ntBAoMKrf3iuQSVF4Irq3VGZ3rgstE1wXJIlTAGkLAZ5NGZHttyHaakLgWxJXu"
|
|
173
|
+
"9oYAB00OAc2niLDqALHGkp360pd60BZFynadRSt3dtepjeFOXwXyq+yzo4UB6nO45+d7pmcxv0ZLrql/3xMFTQE1ydjSft/RuKuCjx49+nfjqtQjy3E2PyWz"
|
|
174
|
+
"axLADtlkjBgji5Z4D0eaCDRjzSLPtV2sUN2gCZUto82w4be29c5ezyowS/vwX28VR35dLRbke9Vjz66hfeQYfhefheF38XmcNTfLgr5pW2V4uFIMDkx5BZ93"
|
|
175
|
+
"zJUEiqytN7e9RaumnWm5yGczr2GyDVfjI7vr0Lj9fXGpxDANj0LBm1O0yj8+eg1qqA/Z67cnH46yk+Ps6H+/OVVTD9P1SGDBMX9fVVVDUyAt2AEGeKNqo6kk"
|
|
176
|
+
"fAf7y7qafdKaIKfgtvbKUVwaY2DTHFgE/15atLcbfxEdsWWwgUnbMNYZD8MO6VmSmrepW8uo5+PV7GuvXvFZUUEJ99nReva1Vw/IYb2Md8bvbA1ty4dyZWSQ"
|
|
177
|
+
"g+gYBlEIB2H/fR+lzdUukxqdWGdyyQ7Rm9fNcxuf38lFpg2GJdJWvDaGxAg8dCtUlZ+KaEV6NQR7Tq8Su56AgXu0ppmFTJT02phV44+qnvElHb5/q57460jU"
|
|
178
|
+
"151kYH8vG/br0GKeXPjPlQTWKETky+yCHRmBm8Br5AFBs1AJtfdo2OocUKJYGCIZOKJwKwUwq8SWh+ChdlV87sVb7XDD0GesB9EOoVaR5Jr8g3svnrdPAoEk"
|
|
179
|
+
"XCPUM/AO6LW6OERR4HI3v33H92HziJzRMGJVU2Jqx7T12RkN90TbJBQGWuMisq5eYuSBrESBESimgNCr1c2obQfuwAM0rlbNdcZtiH7fV9f++MH/tgUH2JDe"
|
|
180
|
+
"IR1CA8WQjx0oLdCj14tBj7W5gg94C/DK0sjoJ3mNrUSgKD4X43WDfrjLphfQXpqmwbOf3x/+9O4w+ZuSddTuj+fn0f86fPtvQcHX748OT48S9LpL3vyYHJ+c"
|
|
181
|
+
"JmpH/3D6AVX88zwDtVWPharkzfHp0U9H77HY8S9v3/bDFt8cfzh6fwolT2JN9JMPR2+VEJHsJf/rT0fvj0SPPX6zn/z4/uSdrBzppgNw7qkOUQUfPZTTo/99"
|
|
182
|
+
"qvD05t3h+78k/3n0l4ErP9J7Pc4B+8C5D6PtC/nSKz7PF+VUHZfRoN97R8fOWXW5TRf2VJgoLLw1pZMfjn48/OWtwu3AHgpbi0Sbnpefi01twxEB7mTyxi0y"
|
|
183
|
+
"4PN3+MZWDjrdbWpX67ZpVW+ychLOKt5pbDGXfDmRfSwXk23mQJ2U1vOLdoKxRhjR9wYjjx/H26f7yhipCCkrfGvbvb17DBOyrGrFADYVjKMUXaQ31sUza/vo"
|
|
184
|
+
"Wqkl2ifdykZKG03Ar0pcGd7ntJAY3fUoItNcUNBZcvjL6cmbY9X6u6Pj04GkRjOEbUmLPSJjc2/uuyPshcCLvGibgQeia1IsZ9XNvB1ftkB0ZcbXjRlh/HWc"
|
|
185
|
+
"5cYWNKqYMrVUxnDL7W9cA7WtrMpxHUNyfD22kXEbxT4Qu4uqKdUKV2enOHLxLiYgsYlC3rgIn7NC1Xu6A4oVHIvqelZMLmM4ENPaY8AsKLpz3V1/G8z88vMP"
|
|
186
|
+
"gBkhAyi54VRv3qP9sIYvBsmTCMjDrIrQm/ABKyylxxl8tM/uPB8PF8V1IE8P2EFdtxN43w08f1/P19fApBuw04sAml9Sj2WfkpDiPfRVWvbNE/t1bkIQePW8"
|
|
187
|
+
"h3GnW8cCiwp7dljwgWOgo+jFieFJtBFg9j2LsvsEicGKtjx/803VHJEO7AXxgW8iZ9GniohfvtEdWf7Cn2gLprI/PMM6VAkKhiBM5OQp2HOv1SgFG3Kw3UQr"
|
|
188
|
+
"olY/0EduFaDZHCK00C0LnjZwHcDrwP8RHlrijBzs7UlPrhZ7kgHtw2C7Qw2e6sHfd+Scb3opy/+aLeEpQAvzycn7HxT//v4vkhPjhVmyr87t06IZX1UL/7Cm"
|
|
189
|
+
"cKp7Cw/tfAXta21atA7648Rc8OsOkltBlHC1pbs/24O7H2siCuvoLmSFIiyPrOmUw+nWRovRmDVueXkiGbW7JruV9CSMkim7bD/d29vbf6oJ4umtaPbs4Jvz"
|
|
190
|
+
"O5f9NvmKLmZjmtJnuvlIlW59IHp1eeiIuB87fsfhPPYYumcU1EGqX7RjULvuBT6aeZuVFXJzD5HhtipNxyNTFb7EU3yqJ6C1tiqil2+kDGJmhP+6L90BOlRu"
|
|
191
|
+
"sJXqYZIO0+6mbmVR4fWfDo9/Onp78tNwPkkdRKe/NeHPwNP8zaJsynymZhq3gmKSUJiapxwvAOT/vCkxLNhNMstvitXwrwBC90Q5DCacAqmsMGoDPWPu7sHH"
|
|
192
|
+
"xoGcIudEb8/wQuKWZ+J+8ufDt78cfej9ceD8149MkwECyERrjtOBQyC+JKLK3QeLqlXelPhvf2vC6GZ/dghdXI85nmYIZg/iZgHrYvdx5Q18CTzNhuuJgCQ6"
|
|
193
|
+
"tFwNrT6hOt1SaqilwY3cmWGj0BJZC59sV4l7Q92AIwGVvl33bt8jJZz7d8RygEnuqmUnFxuPRCGhv39mQD4XMiwMDw5ZZuWQiC0lVzAWVUjVBmdoB6ijXT1H"
|
|
194
|
+
"KMDc68yG58BFy6bJ7EbHGEavUguZHo7Q1WPrcctEBwDHBlFMohJ7DTRgUGbNzLbX2F5fQdAVPQKwq5oVCz2Gup98y1D+LtkP9yVdbJgvlwVEwWOb91BMqK7b"
|
|
195
|
+
"pCdPjekKUaRy1aeoP6qZ0X0M+q1CVDglqnsllaCDqgIEaU6uF3dJ15ZgVOtV7VLiQJ9kdUA0tlDhByGh6jUfoVJLQNqWJbJS/FUt1hUVCQIe+csD2nQWhOiW"
|
|
196
|
+
"dH464pqwsuhkMFpL4ZLunrbRlcYaVBIsM/oBGfOILBggGXHT346SvXsM6WI9/ohiHNl+OYGspuktz93dP2/tvKkfBoK7NAhOI4/DIDcOkv2v+8lXYAEU3KqY"
|
|
197
|
+
"gaiRMSTfmhEh2cWht6yP5c4IZ0LKihmW4Creer13rMQnmxef4G/9lhXIk6r62TJglLa5VrWyKQTTFFGXNFeAFvW7l3svBSWKy+dAaNdLP0YoQiL2gufF5Ea5"
|
|
198
|
+
"J5uDKq8aU5YcaM0xtmtrtm2w8zLqb1DaWRUZk5spNJDHYSM++Su+Db/OVTy2l2mv8Uh4KwuZ8C4HpP8hxF1waNe/gpKZPgMaZLGHOqgJfHEhWiZmsMcIdTtx"
|
|
199
|
+
"sbIhJJspBubJxSwXYhhcW4BESudQdUZzu2k/rOl29N4jm/XJQYKpDSmYBnW9NjWA6YYpyDQxLGsEsU0hsJlQYMRdNIJx2mwcNJ88zELRJqwWtA5Thtg4t4t2"
|
|
200
|
+
"9psR6x/khBGif1UMEGG0rhI5t/WZni5cJzgQX/HTrujQI0ReJrV/5zHye/DgCIBNw4OF6PYuIv5ZMhZB+YIBO7MrahOZ0OP+/+vRM9VuGD3v+O5+3ItsOAML"
|
|
201
|
+
"u6fdhXesB4qPTr7As7x65pxm6sY/yqAZrD6voEBWTad1YR7tRQ6l28sOTdWos/qIHbEiEsTrk1+OT3tP+q4g4ag7A+2gGh96ypArdHXdZ1+Ra+C5G9QhrgJ2"
|
|
202
|
+
"CwWIVXlYmwRhezAQ5gQtSpEttLw/HH14zarePyYnP/4IF0F/jOlM2PwYhOdXWngmV4V+f6BFazROxknsB5oOwms+m/X65z5pmlM7WJkDkjFEtppA9RP/WjXN"
|
|
203
|
+
"QexwIg7k1isOoiW3yaaeqkNKZEZmvLfASg7v95RaNUE4AqtLheJy2BAj+lw5PlnbkSNDJJv0gGolGwfabeYamIMWTFAGEzGc1UuSSR21H/C82zuWTOGL25iA"
|
|
204
|
+
"GtsTv92CtKxqe1vDD8K5FsWxtFv9TOwQARGropYMqUob+UW1QzG43A691mM6odbQrPAx+8ZoE8nHTxy6/HCbo8evL04DFDWKKBgPFZ2d4MrBMbR2weiHMjg1"
|
|
205
|
+
"2zmzHM82GD9dcHTIiQ4kCK7gVJNyOt2OP+mUMqoCJNkROjngcXo2Pc4XzCntOCzpUyHWe7h7ffQsyAkk9Fw6xEr1EbXchz9TlE7C3K2JvvXSijlCxcq1EsP4"
|
|
206
|
+
"ar342K5klBOh6UW1K4fVT/6JofUEsP65lYozCmVdBJIEJOlUupyVDUYB8rR+2AU3I7rbsRUasuLxDegzmTqGa0Xc4JaI5BXwfQI63N8RisiNlhImALTRNL3l"
|
|
207
|
+
"uVVQPX42KT49W6xns8d3zyhucURkaCpdkymroywMTwEwH/kXd/1AjPUMIL6MmQIgCyJOmajMhFvHlsAsXPJRUJNmdGjaFWe9sMpWaU0irUEUlbn8GWPyWAuS"
|
|
208
|
+
"xMh9ui5GcWuzqdGLqi3EPfUs9Pior9nurnp7wacGZ9XFuPgCAjdo7yN3Suam/Fb3dbD3cnL39FYn2hrC/ZqO+TJcN2N0OJhi/ovHX/3lq/lXk6/+9NW7rz48"
|
|
209
|
+
"7kMtRIN3tw5mbWQ7EXUmeqYNYcMrdq650/26vVtHncNWF+w9DWLkdl200nVzu+X1enQ1xdYOYdJ9hrfpoS0P36FPUxM66OCWUOrzhOhNuh2De1FqULLFFbos"
|
|
210
|
+
"3X5/jhcMxmoc+PBroy7nMziZJg7TbleS3Qw1RFoMBnPATdxnTf7PXM/vclZtu7TffHHPhCg8+tKBpsTw6j6cztT4ihN35dAdCna8/Ym+heG0v8UR6tcb7vrb"
|
|
211
|
+
"ruUv1MayyOhKKiIZDvT1jNaQvIrIitvL+HSE3nDbpw2lMnPtx452XXfosSLBJbrUivu9wNWuNNPKNt32gd7NSOdRW4JWZRs1nSnEL1cVOvGjUciiaq6KVeLd"
|
|
212
|
+
"7ZR1ks9A4MeoLa+1E7+jbnOvcyUuAsg33LmX02BQIkZX/MaxYy5dm/FRwufnLFVbrp9gyFRaoEqDCFpud3gRK60t9F23DNUoXvsDGSTmvvWAgm7QL7C0cQBV"
|
|
213
|
+
"r53fA4xwsDJGnwqyu7uWnSGwV0HAH8xMU7agNnoc0JoRjxw9ptE/jul2Ahx4ip0dOLZ0EHDRY1i4Z+xvmLi03XdM9aUW0Vre72RZ5c1UOOkBlQz8yb9VsgBM"
|
|
214
|
+
"Kv6ziYXifIor6QvIZOUwUEjVWOS1NJnpMHbqWj7GK2lHC6cug4l7GB/517NUMMoMbtPqow5urFjaBZ57Tfh+eXYyY7uTYN/P4MJnM35ld3VHm4gyHWdpe6cP"
|
|
215
|
+
"b507b2/DlRQZeMSG1LGsi7yH5BTggI+cDk7KwAU11lCD7XQAzu1AiGy8TD9w5HrX4wmCVWBYm9vvXUwa3p6LKZIxAIb3UdvzOErLkcHgA0aXHB7/4DNDtIUy"
|
|
216
|
+
"mPG4XtB19B5UwwNeiq2wkL/c6I+Cfakf0r1NwUfwBj6EbZBHoeltnEHNwQa2p37YVmBR7yyODVPUiSep0W9Fl0QT48UBYBS7CsLx48icsp2ji+tuLHdyn1tO"
|
|
217
|
+
"FXm5ce2mYohZsCy9otuuSA9AXp1R9RBfwG2n2t1eXH+obd4X22pQcN1iQ4jJ5PBqs9ld8q2bEgEpaKP8rpe5uCjn3CT0PFECPbT7FcvtqLQptE5XpK91xXkt"
|
|
218
|
+
"R3moN+L6DptWt0l3REJus+3+NeTWfDW+UoBNNEM3zxHex8gcueK33+0uznb3TX3Iu2vDkBwJGtmO3+823baxQ93jY1cuHoE9QoQ7btzldgYwFCvaNjoL6q+5"
|
|
219
|
+
"u/XsxhUMo00YpyhExkxfxkR6FrTiMxUq3uIYVl+tm3I2HFfLm2ZVFO4N3IDrRpwApNzbfQb1TkmU9wAfDXROa3nSFLcALP0xnyc9nRMhje3zv9wJxNUstBsW"
|
|
220
|
+
"b8vpA+EQ5sMdiGb27tN+qxpCzAKNRJ7ICA0jsc/O88U6n2mGbARqz8fu3g1p5q4HE+kgOmrP7WBrboujD42W44LaQ30KQnGrzcnAm9Kt/Ay2wEvrlqzRnK0X"
|
|
221
|
+
"+ae8nEHcUUx2ojZg1jryBn2l0Lio9LWxlSKcHbhVRvKI8v/ZTjxoOfsZ3YYRK+mLPM/9yrt418nM9Uj5/26r3nRy8RVwJ+8jAGmvn4fsGbHzxTZTT3vHvwOc"
|
|
222
|
+
"5XheNFfVxOwm2fIPrzhKN9sw4BXDuZ+dTCzNWNBoHsze0Pp7VKtJscKoxWz3wIFLB07iLzJVMbk5ZMYV7HIxKT6DDU+56IHPFTcKCQj3Hes/9+0TBckfXmGp"
|
|
223
|
+
"fohyDvdPhc+wC+lfUl3UxQp2QfZ+oZA5u4V0EDEaONbsAcY2to8n61WOSUw4A9MBgWULoEqBaikEeJkv+fCxrGbl+CaWUKc1nMP/qKuVS+yMaKMZ/AI6wPvq"
|
|
224
|
+
"St0tyjk0coPOwKXLDWQdENo3q9F8EAa+gDLzXnEcNrs+hSabgerG18e3uyFaRygt8Lmm3JgnUu+BbQLftjiFD987uEagpl95K0EJNtH604XZuM9x6WFdNBx7"
|
|
225
|
+
"3V4U36YyzPXewISr5l+4uvUPzQsw1vX5Xay7M9HeOVu70BudOES/FeoMadEim9KgxBrS75xmsBCEZue3UYyc6UHFmuVX8VbxpW9srHEC9nTEsUNbY9mDReJA"
|
|
226
|
+
"IdFXO0ENL1/m+dnTl3/4w8F5vFvtruvtWj7bNvHd4xixQKGdsP7llI1KjfDZSkqRJOspoNuXJ1sqUFWtNHa1qhEP2MwuHk36gHnB/KQdrg2mF68mLihkNdvb"
|
|
227
|
+
"uFovtJdqAEM71UcAkC0FYG3T0jQAC1xvnf1BPN+N8fMThgZiEdG3yG6Y8fIDszQd8j2GlsgafuaPIIImv/UWVEVb9/AQgZ04wCbQQz6xDeRe2y2Ax9reDLcS"
|
|
228
|
+
"kI3JGgjLLYB77CdGhn5LLWC2tkTSHmzPUvrzGJ3zztGst+fPsxkdhKis80pQfw7xfzdKXlFiaI/6zQtX9RAS8LcRsnvKs0egb52owelJPQwo7ruQUH4X6Wqr"
|
|
229
|
+
"NA1+Z6GKxZ3u7yIBLAE/Lnl959V6kkBehyiUuyR2aNGaCl6LvtRljZPs3ZEFcyZvs/Qz73oqmFN5WdZSx5suTLjmdEVT1tqTU8uf6Na+AI069e2BOxutPbl1"
|
|
230
|
+
"nOctod/sIvKFbFYTjtp1jJj9Zg4ndn0cE0vSJqSLxt3ZtNfkGHEb7M6FSgEOEZYgACvmh6t30l/tUOlCa9Rxw0ZLSKwfShYDsh1uyoFk5zABSiwzcBLLkIBn"
|
|
231
|
+
"6sIpzL69Ow874i+QD0w045bLtL6BCg/xJ9rEFn3KMgYDLRe6MeSrdoUHGW5gfEJJgaC2jzNMkDOQCXKES5uTIOd+nVBunYHMreP4R/59Xa4UZWjRKYDtTCDs"
|
|
232
|
+
"3OCGEfgtBsgJ6vR9hJlu9EAc6HboAssHzRezfFnL1rUs3yNp22w5Tm4ga+QnArIoUeHF13sg6XdJhWr/8zAHzN4FQ5bBJ8FtiBymKg3jJHpDhVYkUQGrRDU7"
|
|
233
|
+
"0YYA1gBk55NzwCh0H91cwl5mHxio3PP3ovjcZJpf0KjkNMOR9dyXHdTUnBlegvnlTCPRomIGz+Mnqx108tKOyijj6Yt3UN/tumZbFbS4G972dGfRY6PAtR3u"
|
|
234
|
+
"7kUM+eQTyJpIDNaYVnbbRiN3j+7dKQSULRfropsCI8coo9il8MvRCFUmsLH2faLwxq71DGtYRVJMqzXstNDYnjJkSOaNt3mpm1vBRpdm0uAh8hW8GaK+kqch"
|
|
235
|
+
"8i9xYxFezYN+jBtjrwbTGP/mxvrG7ijuhR8qADv0jS0+IdblQzp3tEcf2NbXXfJ1Zxo4dgzrKDEyUVu0Cm2BpP7QxkKlxG3+ubY0atMTOPZP2yjr7+eeaLCo"
|
|
236
|
+
"bdAAUPtQj8SPiQy4Tg/k4MQk6MH5npCOMUM3XkKBvTV+YsT4jgVUUdhevLcbJ9gygSFa1M5OEEd2T5ay0TzvS3tbnbxP3h/9/Pbw9RGZ8YtI9Btjuw+8OPGh"
|
|
237
|
+
"YX7UJH8XpuGETNvSi6nt4lS2JWaNfAyy1VqHKCTlB92dDRKZKqQtXgI5YY4g0Xt1D+cV527qC+0XO3huYJoVGgMmUNF+GXLkgyADykDkLPkCfhrcPyP/jMKv"
|
|
238
|
+
"n6v5+/u6WKNAgUY/AqT+QIpgJLFR7f4W7hrwIURiJg10ENb+0rLXWx0IPgAswIgnEzKsEDBGQH0XkCv1aemRECjo0XHiFrkrB8mTJ9OymMEV8eHiJpJ6Kp9B"
|
|
239
|
+
"ivQJ+kUFBnggLnnZZVKS1G0WmRTtB0ximFTfT2lRS5v5pSKyQg2B7EwsGL6xw+01lVKqUCk6VgZqJCa/N9U1C98NnoDpGYx9ACEi7qOsGDxkaeM7WUZKVPBG"
|
|
240
|
+
"OdKVu+xw9MXPNL1Vzd2pIbjkRMPQpYggOeUyQTBUz2tYwr2U0NnHU2AsM/qAOA3uN9TGo3g3RBwP5hDTwMjz9vHgMUUeECjo37HURN3irGrLDEPAtKAitGtY"
|
|
241
|
+
"6EBnvRExBO6bNK19ESPf0j3JZBwSQFlldzCj7FzYZ0fFV5G6BwVSm4nIRjYy2YdEOKyWzAduliCTYhufsT2dgSgwqpur9bgqMS4ZEextSrgAdZ5mhoAQ9Zvw"
|
|
242
|
+
"mTJOMHAFfpNGq5xWw/k98JJigOWR8Oe12Bh5yVR6GrrOsLJCad225fnbnU78xCPEgck8TG7mJZFraYOruL+z9VxS1FFSiSAHPpZEN97W5S+uOlPswkiXFK9F"
|
|
243
|
+
"U4wJVufFs3u+97DwdV8g0pwGcfAQvAdrhhiSbvu7P0YWEYeTa4six/pRF4+s3hskIs7c/p4TaA6KAFq3DC0n8AXIDNB7ZpZWWygyiauOSGRaDCYE2QB2s7xu"
|
|
244
|
+
"zPjYyRlenT3dPz9LzQttgsvsIoYWaXW+9kLbCV76UILbYHCEe1WwH+lFt41t0Q4hMKFVJ8iyuS2FzttjLMOHKfWhofHM+gkXzi5LReMqXCgSe1sQdLVudHBB"
|
|
245
|
+
"YAi+dIZZpVNxTLiPxOmJcpCFXjV9dvD01bm3SiAHPawSlAtblgZAhoMF2AgpB+FMtS5EfLvLUgRw9TKEtuirv1JVKS8wKR7J/gWDkgJcv3pA0nscRf2zDbnd"
|
|
246
|
+
"xo+lbnJJy0j+lYORIt7jgUgVK340Vpy9dpjW+/UC4kwdWKGX0hRnmklzVvMDtxY/FYuOEpPzcwg/yV/dAttlI1czuvYCivNpT4TNEyUztMSGaBvqOK5D6OGP"
|
|
247
|
+
"eMtk7qDKfCOWk1qkbZlKcOUoCOxoo34p0vFIYqMjToprYRucRr1uyoWLn7ZLOVnozG3DvRrz/VMsxJscVeDDyFzkc9Awp+NZtZ7UGaUW0jHaQMm1KrI6nxad"
|
|
248
|
+
"bdXLYgzsBeNFYjBA8J6Dp+ioQIntFI7QrilkCQKUSIRAP/inDZgZyZa1vuDG6gIcS02n9QhVDU5j/fMutRFcxMKwyprs6oEu1G/cI4qVfryDaME4xf1kqgQp"
|
|
249
|
+
"uj9bL8BLSS1ypGHtn0SDAMMJmeHd4ipENg8b0Q1w9uAfb5ZuakNXAuWw3Oinu2f4wT30RDMGkMNnVLFHfzxxqDMfvQcQbreSDhC7kYtrJ1M7zxNwXEWbeQOZ"
|
|
250
|
+
"JrANhdiWbJPS/O83o7aclF8O1g5KMCky52WtzsDjK+FBrt/JCFDkag6pvpA0gkD6XYwjPscBF9b6pq7FTtmQ0CrBr95Pvgs5dQSXakMrJPsNwFAYnhXTJpK0"
|
|
251
|
+
"iOwjZPVRp7Og/kTVfhYYs6wkEnGind7appoKMS8FRqPJUbauZjfDMlkW99gIxgiF48MBcQvX4YR6VZvLGX6BF/qJoFk8DSF4IwksFx6iyQQpLcXb3yXpMO2f"
|
|
252
|
+
"xyHAYXuLQkAUwxRvcEyIZvNWuwOcPZuqmmlxGL+zqhjECApWGnFyXlWzQv6GaS4+NzGXKJuZDFu0o3LnHkWJgPDHuTHZCziNhD/1x02nHDSiZb9tAKGHhdEY"
|
|
253
|
+
"DXIYwChG8I8ZwEgHc4cfUO72rt8nk6MZ+rX2dHusPg5aDS4gCBD3GpQeDrB2a0P25nN9iRYkcHG71KpV+qGvKr7IJDnXbg+dHRfoLeaHNKtQ2IRs/ALzE7Tq"
|
|
254
|
+
"zw9FgkFgIECrU9TMwFVVacd/5A1dWGd7b7w10l6ByZMnH68VtxR3SY5k/EBkwxgArn6IZU7+xTjWYHSijAegTj4U/ZjkULyIOEiqi78Vflxi7iU1sZSxdUj2"
|
|
255
|
+
"k88W63mP202VXAtsckxRuUH2x/slQnbfnLQ+NKv1GFI1TX6G3fnPkOMkbyru7MeT99+/+eGHo+Ps+PDd0QfHgjotVIukBynG+hatJGd0dURDyY02gUptt/D3"
|
|
256
|
+
"Qh2pPi4V1ECh6eWsusiRmaQgwMI327aScRV1sAtGWlfgaYR9NDdLNJNN5wprJZeh3EPpejVToiKpXdTJqm6oMoa5QO2MkoexAPbT6TLMN2sc51leo5PT8Kr6"
|
|
257
|
+
"R7FQp18vASRPzo+/vH2bHf50dHyqhK73R5TbmHa8FTfGhqpwA4vbJEZ9SA8vyfz5U5HSNKLMdvL27dEPTp7kTm9nsnCZZBTWnKI08CJCBajVZ5aLLQLha72K"
|
|
258
|
+
"NTkFumryS8VRiylaw60a/l7A3cCqvLwyj+lHgX4Uuu3hB44y/Q5Irli5h6V8RCA7sdkHycUIgY9HbO8DOrNqCVcrsTtTBS16Wap+Qd0Q7PU0xt+R9aweSfLU"
|
|
259
|
+
"GaAdyVM5xGD9Y1viiu4TLakiy2uznWDKAz94+cClugOPyiJ3dhC2Rc1MDjnn8lVdmCTQ0ADwqBEeAU153b6Wl9RyVqv+itiZko+KuSNaOeDcOdO/UIjGIqrr"
|
|
260
|
+
"63z2sQeghHgXuzDUGGD5N0hou+RsUj1elJNJsWC2ksrgxEy4dZIvIH2wqqemckVKI33GDE69HYD9qHalCHCuKgE3AVWRJcMOIdepNwLWOF1j7j3FoMm9qKGW"
|
|
261
|
+
"ZsUndSaiQ9sosJzTn1YRn4+IQVOK7Pfhq4TDNR/Q5LBjjrTtJqVaN3U5ofhgpHdJ8kvQWqMx3eZ56cHE/IRbBU2SWgG4XfjpI7aEtR6r3cgFdZ6TQcV8jZaI"
|
|
262
|
+
"hoaQzKmzhKptCe6P6wVa9P5QTAnmw/pmMRZP+9paQs3VpFCYgB03A6H0XmMyTbjjmnKHtRnhGgUOLlxvuSoOlfBTXqybom9CRyPgIBSZNBr2aKUIPJZub4th"
|
|
263
|
+
"5Lqj+PTk5EE3WS9ANWUKbzuM14raaQTe6yHgiWkL+KCdGngxLCdGqelJQ/caJIh/anxTMUB4VCemiJm35FZCcSdF5Hy5nOkgvN3pL/SNlLYBDESZZr2cFWd+"
|
|
264
|
+
"Ne/0uISNWrE93ZrObQP6HN/qNFqmLbOMzrCBxbqSF4UFnAOdyEbDV6KRHKCt01OoY8hNttTqKTs1eiwKejJuAvabA+sEPLHgnPAYTV8X6wllJtCGdvQgdWAE"
|
|
265
|
+
"3RKnxvmOIwlAIcji9Zl23CCvYSv8CACczbMZaIncMZA4WIO6AXI23CSMP3vWMZHPts1z4ggGxtIzIjLbKk21VqIe3KUXJoUQJAQSYEixNZA39fVyZFp16l/F"
|
|
266
|
+
"mKCUobsrVkDFgiQIaiT+tSqXvb7JEJH+9a9Q+VnIWzwLN9mazoLk7a4RiDsnE7ahC1Duk+Vw9qmsZjkHzxTTqhggkK0iv6aCzb2cQmh/te9yfcRUCL/O06Tn"
|
|
267
|
+
"Y3u4JopRlGN0IEVym/JZT1AaLQ613+DZDMSiZaEjXMAkggokAhbDMswnk54rsMLHtBHMsXlDRz6cOlY4ml9OU06KKSb7aF4oH2sChpFpnDAQYpDvZwNwUZFC"
|
|
268
|
+
"Z994R8WsratIL9Us7AGcZTuVroviOqiknm3U1AJJQ4cwh7OJpnA+Wu4mNhLxsFIps5mDU5+hai1Uco1x7LRNSgifxTcfHPUqVqAOYMiDZN9HtJ+ythNkTv3K"
|
|
269
|
+
"dO9Q3XpRr5dLjP0V7Ah2Kj2gXU73Ox0uIXZuH+jReS0Q7Z4B3QIn5UJyf3E7CbcY5/0X2mrgNqyY1Cjsc/tPoX3eEQUa1gudpdnGToPtQIeQU2fsqBLF8WCy"
|
|
270
|
+
"jWwJfCdr5UspTnpseJmcYdo5B5xkrx7dsixyIEAR9qCtSag064maeBMxOFqDoJHBJuyYq7URpyb+vliMr+b56uMQbFJokL0YTbnXxWe3KdrXpbrBlPzkwY0g"
|
|
271
|
+
"ncAxE4g8RR+ChlwkwU0FNiM+7Xl3fVBUnaqApSX7d97lNBzZ1YFRRyAYoSkK+DKTxaKNUET0RPzLq5SCzzvysv09xzYliOqGYZk0XMxAIUIApRLbckEEXCR2"
|
|
272
|
+
"8UksLiwqzmp4V44Lp6yBKNUPQuBT/7JUbZUQyCBozRIladjJ0QPr3G1wMzIRFv34GA6POHB5iuebplcCyLW8oQdOcpCkE7yXaL23lPIWKXs7OVpTrwSRlNrs"
|
|
273
|
+
"3ZSCqFEHdboi91IJJIu65xmYBW6FKdBEymEMIXPhGNBIJ6e0WKznBQfhMN/hagxIUnv90Uogi1Shz7YiI7wyP0D3XSwIa/BdHWhSuhEEDXf+GVx888/wvYRS"
|
|
274
|
+
"6l/R5gomA0yp4C9soEWDGZwb1JhzkH/6QttrymrGtF5DXE31L1hgwfkPLLDgr2j9H+VSPVX/qjLGvEE9Md/V8z/DlQKuA/XC/kAnZ7i5SPUVhmg3w9mYZHj9"
|
|
275
|
+
"kGWqiPdE3iAfAE7QWEfkItPae77B8Phbu50Y25HFjMsCKzFWH450rS6Fu2CorLiNH8XHeV14970e72IaAuvIV8OYNSSbaqIXlF5wbYc2Olzi/lzTyoRLT/zB"
|
|
276
|
+
"l57CUX29WGDS1NXjx48f8U2Aa3yjn4KVqP7OFyr6Z31T669wNgEAHj2yIMtghDe1OnNNygUm+O0Jc1M0N9O1h6cFNJevbn4oV4qMqtUN+INMy8+j1OGOTy/0"
|
|
277
|
+
"9D9N0TwUWuA1DpHSRxrUIeq74S0xQbToLEAI+FRoVQrdVIwb7d7gqiXoGjTcuns6jSW3FuQ85Mb1dWs8saG2VNObgGurRtk0dzNEIzsFBk7alW20I4M6fb5j"
|
|
278
|
+
"39VQjLZLXl0sfVkrMEXsJCyLIKjLfPwxv9QntntYgEnLr4jJ1zZWXTpmCXr7eRYP4R17ykmK1eKRv7RRPTkbQHWOrluzAogFAugErrqRBvELeFNoCExWEL42"
|
|
279
|
+
"9m+hoQJfQLOV7cNsxETIFYZPm0m1NTDa57nSdtIExlLxZMTgnllgwHNgcMiAnEVFrKnP/EhcbKFphmrTEQ1uU7j9hU1Bq0gBz+Y7RAuHiceYT5A3HM/nd57w"
|
|
280
|
+
"s0MjF+AB9oD6xAACKKwaMDA/tBRliHdon2lTjBGgLabZIOILq8LTjspE6YWo6Fgr4b9sH0IhemsbVaAwegNrNhL2wJYepn3PyCb9/vDDUfLz+5N3P5+mD+tJ"
|
|
281
|
+
"Z//WHQWLsHfmOtVHKOa8q4MKbEJCsR8vkGjhBO+8Swc7oaw53LYCTOOWVfSUspJ8U3GaB/LUjZbFsxKV2tgYpTuPdBy6V2gew6dMd87VIzvjNGcIRvWx70Ul"
|
|
282
|
+
"Yp7zu5ENt6sKCcdMz0oXJAT1LPQK3BUeneyvYBmY8qqO+2cHz1/u6SDH2oCCHSvggLSJEzLsyxWMB+WmyXq+rHtMuoLYEPma5462a3xgg5zsQ7iv5Ilu4RkB"
|
|
283
|
+
"iwcOOrUe6I7w3H+tXTfuFIQgKmrkBZxMhoezxjwgWIdr5wy2bvKlgb1O9fT0DTDVpx/wXzAuIgH1PDz1lovluhl57qhBKVzLYb4zBDRf4nW8EsOhpXghltNH"
|
|
284
|
+
"FFztlVZDeOI7Kh1AcI9AUCw+qZP5z385/dPJ8ZuTo+PXJz+8Of4JGA+Jf2qg9PL45JcPsMGeHsHL/bT17M5ELZB7SuAcfV6CCNEVoVfTrSYDVK0YKjbSZ2LE"
|
|
285
|
+
"asTABG7zU0sIZ+dOJEgz5UPqDIxxQLRoiRUcqkMEaOFLCWr4VoPes0Co84V6itFS5TPw9INFHBkjOyz0z54+39vbO4hQmx25lzpSaFTY1FKcdvz+9XVTq79r"
|
|
286
|
+
"pxmrvEjtmk0xSV2HV8U/lCyNHKKH4fxIdaHOwQU5YLeaFK9QhqUGhu/xTw9qieFUdD/Yc1WF4BldXNP34vNypqDG7/N8kUOYERlOpmF3U88LXmEF1K1JnqAa"
|
|
287
|
+
"WmFsWdUlHBBR4zO74SuhvFypMjCxwCubokb6rddzNeHqxJwoSCZgiaavhopJ2YTKujT/VJUT6KTIG2S6KKEmfJkPMQMwphtGR+LTTV4uyP5wWi7K+iqpFrMb"
|
|
288
|
+
"lk5QQVqOWfPs9nSVo/o5XyQivw/2RiaWZP2jAMA7tqd1UyyTJq8/0njHlWob7MbmareZqRHBogUjw1lC/C2N6UOZ6nxhG7fAKW4eT2/LCcYtvBP6XzX/w/FV"
|
|
289
|
+
"BbGhcKL7UgssXvIk9oXmF96qnQlo56JUMtmL5303hCJe+aou4UgE6i30SvxmwM6da0gyY+E/t4YRE7XMVhAws1ak3pbORITr7bKdsMp0GxJ4y/K+ogdfxRKV"
|
|
290
|
+
"YJJ76UVImWlcP0LW7Mpi0LZXCr0l8otiJvUYvZ4N7pMOgpGrOemlQrUWDDVQg2v2ptVkWvOl7yVw7J7LDg7yDGE7lxFKOQ6BVetT6xyiFJkZxqbWoan9BASM"
|
|
291
|
+
"F9sySj6yEeSBlCoh7jBwS/2gVhq/gNaXmgXFL31jHRpFO2UVN40z7rH6GtTu1eznWb6Iua22UCWoWw6Sn50UyyLFjvA2V/s9sK3VQfKa1QNnZ6SjNrSBJhba"
|
|
292
|
+
"jieWNQc+yBgybZAuW9NEzf+oVbeprb/BjUy0LQHZFu0QTGoiqtmncISbarOH/idjxW7rD4fDjsqBApi0hqgtRD3YkEHy3XWFP3HM/bhnWvPrGQ1zRB3dc5r2"
|
|
293
|
+
"aprBqbpt1vs+lFbGCe4DexIcrxqjUxvYGMQ8SzgWwhBjBkQrbedHDRwkKBkryA7KNj6d3yvISBDnu8PCCAtS+FXSc2p/bCIL7ZCt+JNa4aPn0isVq0aWIARC"
|
|
294
|
+
"CJ+61dxlRroJ8cAt7K4jkCOdB7GW9WIxLesH0XmR5OM/citgjBUdrgAFbF4ybrGsbqqlM39HGJSq7xeDy771DNQxWPJA1DjFb+7ClEcLDEjqUWRLVtMw7LGo"
|
|
295
|
+
"YxyGIgkiLLy/TV4riR2sgXUceiXFw9aIYKxXnyDJYp7wsQv2RDB/HSaHSyAAddZXm6hsTIe2VxvMBIrPwYLwEkS1BZw/buDaV8mpOB6QLm0cY3XuBbdDD5Ms"
|
|
296
|
+
"52UgWK1WazACozAbMjJYayFoQ+DM+vcb1/p7ZUwMUurSacqmjXdz6bYnGUzeHCe9xxS18PHg8Rii948BL+oHaClQG6G+XxYLNK/BHybwCszVjXrANK3ektmO"
|
|
297
|
+
"emSYIFWBhQXfYjEczaeXHpnYOGNBFAKr+ujA44O3PY80QPi4rlYfQTZXWFH8WnVqwlZzKFAxeUySOFNdIVEEq42ese3NYEDuiKi2RQJ+fhaaOv9UZAKkgYly"
|
|
298
|
+
"yJe7ibZ9yeBkRy56JnLXE6tMNb6PO49HJOe15cJQSAEcxsNCGvul+i1egeDtBxQJKu9ixc6Qw59ZSVYuhSAceKmtJ/4NrgjhRpTpRqGXLOACX/fxrMjhIqBj"
|
|
299
|
+
"GmkmTOrNkbaf8jET3QEicSO5x2jxHr4VunH6u6FzrHRmca4Tp3XNiJ/TbQNPxy6CIXPHFOQUjSlPptOIIaXGjiNhxI0aXUkD7VXDqqF+3FQ1HNaGbXTrkjXd"
|
|
300
|
+
"GPdw9GDkwK0m8amgKhgYCDhwoQojuws79iKmxDKwtLcXOSvJexOaATAKAjzfeb4J5Bu3Xrp8A7qQ3rtaJOhM3r1p7dMS0NG7Aho0iwf9nLTxL341zitk/N2U"
|
|
301
|
+
"zUzK96KmBpRrm59eC2DwvXKjrHvdR7KCu+Cxae3Rn0/e/nL65uQ4e3fyw06uJtpYVbuWYqvzdd0ofgPzOkhO1wvU559elYuP+E0NQ3ieunRE6LXryIc4HFAT"
|
|
302
|
+
"w1w4At2wLoGNA3ZhxNnP74+UMPHhzOvtXJZvQ3S8z2h/kVq+3OnPkI5zu7HDDcvGZDXmfSENcnnCxmsSnshdWHP9YMsadXNWDgTpdnBmAACEAJj+wsdiQh6A"
|
|
303
|
+
"pW3FeSumuCYgmq8Ggj+5kUVfDcs6UziFY3YMl5GDxxDYT3DeDfscBacNjv8y8ivMqmpJkQd8IyPDK8EEOC/mCt+Ark19k3ecFM4B8M0IFIN0z7Fb7Fhb7lT2"
|
|
304
|
+
"uKChySbrwsYTFsHj8TTI8eTYTg1hhttWZ9Ld6OJm46WLGHoll6B6XQX7svajh/XgXAasPuFVqmenCimGZpBCAdIXeQr1iRK44dU3X78M3l0XxUd8+fvkSbSA"
|
|
305
|
+
"2qdXN9mLbJLfgD7wRVjsDpeZtVB1+NPAS7BpRtCaGcMdMT/0EjnJmWADnu9Iq6rb13pyAmisZmhSXS84HdY5pA4HRMWmn8i/jSIpKpI5jBNtXudl03ux5y3Y"
|
|
306
|
+
"aGQtFoS7pHgc9nphvMqM9GKDSWLApdF+nxncWieRDNpxEKXzZkL5s71zvhi3wZyFOVQ5JRAo09YwzG4YsDZcNTQed6XYsOg7yZisdelZYoqGHQu2/6hhg2k1"
|
|
307
|
+
"qtCBJM6YFUDVG47RXTYwZxjIFAAWJjBya8gCVdHnXQzCrsBom0FKLVnaYHLmltqezE0xRWFr9k+ig3eesHVeK6BMl+RUbxaEngCO1OQnzgBZdZ4v1hgApf30"
|
|
308
|
+
"e8/D7uZV0nrWDZ/+xrqh2jPYr3yyxQ1eCQ3miAtz0XLIbd8xdgDSnosnZQ3qfqQU14HCQl3WeMCJA2PWqlZNq0WLNpm9C/gFFrvkILJjwOVyAephFLKRHheJ"
|
|
309
|
+
"G3VZAZXPUHuVEJeIwRdTf8Mnym19E9KIMrqvsz25hVydc2SknaOlLsT1NUb7IW0dsChFHNgBn0oqtSAuCiYCuMqIDZzHE9F6t4bJEGvIrRH4JgfYdXcekVVH"
|
|
310
|
+
"s3gneUucs/kXFpwbxS/bLZWijSDEkBp5WWX6jgTK4urUroOnt1T+7GD/+fmdjtAQ2Wu31PTDh1n693lddFvSteLAo1pTziw1xT8g2W88JGLXrhldAG4fejpF"
|
|
311
|
+
"UhqjdDG7CymrxdbCaltvX8H4bkKdzSrcKASMtt7JB3aOqf8+K5vixRB/tiwu+IBNHtwfvCdNPgh7C/QQgySESQlmIpO8vrqo8hXYpuSTZ/om4lEMfcKUSIE0"
|
|
312
|
+
"CFdu1zrfjdfRCdFu2ZvQCRsH3KGSeQw6uME9iBIvV5BEEzgGV2SeUioM3mDk2Bd9MndRQ9p8+A7zgdCVA0gTJk2S3SYPIjtn/K6AeB60c0txAIW72jTFZ3eZ"
|
|
313
|
+
"5kLTVEcSn5GNhmWO0D3yxrR/J1Rq5QL07FkxvygmEHuSdjQT/xo0eUYsySdk/kLMVhUhL1tdl/0xY4q2NE3fAIXMZkhlZNhhHDhBcAHbOTWGAm+pnFjSlLL5"
|
|
314
|
+
"qoQpuxmmYqEHGcFaY06bKmacbMfCqj0wEn/75vjIiYTmnK+0AxSBjjkE3Kv0IbVY27T0FHgtVtGDIqqQaLVijJuRpjSRVtcTKRLLwBWWMtlFtisu9McuHcTE"
|
|
315
|
+
"Dqe64yO2YXd05NUBk2JGY049LUknO6YqIDC21SIFeqQCaMs7kSOQQdEo4QcrcoUKPdhQvEi+BhL6ovZNYzoVGjgznCFXbp8yj/rCAsBNRmY6I3eYJtHlKD10"
|
|
316
|
+
"eIImQlzlhh6IlSeHGJyLOPowFnwcraRGt+mV2nW0ATk6nE4b8RMzZurfrSbM4bxeqC8LfUdh4hHv+wHC/WzQpr5OCx0N6myM1+6/altKbMhEimXuu2QDKo22"
|
|
317
|
+
"zZmrbUrqtmUc4lyswK0pV+TKDgnPT2ExakWKSI8yYvvmyOE8ELS2p6con8BtnrqLxm6mV3Y72sZ7ZGcZ0+pWNkqSnUMyzd4GahvvRpAEOSNUkHZ4x0xHrUoL"
|
|
318
|
+
"MpUMuaNIqQR0THpnb3hk9jHi/KlQ2GSpB+GsKzCPrjuyeXRDIquMMdVOF6JwKjcqdDDTcUSrSCCaVhoQQzc3vDRpW50ionfHrU3eyjTB+mvkcrhbQo61Ly6l"
|
|
319
|
+
"rdNBrLOWWSox/0thlqClXWcA6aLi6DScUokZDfWlUIXd3d3jTpTPJhRh0FO66JloUQsJQstB4w2GRTnbe0Vo7l+EFH6laY/sJt5dU+ta3xJ+1/7BZ2QPGwJU"
|
|
320
|
+
"EIerDM+bWSc7DG42WRjWkSGt9WnIcdsOyQ7Ju1wHIl7J10iQAmB1RKPTpA4BeQOxe3WiTxmrXv0An1b+BXGfmupjAZav2rPH5fTb6Ay3DbYUUwMeOeYmMxtn"
|
|
321
|
+
"vax1fEXWAg4dAxTpdeCC06PB63GbIcvRBqF/2tyqthyatLGYZdqzxjfOsVEjmWDz5D8+nByz1Vyb15dM7mosi6M3skZ6bNu2WeyK3aUtivzSyGfiOOzeHPJ5"
|
|
322
|
+
"XpdTO/myuUrP+w4+XVqJWEN3upBEyhstq4BKQ9oV67VjJkMsB7xE92Bl+JpdqlYTdvnTP40jyB5uVhgQKD2uhCV4YuzDJUF3OuKJ2c7y9aRsMnCS69mnImar"
|
|
323
|
+
"O81YGk41H4ubA04jHWaVRhWKgNCNY+Illr41A1cj1FRwYQghvbM7wOWqWi/xJGYbJ/EtUg95eaCM3FitNXwsDv0sVuU8OObVNv9Z/yByBLTYC161YFOHtnFR"
|
|
324
|
+
"GdO9Amp/M7Lk1HE2g77MwVf3APbihOdoL36oXM5l7EYKCLC2yq8zBihbQo+wP/I9VUO3iPN8UU7BaRLQmS9u3AMiBQWQjuZ6fN6uT4HwgqGQGdN9xhLwThyS"
|
|
325
|
+
"tGeoIHA/6Cr+xZwA8ZVk4/g0thMb+HSU3bZAT4FTXwTmTW0Efn/tHL7zctBl7m7RULsQLkL4pP6A4ObeVdBiUCUPQxG9CLbmD62lNQ9Xba15nUolDj3ZCIWu"
|
|
326
|
+
"5z1prVdz6TpW5i6mL7GR8mgC5MUDaOu9cHpUiMUmzkYS0TeCs+bXmUkb7ce2pqiiJG+h6ZMtRN1k1A+WoKRpEKoRWwxziN5/m88V8ZF0Rv0RE5BOodYhNnLA"
|
|
327
|
+
"uthY3brLRqobccJ2cpDkMkgqRDFxnEypbXcT2Ch5EnBW8vwPvFD35E12bRXMsq1Zas6T1anNdgldB6RvkWeMuw0HF9AuOl4IPIxFS7xyy7gDOjWqcAk3jQB9"
|
|
328
|
+
"fyPm5cL47UKFckHGYwNdl5p69ix53u+7nDsWOFWGSrANE8jyoqJYBMIHuS50S2gYaaZNHisnNnLq3V24veoYWwid2PPNV1T5zcql2njd9FIYkeYSEtJopNfN"
|
|
329
|
+
"KnrZVXtZpLTd+9kBtXBue5tSei8Knq/AspPv6/HVY5Qe3bQBHvFsSDIQlG7LDaBBA9FHY0zDEDWgsa7eYGPZkuoRCQ1yW8Va8I+b3HMr/4JPe5IXDKUAnps8"
|
|
330
|
+
"k7o5zdkw+oIa/tf+jYmEBkvt2LMIaoFBP2S3MuCFP1EdJbsmCT7CwN7tPVLWy0CvC58dfNOS2ZHo2CKSAvk/fxlBG6OOFk30bTu0OqAUVg6bRhqM3UfBhwJw"
|
|
331
|
+
"tMz0BPD3DZmsTtVuUpeXi2Ly9FYQLIXriDfNITzijVOwNdO8DN7SJhRx1A/60ibKiMk/aMFXS1Vt1tgKsinAtB+RlEKaKReXxQpjbIFkfE8ODcc6NRsRTWs5"
|
|
332
|
+
"dbpAXWGxc+ZWVQVVqqKpOB2d8e6gHezAiAHiZtZX+fNXX8v6Q4w9WvR05NH+8Kr4PCkv1UEPmPk35yCXxTsxBO0GxjOpD2HzESoNwBxIuJG7cS/4hVW42of6"
|
|
333
|
+
"oNV2xPJy23Scrwz48QOZkTnEaaw9L06QHUeH7xdqZN8V0D+EDChFQKaEplyfloyj5ZDT++iSPExj++d23HnpKH2ehSAIl/e6W1Nk6AbPsRFdfNC9GC/2AMTN"
|
|
334
|
+
"kqE9/DjT4VXU8cBKxOcxvHTWtdKzE8KFXNYqPk4g3TugPEkgfLxxnDBqTfUu82s7ak0wRytRLPFcUKY+xN8GYASiE7fkeFbpVzeZlja9uXCEzfbzFsk7cNwC"
|
|
335
|
+
"GgaW767bh020gNA7kT1gyh867W3zEMz93vAPr1rVwOFgWzbhSTHW1oB8d9m6pxojKwe/dAzTZEI3yXN40lzli+TVV0lzXY59syvTKJvZwJ+OEhmTWauFCpak"
|
|
336
|
+
"PFUHgvXEFAqJse1xUWyNfFwTHzDw2YUVbmZaHJlhW+Mvex9Ehqd8C66+Y3h8NqjUBdODbg1Jlw0YjF+vJqli3MAqQ8a9k//OhlM7DtzcpFuzEvDnoVtrwMY4"
|
|
337
|
+
"X9fSy+cuYje78+S1T4S5zH7IVOiQhpQzYtrcxfYcJMMY698bvthTDJE4C1R3+RGW+P1eZCfSLXocJtqi5FB+i5cQ5mUUNPzUB94iFweLDlAbVmAbCGJZet3K"
|
|
338
|
+
"CJVbMUCH8bERRgqMF4f13cjbU4HbzdfzDN4CLii1WpRhOroxM1R3/uxQg5kTY/QQ6SlDHZ0bR3dzCyCwBzgiPwmJHM5B91iDCI4OP+ZvXiHe3mHTBuCKoEBL"
|
|
339
|
+
"wLkwo0onR0ckZlbXCD9pselneCMn3FkHjCkZSYhNex2ziEgmST83scc872EIDh9jA77JkNuttXGTsXGF0q6qHosTtQbupfE29+Z4JPc9Oc2tr/FZun3yRF+B"
|
|
340
|
+
"p5nns+B7WZr7XL0tCaOBoG1xpe5CEQZYsQiwjegMF2T78gxdIPF6kMLADQQ4u86GCezktEKGof644tfjMUOj7r0Mi6HxSDSie/qXap3kK0pdHLGOFv57rH5Z"
|
|
341
|
+
"DZNDHEdyfVWoWivK52Tovr5CtxeybRsmoTNU+t4oWtWiQmuRYXIMTuhJDvHck3ytWlw0HDtWHQ352hZTCfFpu2IboIqKILLUqoZQq9VnjPUBwIIHx5jiVNZD"
|
|
342
|
+
"FxIfkbgZcpjfD4gvDvbbx1MKEP7+IHn+5MmLfbW57bdJ+lJtvx+/Q/IuWOwtEeXz8qbYeM3wrRO3/Wr/+VY3VKD297oKjcyXzVYHtOhtmROytuvwRhcQ25SM"
|
|
343
|
+
"6LXCvSMYRXjtEipRmXgoKmJXQUjDvMQYEFhqk5qElSWRhujWQuO/XEwKEe7Xn5gWC8vNC9xOpNXxxU9HGEVZyTq8hDGHIUuxYg2z2wy4pBGXLyE9uLBhSd7r"
|
|
344
|
+
"2GtsxAqkG6YLtvcdbSc/DzeOSEfYUrvJxrq0hA58Sm87IWofBSEdUqxsFMLPorXgcyvFxVV1rYUMftaXbjfmNT/pmxg4s+pSvrcPIz77+kOedNfOJVO0cJfr"
|
|
345
|
+
"nnD1Iyf0trJBbjpTzXvT1oLOCmxz6IqMeL4aMJZRuc0wQZpzYItxtyEsa8kVT4T6R0tp/3btwOGMbVcCOg9xxrsMHgmVhEjaf4zU/Obdu19OD79/e0S5hU4g"
|
|
346
|
+
"x9C7n0+Oj45PP7QNk668MfDGPE9jdlymqDzAmNX4T21Z3l7P6nFgH45mbjRlJd2mgCMItNJZAeT6aT4vZ7ykdCctBIOVwhk4ax831qCknUrgWKGLLcSQh0D3"
|
|
347
|
+
"TTktW69xLAI4v2eO0g7+2lDDJv8ELop3UhTnkqLKNWAbWazUdGxsyb0kOks5XC8MgHNjzekU2N2MuDAy3zEzQHJRXOWKx3UhwU9L6rSsj1wbJkCvww0TpZuE"
|
|
348
|
+
"FtN/psO/VUpw2IUbtKwTp32bh/jAZJT+p5PEeptGZpOtloSpAHmld6owrgwVbVXpLrwGNq/iNSOPN0ZNjJ1WbOjZjV5AO+oqHU/w7SSAuDLNCsdx3ab+GCMN"
|
|
349
|
+
"k4XaOhdEceiZ20fLbAQkZoZmC24yRYOPL/yHpVpmZjuRsZx6BilWlLE2OdZJyXos3emIKjA8twneV2hU2nZmx9gqbZ4Hvl8FhlbRmj4jhlqZFD368kRLYPcm"
|
|
350
|
+
"4hY9r0PGW5PnwNmzNfLO7MPzDgI2N9P6wX0WthtGOrV3r6SJuLVLshYQ1huZiIfAoJuWI8lDjgARTHqUPIg7E9/13fAhzvm33apvm+OrbyoHn8gxcOCYjplU"
|
|
351
|
+
"y7avhx0HNQLY47QdO3ZVRyw9y6loaGTCkrXxwxZ7LtEIGJJwd/FWxBHd5qnbdo+ICsOpHw2hg2FFaLxzaK0ReTavQj92e4Q/YdeR+2oyDTYXKaw67DBD8T8a"
|
|
352
|
+
"iDbjC3OlB5QcbSUOLIF2FszXuXNDZGZsWwJAPxR2Zd8+8LQMNs09axxNWrkRfB7CkbC+1BIIm+pNPMnU5/sYWRcfQfTGLYR2WY+eefcf8tNC8eD9yZNpNyZk"
|
|
353
|
+
"AfqWrN1IUCvP9PQFfheS4FxqjkzvzlEgGXx9B8yeXr4vauxaWe759mGbNNJmpR41M6fKLPNlOu+k7ZLjD3KMYvNcnbOLlRPuj57rBiL+4foTj2iFb9g5vdUm"
|
|
354
|
+
"Ez5fchF0m5RgaYdB49V+B2ReKM8uGPSdIBTWia7i62BbZqSx9yBGJOIDxNtjK2ajhw46Y1Jhj7XuLT6l+E964223Ww/HpA+B1YJXxE4dkriwS38mTIYOm4CX"
|
|
355
|
+
"8QEQ8uKdC2mE0Cu7+wdddATIgY8kXLYsiZczJNu+z4K/rE3+DpysmHBIInN5hcER66tqNhmGsOqPHV9rERr3a9ObRRqn/OxuPsDxho7UyJSUXEzLcakmlQ9c"
|
|
356
|
+
"RvvW1tlm9zVtDbCdKl+sT1jk9lfbnG2wSBAAbWGZAJ+Qb2wRR0Z/dpDnTfEuoVJbQkUiDm0fyrGLhUlTq00zJi9DWJTgB7ETrhf7Hj7biw18KRo0qtlnOG0f"
|
|
357
|
+
"i5vRLJ9fTHL0qNDGL/D97Pm5I3PRm/RpuZgCXwO1r/Ok614yFIgJW65AjPiK1pP0DXXEb6f8ljS3A709JBIcjoApRoQ3COcTw7ttcydlw7yFG4+9nTAxlNpO"
|
|
358
|
+
"eJY78g0nraEhzKIO+hazoQonNj650i+fv3fscBzLCbgvOplrROTrpsq0mG2szMKAPEHTlNIN73LpYhfvpJ4uIUWnzLfGAauX64tZWV9hzuCrUFT8bUL2vYq9"
|
|
359
|
+
"Q7gWDCk0TE7wqkKNDdIAYzf6pRqUEslR3TYpF0UdXlD+Fo0y8tm/ya7/+78JD//938m0XNUQlmWhOG4Dbc8VCPkqgeumJI8COF9WK8o7xxkqKXp/c1XWHHUY"
|
|
360
|
+
"XDrrppzNkkrJb9Djh/96q9b5MGhvwzxsc9CPR/Z7dQ8tnea07XHm7s/ld+XwYeC/7jOqYO87nd6cpcFHNecoBzKle5KD1d4WpogXDguSj9ox0IF+P4ydxHks"
|
|
361
|
+
"qUD0xHH3wPB3Yk42ZjEIcb8hpF8Lzu+BVB1TuyWbIIvuvVQdHJubJQZo7g+zDKJ6Z5l6mPYfNEkFBZ6OzhBSDcP85WZD4x7/CsRPgWnOfLN6N4CYOtOP89Uk"
|
|
362
|
+
"bq+3RVjxqOZxmzjjm3NWQEhuJ9OZ2m6KLxVC0U3T1+v/KyRE2BzWMTZLqNaGt2c6huM5qre3iZx3ryB+qBDgxhPTeGQ8Qnbzgk2GPL017iT2HuBkqw1uC7tk"
|
|
363
|
+
"En12sElmMqQruO6tiRqHaD9juml/tVNoPSdsesFaABlLj9QOMWtxL0p5LP74F4kyui05mkvckCbtJHyRGJOcN/iisNpF1XHZ1CazoS4epvuwkUuDbAyxQX25"
|
|
364
|
+
"MKhe0OH3GnI/CurQvxrbTOBCwWdFiC37emiwXIpILiC47YqzHA9ueu8FQ3kGZPzTaOxUs5jI9EkdCdm7lLbvNg9qXuSeC/UT4eS8HkNO4gOU5O3jyZp1W3Wh"
|
|
365
|
+
"lt2k5vO+LYCTRLW0P6zw2wYP0djLWCLP7rxGPNwwpvcmPIjRu2dTHvGI/7ov/XGP/AducSlI+aoYZKnVrBzfjKSNA69E74bJ86WT6ejtrS4av+n80EaYwiMY"
|
|
366
|
+
"/iB72gbWOT3FH1LCLQ39cGg2/Yu2tI4kfcis7GGP4yvAOtIpYsBrCBMfxnMi6J4EkYzJ7ecMDHWFwW6AAUzi4Ga7M9bATS1ztH07whx253LAoIykvtT75Pke"
|
|
367
|
+
"paboTrvWBjOiOVvxRr+eow82oVoDRNPS7/flkHT/zyQ07nLpbpWmdbdWH5gv74vnyTMZBym08sjOqdPVd6MgSJiXq1B0RjkL5XR7vYBJfi9cs4Rtz19RErUt"
|
|
368
|
+
"lZn7Buh4uP+c+oYvTrswL3YiO5q2pfymv9FNfyOCGm6RQZFXoJ5tkydwDl6G44ihhCbsM3Ek0Ln8QBfH32PnoP+Z7II8kkFLyj5/vZrdspgXq8tiMb7JqunU"
|
|
369
|
+
"JrDcLZd0a8ZdcMkzCXvZYyClM5PNh3hAqUzvHp6Yd7eQ9W7KH4MITDMbSePtTF9b4gubqFgnCabf534xfWy3joowr5Fzeix5RUwCEs6odnZNoqu2md3ufB0m"
|
|
370
|
+
"94N/iknMDpNuBFaUfCvD1Kqr9bKhkcqopffFq5Mtxy3EqXgO4o6yeF5oqSOmIZgCr0K4YHWt8I0/bWu8VezecV75JjnSXTZS1USj5g0rCONEXDWsTY+zugSn"
|
|
371
|
+
"1z2O6jd6Htb/gk4pd4/+L1BLAwQUAAAACAAAACFcXz7FrDQIAAB1HQAACQAAAG1vZGVscy5webVZS4/jNhK++1cwykVqeJUgyS6wXvgQJLNAkNchuyfDENgS"
|
|
372
|
+
"7WYik1qS6hnH8O/KPb9sq/gS9XC3G0l0mKZMVrGeX1Vpsiz7SfaqZn97lL1oyEk2rCWKHZhioma6JF8p1jBhOG010YaeCRfEPDHCPnBtuDiSTskDbxlsSsXK"
|
|
373
|
+
"LMtWByVPpKoOvekVqyrCT51UhlAhpKGGS6FXK//bE9VPLX+Mrydah/XPWoqV41XWUhgl28DpO/6/njffMiVY+04pqdZESHWiLf+VVVaFClRYrVZ1S7Um755l"
|
|
374
|
+
"2+O93+OW3qwIPA07gIxccFNVuWbtYR000WswQC1VAwvNDCqJK34UsKp+YecNeTwbpgvHCB+kLwM52UZO4wOeK+z71Xg73AX7YTk5MIiAZ4a3VdTIKc8bPVYp"
|
|
375
|
+
"ERVMeeBHcEwzyFkemckzS6uzNdnti+XjyQs/EK65gIiAMMmHjTVpIS4KApwYMIp8FINgEESD+1iTX7RROTfsVJSw4l1ekINUBH/B+NrN5QKxsqxYk4fkqsh8"
|
|
376
|
+
"8jzkSr53tLxxhJY9/IrcU1/kwUDFHlWaiXUtBtuyw4HVhj+zsW3XzuiJifGeLbk8PMQTDw8jD+cTUhDRKblxP1wjq4/J1xxEhdQ6f/Lfb8iJGdpQQyGXmhgk"
|
|
377
|
+
"VjcJOakctSaNhHyAhLMCQ7ZyTWratmVkixQYRWCNYKOWPjorG25ahouG666l50rQk33XFijsirVoCSmyILmOqyqIlSUW8VYpO9nlcO+a/CAFK1KD7TKwyTNv"
|
|
378
|
+
"mMr2GNzgiOjEuFMQkDuTbUtPNJuQP1LNql61c/K448izolTOu9knUyZMNJ3kwsyZxJ3AZExIO46JOKcLG8tkT4yCXtqSRZLwYzFJsvmBNWl4HZLtcp3Yk55b"
|
|
379
|
+
"SZsKwhncaNiCXacn5kK6rAWKIQ0O4FymOgXGuAkyimIGIIKXTX/qkoBHAECD6O1/VA/vTGgsElTXnG//DUUGfoNbaN+aLQhblFCGIKzymUxYKkrB3udTaFzj"
|
|
380
|
+
"7etQWUr9RD/7+z+K8ol9aPiRaZMnKa2Ylm1M6Fj2thiegGTsSOvzNksjGc6AZgvVJo/Ug6j8YM9D+iHDcToMxYZaULGon1SRPDu28pF64BuT8gYrU+fhw7vV"
|
|
381
|
+
"SRtCvKMKEhlSNM82mzk9Yj/8C4fd7UuiJXXMxgsQjPmAeggz/tBmBseKcojLWbH2iF55uurENQT5ERV1Knu1AtLblgFuIq3EyHOdBwLOF5/+cyzQbedcAnpt"
|
|
382
|
+
"SGJX+V4AqsBP+BLEATjcWBPfKi/TZ4zdtoywzuXkrTp2HeQe4qCahQBIvguC72187rzM+xmDqbfs4USn/Sgs0W8JtMT0tHgywe03e9GVB9Ys+vE9hfoEB3Af"
|
|
383
|
+
"S74mvaDPlEP9sWVn5FSQdGRCKEl4rHFINdpiIuwASwskb1Ijcn5NfuCenJ1Ka63uHL13Rvb9xtCYzdHyDvFuWxfkGVIDbuNG+50mdA5TOWMjEwJu6GxiIKSK"
|
|
384
|
+
"zCInjwRLFXayOVTOt6lMmwbgWb+gOkA86q6ZeuY1I55gqm1Sr4K+aQmL0k796KRPjoJ22HahAWzxqeUJUJZVvqxYi6XHIWOT17cp72Kuqp8oMHg9KP25f6Hc"
|
|
385
|
+
"YIOn0AjaNpGCo+mRcjG1jK+k0KgCGVyRCr9JZb+uB6cOxfMZQBaaUeaF9UXUvUDLa5eGfTC+mta9gvLo3hJjyB7dgrjjp4jEc7ZnB2izbeqRAfBRiGe0xs99"
|
|
386
|
+
"c2TTDtPNRYfsgkTXAf2zO4sEiGKdDnyKWaXzyrh8M7agW/MiO/93wGry0TbWmXld7Jg6cWPCSIVsB8ge4B7Hlu12hPozVr3wrrdD5QHP597QmIqXaxE1mtGG"
|
|
387
|
+
"8h2lCdEdmc5FvyN0ragV+O6RNw2zIwJ2Z+hEyFf8dGBPEIOZ28YBBQLtGYKJhCr5xaefj0X+mPx4gAG31wY0PVFkF8oGYc9MoD5UDN8lBoxAvO46BrnalAtu"
|
|
388
|
+
"hZhzUIcdMvgNrsmia18wqD0TYiZGnUfQLPpviXz59NzcGB1c9GwSwLZpbZKOIfSxNo9fFmkW2L5DDUE9FwL47UBfO6P4q6cQAkcGXHjkoqlUL0aA8NdkPIq2"
|
|
389
|
+
"kO/7BdO4q8aGGSiKIOddllvSuoaxvJUJCmJaL2nf2S9gOAlhouPXnt1+nX4qQSO4TW+G2K9SsIjBBUzDM1PYpILwzWwH6ui3Awy5hjSFG3dmPl4M5AmKRXCw"
|
|
390
|
+
"98yDxGtTYqKJJu234zVDl7chftLLFKMwJGIL/pPHCICFb75+h396kNdCg+NQThVDIzitsi/RMITbD5XmnHaUZXadI99iWhl1XoDqF7rzoJi1yPgS9qFm3cJ3"
|
|
391
|
+
"SkRA2PvzzYcDIDAu7lX2jfe5WT3x1tSqJ6ZcEUq+QuCDsWzH1TClgIfjmIJf2nS+AHtvG1BeVNQK55qh7a1O/Nb9p+Rz8fTxaTwYEFFkQ1yrk0FnAS2xhh8w"
|
|
392
|
+
"t71x7atb7pMvfjB83jVp+i900Jhlu4tjc92TC1j3mr2aXT/ISd8IpXApM9Bffq4QL+rvuqiluAlDtf27NFff/Ng55o+iWzxb3F/M1kS49XzKSYrkXPHwYKtC"
|
|
393
|
+
"8f8t7pwV0ufNWT9Wdj2+3Gf0GhtadPLm4sy1bA+ExkiNvrMJeftKt7+LNPtdDNp9COoXDXUz2/Bx2WEDxOWFGzCStIAfXs0M+/e15LiRFs5W5PffljME0ztY"
|
|
394
|
+
"fUgTt1gOyJnBQDun5j344F7TDsI1UmPEfGv7s5gE1tR/afszVWKHp/dzb79cQP5Ywbx596uK3llIZ1NyJn8ZFPHuRZgL/VwSy35l+ToxrVHc8rr6P1BLAwQU"
|
|
395
|
+
"AAAACAAAACFcCOZlENoMAACYHAAACQAAAFJFQURNRS5tZJVZy3LjxhXd8yu6yjVlJ0VS8vg9WskalUeJNFIseZysiCbQJNvCy2hAGrq0zgfkE/MlOefeboBS"
|
|
396
|
+
"ZpFsJBLox32ee+7lZ+bS/z74wpxuXd2bv7quduVsdrdzptQXVl7cywvjg+nx6sF1wTe1K0zblD7fG1sXZmexJART2r3rzBDwdr03Z2UzFMGcNQUeBrzHvrA0"
|
|
397
|
+
"Fz3P8nWPw/HEluUeb1vb2R77Nl1TyUW+qoberktn8qbuu6Y0bWlrh3e2N3bAEmzPsScYW1S+9qHHCU0X5ibgHx93vd/YvMeTB1v6QtbmkFc/zk031ME4vBss"
|
|
398
|
+
"JeFOv8WjqCO+t67bNF0FJWxtu72BGFCqn4vW/BLM2ub3pnPbLik4m332mbmC0mE2e5IP5smcDn1T4ZbchHzniqHkw6uoYBg6yMknN9C+oSzJuk84YrFYmE/8"
|
|
399
|
+
"xZvserPJ8P2tDzyowMf38A3+/UzVbOcg2W8up2Fl/d1Q+3rLLb86dw/DP5m7BqalOi3ubvt0cVsOsA0Mv28b+AoLb3sYdLSETQqJ/+ymh49bCxPU2wOTxlt3"
|
|
400
|
+
"vr6P957DuHs4sXPOFHYf/h8JfnYIS3r2lA43tsUGXGXWDk5ySTK58nSrDnlwYh/rRdcznF+63hn3EcEiZulx+ZFefJTCOOdp//t1M31f2b1poF3nCycRnDy9"
|
|
401
|
+
"NMypioFALccAoHvsA0STIFg3/Q5ZITv1wPOHphzEig/ePcpeGxDccMPQmqaVmH0zm2VZ1kOh2WKhebvQjF3IjYiQJ/X6U3LD02Qc7HHplsUoWIM9u2boyv1T"
|
|
402
|
+
"Qcs9OTpt9dWKDnt6lMjhrQoWsEvJRKSgrlo7pLyFCauWd8DCedPCMBsohrRvkS2AFSgaaKZMMWIlGLFs99nSXNejir4nlqTjxHFlg4AbT3kECBjeQ5wS+yAo"
|
|
403
|
+
"Y0jiqcIADSpyFUkY2cbHozTBVF5jt+kgW7pOPafeSEIF1/dcmKmxV2rsVXy90uDNjM1z1yJdMl/vXOf7zHzBGwu3sUPZ/4n34BVzM3tzsOjeuVZFbglCgSEK"
|
|
404
|
+
"cCG4KdRGHF20viYIT1D1uPNwXToTmFi8UD3iuKU9a8RTK1gznWEefQ+v95ARulNF2f4Rd/PLKAUkX3fO3ou1ymJCdgG+D/GwH5uhLpAfs9m5zXcHaJ5QWWDd"
|
|
405
|
+
"eonghclUOobAm1EgAX4upeLuY9t0FHfXNPe4DHuYu9HgupEPkCzBr33p+2SwEgaZIFV2asI/2xv2MHa1iBhkh22FoLIvN0aI0C2oHygiVFQLQtuEfiEyTDJW"
|
|
406
|
+
"tvYbhOLyt9DU2IM6h3PnKJlhxxo06hiVngtQ6Hnvrk7PpCTZfuicHHf27vT9T+eX1z8tqwKnIT26hSgMyw7ttrMFc2QLX5xOJVKOTfFU5zh9Quh5jAP5aIfC"
|
|
407
|
+
"91oF4V0VIuDPuvkIQXF0TSQAAJZNvUV4NRIiLz27BM5KxWZFojZSdesGyG57xMKhEU9MUyNZn+E1gTTf2XqrMLoZyvIAsAnOGmoTPt741pW+drPZl0tzKw6X"
|
|
408
|
+
"rdiBE8fIhzYeKdk/NtENY/qQrsTUXM5eLwH9tpAT4J6N38L6hdh6nqJdbUMeATxvWndiDrJbHxFVgESyD47mx5QquptPhIksZ19RbKVADHRgAf2EnIO2hUgX"
|
|
409
|
+
"8z2qkjJEaIgrGD9cC7f94Rjyeed6HPv10pyG+5eKgNU5oUp0/YaogXpmyBvwr2Jp83XhWoc/dQ/nxPW0/QF5AsBBNoGJGAYMkPVQbF1/AojZoHJE/5cQ7vcB"
|
|
410
|
+
"SSAPFZaQOyJ9jIw1pK8AX3WzUtdny9k3y1SCjSOG6IsDKXgzFB06HAV87oa8FwU1zHi6Fkmp2XW+q2x3j91BigJYpPvoutwHDTPYAkEMqoeqI+S0kAAPTfmA"
|
|
411
|
+
"ePt2ac5rmAlOocXmhuE2x4J7h1jgmerT3leOGKp2CNFBhOQJAhfriI2AqqZU6rmcfbc0V65DzCPhfC9+FNstkD/IR9GbkhOlTYdzUen+GJ/iQCQmrO4/yjNU"
|
|
412
|
+
"NgDgnJrVYmsp4SxtA+pB3R9NRmSxs50PDKctAVlzJ1j4g4ez4pGYJI6OuKbUuOZmj4LBAtzkLPVQ4nvmfrWGbZALoITKy8F6kdxIDNijN18dv1IlbH0YaGYN"
|
|
413
|
+
"kxbm8vLK/Ebjcel3x68i1XYsFYoVFG7nt0DPfkF5R9o5qrSc/cDQaZ3tX4hxwEwbJtjIBCZzRCrvJOskGxArtfnm1UlypJJdyzRrRLbE/ZezL4+XypPxOvvm"
|
|
414
|
+
"lfn3P/9lXsf/Xx4fv8oia1ya96jACQ6UCYboZlwbFFBZI1JhH+vwuKlzlWgvwICrAX3aPji1k4pJ5Xgzz9xKhhOYtWt5zuHFFmHI6UtUiK5rAHdYT1/X4OOl"
|
|
415
|
+
"r4ieUV1AsFL5lMg4nxmJyAxeDOxDxC/oFHnt7d8uvZpYqYSGgegJ7lrfYyXiFLGAVPf1qE8xtXJiKqCrF+ouh1IV8LICOJED1GDbX936lwsR6uLtuQG8+NxF"
|
|
416
|
+
"I+f3dfMIXNqKUIco12rnCp0L9+DzmM+xNdOC886TUu7NjfABEO+dPlhFOF7hvH6XQb6h7sN/1Rj2EEWhxomy50N3uIbNcRirCGNVqkj2Grx4vMyKh7K0TFAq"
|
|
417
|
+
"Y5FEMaUChzRRK1HQ5iKdy/V/JtNO4Bg0YkmyYlELRDmGD2zkH3wxYFWFIxlChDjsqNqQ3HpQW/jyD5QS5p/WrhimW/CkVqMbW3z36YIGlJ2qX4WkhzCWJhAu"
|
|
418
|
+
"P8YRYy4FUUqQzj7GUiiUWS+nz+/hFM2IuBt41WtPjKsWPCuRNLHBX26v319iZ77zD9Hxt3bjQCevW9ZB+lI7H20MsvOKuM0UkXbcKts8aEW0cIWpStIDXDtP"
|
|
419
|
+
"hVEYUu7KMmLThuAY2cvYoCnfFOawbjopLvsxghKunB5A2VhbJ66vWVLu37DMVUMgb2BtHWEidrWCFjgtkXWlqxpaiZDGZNy5PCYuG3v4Hxmd+Ak8VzbgpMAn"
|
|
420
|
+
"NSS6yjwWQC1vwXzBahUJe9r3+k/Irp/O78yRbf2RzHeORjsc6cZMjHfYoD9jvJpLsUtmkZOqRD23ZbNmPAszGWFj/iwhdSbGdPqcQ4i827cSMEqYIoETinkw"
|
|
421
|
+
"iVK8ITFC/n9y31JWNI8gVGHnW4rFXEszrxHkYQe/8W7K4xwNhRf4zv6+GInvAqct7shBkPuImIsbIgzhOTL1MKxxYD9IRmOzyPfCTG+TBRgKyJkhEqJRGI49"
|
|
422
|
+
"POlFjkhxY5soqKGdoRQdEihygFk2EsyV2HjVOaYFDJZJZT98GptAkwWJDGREJtbhh2i0lS/4TTZlMf5r7Y02uN51bcfxkEwYIPVm4zRvfP3QxLFEAqio8Rlw"
|
|
423
|
+
"ghaw5QhPCg2ApabzW09QfLZH8QfohIADLEuJOnyvqSA4UIiBvFBS0ekEMQahJKyid6Txm4SY69RhFDeOGIJMAybeFWNZhxJCzzSEkeYjRxfzKH/qd4Dc7e4Z"
|
|
424
|
+
"x0pwszSXbms5ZIsdwMXb0RRhaGOnLRUB3TAjQxRgGUV7gurne8FTmVocJtXYRM1u7YNLxIDVpXOHmiQgXZofWZo0gUcwjIlNE+IUxE3n2OQ2DHPYPSXp0Gqt"
|
|
425
|
+
"Qvw6evzd3d2N+fr4B7WQQ6mFXXWiQuaCwAJZCOQfepqIN9R6h1TbEIsJycwQ2PiXwiwRpb4emL+nsfhIX6CASM4dSyyF6zinitgWR4W9RReGFynjOGB5hiZF"
|
|
426
|
+
"GuAeAAhjJHY8n4iNeRSefKaOgUAnssM68BHr8JiosEZs7Y3MeRqIzQIBewBDpOSUxIB9NDsS6wEGXqQeLsbASRSX50S78mMUW46F/2UCrXXYJESQGfAIBPKt"
|
|
427
|
+
"3VlJhxddz0HEzVPzoCoLMYUjrg4HdjLfj3Efchn7iSHYzbCVBzhokIigAMcBRLXUpcBktHtdN7Q6XqjaIc59DlkEe0f+PKFdrXLrTk2bGMg4IKaYsY5G0jbB"
|
|
428
|
+
"TC8DkregNRKKjIyjyHljfMjcQMjDSbruWfzMx6c7OisNJufPKUQcYY6jiscG8neIDI4lY/9QOhuxnjFdNvk987bHcltyHJBGnlLiQUPPP1xf/nJ3cf1+dXX9"
|
|
429
|
+
"9vxydXZ6c/rjxeXF3T9WH85/vsULsMrTmlNB+TVgHK6SonOC0sY+NVvGlwsl7+EoS6nSubZE7ypJV2izHcgK0xQSEUaK8Qx951NHEV02zrCQ/nsGzvRTEI2R"
|
|
430
|
+
"aEbyDhQVdsirhbquB18WmtLTLxDh82mYGuWP7XjWaifM2V84Cvs6Xz2fDq+hd+lkwD2bfZDiPpamCuyzSD/WTP1SVjR5mEjP6uFgF4d/gHBins6Z6XkaGiVi"
|
|
431
|
+
"IaVMoT9TgWTrfjWdhbgpdfo2jdh7IH3TWe3oaOO5jIRGNjlnQ141YIxA2G9TwdFeBgREofD18dffp/IjTyeiBC3RRSzGJBEiPI2c27LZc+K6nP0HUEsBAhQD"
|
|
432
|
+
"FAAAAAgAAAAhXM3NRj+gAAAAdgEAAAsAAAAAAAAAAAAAAIABAAAAAF9faW5pdF9fLnB5UEsBAhQDFAAAAAgAAAAhXN8qpShBVQAAzX0BAAoAAAAAAAAAAAAA"
|
|
433
|
+
"AIAByQAAAGNvbnRyb2wucHlQSwECFAMUAAAACAAAACFcXz7FrDQIAAB1HQAACQAAAAAAAAAAAAAAgAEyVgAAbW9kZWxzLnB5UEsBAhQDFAAAAAgAAAAhXAjm"
|
|
434
|
+
"ZRDaDAAAmBwAAAkAAAAAAAAAAAAAAIABjV4AAFJFQURNRS5tZFBLBQYAAAAABAAEAN8AAACOawAAAAA="
|
|
366
435
|
)
|
|
367
436
|
|
|
368
437
|
|
|
@@ -372,45 +441,45 @@ def _ensure_embedded_liquid_kernel_package(package_root: Path | None = None) ->
|
|
|
372
441
|
quarantine = raw_root.with_name(f"{raw_root.name}.invalid-{uuid.uuid4().hex[:12]}")
|
|
373
442
|
raw_root.rename(quarantine)
|
|
374
443
|
root = raw_root.resolve(strict=False)
|
|
375
|
-
required = ("__init__.py", "control.py")
|
|
376
|
-
|
|
444
|
+
required = ("__init__.py", "control.py", "models.py")
|
|
445
|
+
capability = 0
|
|
446
|
+
ready = root.is_dir() and all((root / name).is_file() and not (root / name).is_symlink() for name in required)
|
|
377
447
|
if ready:
|
|
378
448
|
try:
|
|
379
|
-
|
|
449
|
+
match = re.search(r"^EVOLUTION_MODEL_CAPABILITY_VERSION = (\d+)$", (root / "control.py").read_text(encoding="utf-8"), re.M)
|
|
450
|
+
capability = int(match.group(1)) if match else 0
|
|
451
|
+
ready = capability >= 2
|
|
380
452
|
except Exception:
|
|
381
453
|
ready = False
|
|
382
454
|
if ready:
|
|
383
|
-
return {
|
|
384
|
-
|
|
385
|
-
"ready": True,
|
|
386
|
-
"restored": False,
|
|
387
|
-
"source": "local",
|
|
388
|
-
"required_files": list(required),
|
|
389
|
-
}
|
|
390
|
-
if root.exists():
|
|
391
|
-
quarantine = root.with_name(f"{root.name}.incomplete-{uuid.uuid4().hex[:12]}")
|
|
392
|
-
root.rename(quarantine)
|
|
455
|
+
return {"root": str(root), "ready": True, "restored": False, "source": "local",
|
|
456
|
+
"required_files": list(required), "model_capability_version": capability}
|
|
393
457
|
root.mkdir(parents=True, exist_ok=True)
|
|
458
|
+
backup = None
|
|
394
459
|
try:
|
|
395
460
|
payload = base64.b64decode(_EMBEDDED_LIQUID_KERNEL_PACKAGE_B64.encode("ascii"), validate=True)
|
|
396
461
|
with zipfile.ZipFile(io.BytesIO(payload)) as archive:
|
|
397
|
-
|
|
398
|
-
for name in
|
|
399
|
-
|
|
462
|
+
names = (*required, "README.md")
|
|
463
|
+
contents = {name: archive.read(name) for name in names}
|
|
464
|
+
# Back up package code only. Runtime records, config, signing keys
|
|
465
|
+
# and artifact/version history stay at their original paths.
|
|
466
|
+
if any((root / name).exists() for name in names):
|
|
467
|
+
backup = root / ".package-backups" / uuid.uuid4().hex
|
|
468
|
+
backup.mkdir(parents=True)
|
|
469
|
+
for name in names:
|
|
470
|
+
old = root / name
|
|
471
|
+
if old.is_file():
|
|
472
|
+
shutil.copy2(old, backup / name)
|
|
473
|
+
for name, content in contents.items():
|
|
400
474
|
target = root / name
|
|
401
475
|
temporary = target.with_name(f".{target.name}.{uuid.uuid4().hex}.tmp")
|
|
402
|
-
temporary.write_bytes(
|
|
476
|
+
temporary.write_bytes(content)
|
|
403
477
|
os.replace(temporary, target)
|
|
404
478
|
except Exception as exc:
|
|
405
|
-
raise RuntimeError(f"unable to restore embedded liquid_kernel package: {exc}") from exc
|
|
406
|
-
return {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
"restored": True,
|
|
410
|
-
"source": "embedded",
|
|
411
|
-
"required_files": list(required),
|
|
412
|
-
}
|
|
413
|
-
|
|
479
|
+
raise RuntimeError(f"unable to restore embedded liquid_kernel package: {type(exc).__name__}") from exc
|
|
480
|
+
return {"root": str(root), "ready": True, "restored": True, "source": "embedded",
|
|
481
|
+
"required_files": list(required), "model_capability_version": 2,
|
|
482
|
+
"backup_root": str(backup) if backup else ""}
|
|
414
483
|
|
|
415
484
|
LIQUID_KERNEL_PACKAGE_STATUS = _ensure_embedded_liquid_kernel_package()
|
|
416
485
|
|
|
@@ -420,6 +489,32 @@ from liquid_kernel import ( # noqa: E402
|
|
|
420
489
|
LiquidKernelError,
|
|
421
490
|
)
|
|
422
491
|
|
|
492
|
+
|
|
493
|
+
def _ensure_liquid_kernel_sqlite_lifecycle(registry_class) -> None:
|
|
494
|
+
"""Close legacy local-package connections without replacing customized files."""
|
|
495
|
+
if getattr(registry_class, "SQLITE_CONNECTIONS_CLOSE_ON_EXIT", False):
|
|
496
|
+
return
|
|
497
|
+
original_connect = registry_class._connect
|
|
498
|
+
|
|
499
|
+
@contextlib.contextmanager
|
|
500
|
+
def managed_connect(self):
|
|
501
|
+
with contextlib.closing(original_connect(self)) as conn:
|
|
502
|
+
with conn:
|
|
503
|
+
yield conn
|
|
504
|
+
|
|
505
|
+
registry_class._connect = managed_connect
|
|
506
|
+
registry_class.SQLITE_CONNECTIONS_CLOSE_ON_EXIT = True
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
# A standalone main-file update can leave an older local liquid_kernel package.
|
|
510
|
+
# Adapt that package in memory as well as shipping the fixed embedded archive.
|
|
511
|
+
from liquid_kernel.control import ( # noqa: E402
|
|
512
|
+
LiquidKernelRegistry as _LiquidKernelRegistry,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
_ensure_liquid_kernel_sqlite_lifecycle(_LiquidKernelRegistry)
|
|
516
|
+
|
|
517
|
+
|
|
423
518
|
# BEGIN EMBEDDED COLLABORATION BACKEND
|
|
424
519
|
COLLAB_DB_FILENAME = "collaboration.sqlite"
|
|
425
520
|
COLLAB_SESSION_TTL_SECONDS = 24 * 60 * 60
|
|
@@ -12152,6 +12247,63 @@ def probe_ollama_environment(base_url: str, timeout: int = 4) -> tuple[bool, lis
|
|
|
12152
12247
|
except Exception as exc:
|
|
12153
12248
|
return False, [], str(exc)
|
|
12154
12249
|
|
|
12250
|
+
def extract_ollama_model_capabilities(payload: object) -> dict:
|
|
12251
|
+
"""Translate Ollama ``/api/show`` metadata to provider-neutral hints."""
|
|
12252
|
+
if not isinstance(payload, dict):
|
|
12253
|
+
return {}
|
|
12254
|
+
values: set[str] = set()
|
|
12255
|
+
raw = payload.get("capabilities")
|
|
12256
|
+
if isinstance(raw, dict):
|
|
12257
|
+
values.update(str(key).strip().lower() for key, value in raw.items() if value)
|
|
12258
|
+
elif isinstance(raw, (list, tuple, set)):
|
|
12259
|
+
values.update(str(value).strip().lower() for value in raw)
|
|
12260
|
+
elif isinstance(raw, str):
|
|
12261
|
+
values.update(part.strip().lower() for part in re.split(r"[,\s]+", raw) if part.strip())
|
|
12262
|
+
caps: dict[str, object] = {}
|
|
12263
|
+
if any("think" in value or "reason" in value for value in values):
|
|
12264
|
+
caps["reasoning_supported"] = True
|
|
12265
|
+
caps["reasoning_style"] = "ollama"
|
|
12266
|
+
if any("vision" in value or "image" in value for value in values):
|
|
12267
|
+
caps["vision"] = True
|
|
12268
|
+
if any("tool" in value or "function" in value for value in values):
|
|
12269
|
+
caps["tools"] = True
|
|
12270
|
+
return caps
|
|
12271
|
+
|
|
12272
|
+
|
|
12273
|
+
def probe_ollama_model_records(
|
|
12274
|
+
base_url: str,
|
|
12275
|
+
*,
|
|
12276
|
+
timeout: float = 1.5,
|
|
12277
|
+
max_models: int | None = None,
|
|
12278
|
+
) -> list[dict]:
|
|
12279
|
+
"""List Ollama models and inspect each advertised capability by default.
|
|
12280
|
+
|
|
12281
|
+
``max_models`` remains available for callers that explicitly need a bound;
|
|
12282
|
+
normal discovery must import the complete provider directory.
|
|
12283
|
+
"""
|
|
12284
|
+
ok, tags, _ = probe_ollama_environment(base_url, timeout=max(0.5, float(timeout)))
|
|
12285
|
+
if not ok:
|
|
12286
|
+
return []
|
|
12287
|
+
records = [{"id": name, "capabilities": {}} for name in tags]
|
|
12288
|
+
names = tags if max_models is None else tags[: max(0, int(max_models))]
|
|
12289
|
+
for index, name in enumerate(names):
|
|
12290
|
+
try:
|
|
12291
|
+
body = json.dumps({"name": name}).encode("utf-8")
|
|
12292
|
+
req = Request(
|
|
12293
|
+
f"{str(base_url or '').rstrip('/')}/api/show",
|
|
12294
|
+
data=body,
|
|
12295
|
+
method="POST",
|
|
12296
|
+
headers={"Content-Type": "application/json", "Accept": "application/json"},
|
|
12297
|
+
)
|
|
12298
|
+
with urlopen(req, timeout=max(0.5, float(timeout))) as resp:
|
|
12299
|
+
payload = json.loads(resp.read().decode("utf-8", errors="replace"))
|
|
12300
|
+
records[index]["capabilities"] = extract_ollama_model_capabilities(payload)
|
|
12301
|
+
records[index]["raw"] = payload
|
|
12302
|
+
except Exception:
|
|
12303
|
+
continue
|
|
12304
|
+
return records
|
|
12305
|
+
|
|
12306
|
+
|
|
12155
12307
|
def list_ollama_models(base_url: str, timeout: int = 4) -> list[str]:
|
|
12156
12308
|
ok, tags, _ = probe_ollama_environment(base_url, timeout=timeout)
|
|
12157
12309
|
return tags if ok else []
|
|
@@ -12167,7 +12319,46 @@ class EmptyActionError(RuntimeError):
|
|
|
12167
12319
|
class CircuitBreakerTriggered(RuntimeError):
|
|
12168
12320
|
"""Raised when fused fault counter reaches hard break threshold."""
|
|
12169
12321
|
|
|
12170
|
-
def
|
|
12322
|
+
def _fetch_ollama_models_cached(key: str, base_url: str, cached_tags: list[str]) -> None:
|
|
12323
|
+
"""Fetch Ollama tags and publish one cache snapshot.
|
|
12324
|
+
|
|
12325
|
+
The caller marks the cache row as fetching before starting this worker, so
|
|
12326
|
+
concurrent catalog requests can keep returning the last snapshot while the
|
|
12327
|
+
network request is in flight.
|
|
12328
|
+
"""
|
|
12329
|
+
try:
|
|
12330
|
+
ok, tags, _ = probe_ollama_environment(base_url)
|
|
12331
|
+
except Exception:
|
|
12332
|
+
ok, tags = False, []
|
|
12333
|
+
finally:
|
|
12334
|
+
with _OLLAMA_TAG_CACHE_LOCK:
|
|
12335
|
+
if ok:
|
|
12336
|
+
_OLLAMA_TAG_CACHE[key] = {"ts": time.time(), "tags": list(tags)}
|
|
12337
|
+
else:
|
|
12338
|
+
row_now = _OLLAMA_TAG_CACHE.get(key, {})
|
|
12339
|
+
row_now.pop("_fetching", None)
|
|
12340
|
+
if cached_tags:
|
|
12341
|
+
_OLLAMA_TAG_CACHE[key] = {
|
|
12342
|
+
"ts": time.time(),
|
|
12343
|
+
"tags": list(cached_tags),
|
|
12344
|
+
}
|
|
12345
|
+
else:
|
|
12346
|
+
_OLLAMA_TAG_CACHE[key] = {"ts": time.time(), "tags": []}
|
|
12347
|
+
|
|
12348
|
+
|
|
12349
|
+
def list_ollama_models_cached(
|
|
12350
|
+
base_url: str,
|
|
12351
|
+
ttl_seconds: int = 30,
|
|
12352
|
+
force_refresh: bool = False,
|
|
12353
|
+
*,
|
|
12354
|
+
background: bool = False,
|
|
12355
|
+
) -> list[str]:
|
|
12356
|
+
"""Return cached Ollama tags, optionally refreshing without blocking.
|
|
12357
|
+
|
|
12358
|
+
UI/catalog reads use ``background=True`` so an unavailable local daemon
|
|
12359
|
+
cannot delay the first render. Explicit scan endpoints keep the historical
|
|
12360
|
+
synchronous behaviour by leaving it false.
|
|
12361
|
+
"""
|
|
12171
12362
|
key = str(base_url or "").rstrip("/")
|
|
12172
12363
|
now = time.time()
|
|
12173
12364
|
cached_tags: list[str] = []
|
|
@@ -12186,25 +12377,19 @@ def list_ollama_models_cached(base_url: str, ttl_seconds: int = 30, force_refres
|
|
|
12186
12377
|
row = {"ts": 0.0, "tags": []}
|
|
12187
12378
|
row["_fetching"] = True
|
|
12188
12379
|
_OLLAMA_TAG_CACHE[key] = row
|
|
12189
|
-
|
|
12190
|
-
|
|
12191
|
-
|
|
12192
|
-
|
|
12193
|
-
|
|
12194
|
-
|
|
12195
|
-
|
|
12196
|
-
_OLLAMA_TAG_CACHE[key] = {"ts": time.time(), "tags": list(tags)}
|
|
12197
|
-
else:
|
|
12198
|
-
# Clear fetching flag, keep old cache if available
|
|
12199
|
-
row_now = _OLLAMA_TAG_CACHE.get(key, {})
|
|
12200
|
-
row_now.pop("_fetching", None)
|
|
12201
|
-
if not cached_tags:
|
|
12202
|
-
_OLLAMA_TAG_CACHE[key] = {"ts": time.time(), "tags": []}
|
|
12203
|
-
if ok:
|
|
12204
|
-
return list(tags)
|
|
12205
|
-
if cached_tags:
|
|
12380
|
+
if background:
|
|
12381
|
+
threading.Thread(
|
|
12382
|
+
target=_fetch_ollama_models_cached,
|
|
12383
|
+
args=(key, base_url, cached_tags),
|
|
12384
|
+
name="ollama-model-probe",
|
|
12385
|
+
daemon=True,
|
|
12386
|
+
).start()
|
|
12206
12387
|
return list(cached_tags)
|
|
12207
|
-
|
|
12388
|
+
_fetch_ollama_models_cached(key, base_url, cached_tags)
|
|
12389
|
+
with _OLLAMA_TAG_CACHE_LOCK:
|
|
12390
|
+
row = _OLLAMA_TAG_CACHE.get(key, {})
|
|
12391
|
+
tags = row.get("tags", []) if isinstance(row, dict) else []
|
|
12392
|
+
return list(tags) if isinstance(tags, list) else list(cached_tags)
|
|
12208
12393
|
|
|
12209
12394
|
def resolve_ollama_model(base_url: str, preferred: str) -> str:
|
|
12210
12395
|
models = list_ollama_models(base_url)
|
|
@@ -12619,39 +12804,45 @@ def clamp_effort(effort: str, *, ceiling: str = EFFORT_MAX, floor: str = EFFORT_
|
|
|
12619
12804
|
return EFFORT_LEVELS[min(max(EFFORT_ORDER[e], lo), hi)]
|
|
12620
12805
|
|
|
12621
12806
|
|
|
12622
|
-
def model_reasoning_style(
|
|
12807
|
+
def model_reasoning_style(
|
|
12808
|
+
provider: str,
|
|
12809
|
+
model: str,
|
|
12810
|
+
capabilities: dict | None = None,
|
|
12811
|
+
*,
|
|
12812
|
+
capabilities_probed: bool = False,
|
|
12813
|
+
) -> str:
|
|
12623
12814
|
"""Return which reasoning dialect a (provider, model) pair speaks.
|
|
12624
12815
|
|
|
12625
12816
|
One of: "anthropic" | "openai" | "deepseek" | "glm" | "ollama" | "none".
|
|
12626
12817
|
Conservative: only models known to support reasoning return a non-"none"
|
|
12627
12818
|
style, so unknown models are never sent reasoning fields.
|
|
12628
12819
|
"""
|
|
12629
|
-
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
|
|
12634
|
-
|
|
12635
|
-
|
|
12636
|
-
|
|
12637
|
-
|
|
12638
|
-
|
|
12639
|
-
|
|
12640
|
-
|
|
12641
|
-
|
|
12642
|
-
|
|
12643
|
-
|
|
12644
|
-
|
|
12645
|
-
|
|
12646
|
-
|
|
12647
|
-
|
|
12648
|
-
|
|
12649
|
-
|
|
12650
|
-
|
|
12651
|
-
|
|
12652
|
-
or "gpt-5" in m or "/o1" in m or "/o3" in m or "/o4" in m
|
|
12653
|
-
or "reasoning" in m):
|
|
12820
|
+
# Probe metadata is authoritative. A model name is never evidence of a
|
|
12821
|
+
# reasoning API: providers may alias, fine-tune, or proxy models freely.
|
|
12822
|
+
if isinstance(capabilities, dict):
|
|
12823
|
+
explicit_style = str(
|
|
12824
|
+
capabilities.get("reasoning_style")
|
|
12825
|
+
or capabilities.get("reasoning_dialect")
|
|
12826
|
+
or ""
|
|
12827
|
+
).strip().lower()
|
|
12828
|
+
supported = capabilities.get("reasoning_supported")
|
|
12829
|
+
if explicit_style in {"anthropic", "openai", "deepseek", "glm", "ollama", "none"}:
|
|
12830
|
+
return explicit_style
|
|
12831
|
+
if supported is False:
|
|
12832
|
+
return "none"
|
|
12833
|
+
if supported is True:
|
|
12834
|
+
# A provider can advertise reasoning without a dialect; use its
|
|
12835
|
+
# transport dialect rather than guessing from the model name.
|
|
12836
|
+
raw_provider = str(provider or "").strip().lower()
|
|
12837
|
+
if raw_provider == "anthropic":
|
|
12838
|
+
return "anthropic"
|
|
12839
|
+
if raw_provider == "ollama":
|
|
12840
|
+
return "ollama"
|
|
12841
|
+
if normalize_openai_compat_provider_name(provider) == "glm":
|
|
12842
|
+
return "glm"
|
|
12654
12843
|
return "openai"
|
|
12844
|
+
if supported is None and not explicit_style:
|
|
12845
|
+
return "none"
|
|
12655
12846
|
return "none"
|
|
12656
12847
|
return "none"
|
|
12657
12848
|
|
|
@@ -12662,6 +12853,7 @@ def resolve_reasoning_payload(
|
|
|
12662
12853
|
effort: str,
|
|
12663
12854
|
*,
|
|
12664
12855
|
max_tokens: int = 2000,
|
|
12856
|
+
capabilities: dict | None = None,
|
|
12665
12857
|
) -> dict:
|
|
12666
12858
|
"""Map (provider, model, effort) -> concrete request mutations.
|
|
12667
12859
|
|
|
@@ -12676,7 +12868,7 @@ def resolve_reasoning_payload(
|
|
|
12676
12868
|
eff = str(effort or EFFORT_OFF).strip().lower()
|
|
12677
12869
|
if eff not in EFFORT_ORDER or eff == EFFORT_OFF:
|
|
12678
12870
|
return {}
|
|
12679
|
-
style = model_reasoning_style(provider, model)
|
|
12871
|
+
style = model_reasoning_style(provider, model, capabilities)
|
|
12680
12872
|
if style == "none":
|
|
12681
12873
|
return {}
|
|
12682
12874
|
if style == "anthropic":
|
|
@@ -12707,13 +12899,19 @@ def resolve_reasoning_payload(
|
|
|
12707
12899
|
return {}
|
|
12708
12900
|
|
|
12709
12901
|
def openai_compat_probe_headers(provider: str, api_key: str = "") -> dict[str, str]:
|
|
12902
|
+
raw_provider = str(provider or "").strip().lower().replace("-", "_")
|
|
12710
12903
|
normalized = normalize_openai_compat_provider_name(provider)
|
|
12711
12904
|
headers = {
|
|
12712
12905
|
"Accept": "application/json",
|
|
12713
12906
|
"User-Agent": "Clouds-Coder/1.0",
|
|
12714
12907
|
}
|
|
12715
12908
|
if str(api_key or "").strip():
|
|
12716
|
-
|
|
12909
|
+
if raw_provider == "anthropic":
|
|
12910
|
+
headers["x-api-key"] = str(api_key).strip()
|
|
12911
|
+
else:
|
|
12912
|
+
headers["Authorization"] = f"Bearer {str(api_key).strip()}"
|
|
12913
|
+
if raw_provider == "anthropic":
|
|
12914
|
+
headers["anthropic-version"] = "2023-06-01"
|
|
12717
12915
|
if normalized == "openrouter":
|
|
12718
12916
|
headers.setdefault("HTTP-Referer", "https://clouds-coder.local")
|
|
12719
12917
|
headers.setdefault("X-Title", "Clouds Coder")
|
|
@@ -12753,6 +12951,21 @@ def openai_compat_model_list_urls(endpoint_or_base: str, provider: str = "") ->
|
|
|
12753
12951
|
out.append(url)
|
|
12754
12952
|
return out
|
|
12755
12953
|
|
|
12954
|
+
|
|
12955
|
+
def anthropic_model_list_url(endpoint_or_base: str) -> str:
|
|
12956
|
+
"""Return the Anthropic Models API URL for an API base or Messages URL."""
|
|
12957
|
+
base = str(endpoint_or_base or "").strip().rstrip("/")
|
|
12958
|
+
if not base:
|
|
12959
|
+
return ""
|
|
12960
|
+
low = base.lower()
|
|
12961
|
+
for suffix in ("/v1/messages", "/messages"):
|
|
12962
|
+
if low.endswith(suffix):
|
|
12963
|
+
base = base[: -len(suffix)].rstrip("/")
|
|
12964
|
+
break
|
|
12965
|
+
if base.lower().endswith("/v1"):
|
|
12966
|
+
return base + "/models"
|
|
12967
|
+
return base + "/v1/models"
|
|
12968
|
+
|
|
12756
12969
|
def extract_openai_compat_model_ids(payload: object) -> list[str]:
|
|
12757
12970
|
out: list[str] = []
|
|
12758
12971
|
seen: set[str] = set()
|
|
@@ -12788,6 +13001,406 @@ def extract_openai_compat_model_ids(payload: object) -> list[str]:
|
|
|
12788
13001
|
_walk(payload, 0)
|
|
12789
13002
|
return out
|
|
12790
13003
|
|
|
13004
|
+
|
|
13005
|
+
def extract_openai_compat_model_records(payload: object) -> list[dict]:
|
|
13006
|
+
"""Extract model ids plus conservative capability hints from /models."""
|
|
13007
|
+
records: list[dict] = []
|
|
13008
|
+
seen: set[str] = set()
|
|
13009
|
+
|
|
13010
|
+
def capability_hints(node: dict) -> dict:
|
|
13011
|
+
caps: dict = {}
|
|
13012
|
+
for key in ("reasoning_supported", "supports_reasoning", "reasoning", "thinking"):
|
|
13013
|
+
if key in node:
|
|
13014
|
+
raw = node.get(key)
|
|
13015
|
+
if isinstance(raw, bool):
|
|
13016
|
+
caps["reasoning_supported"] = raw
|
|
13017
|
+
break
|
|
13018
|
+
if isinstance(raw, (list, tuple, set)):
|
|
13019
|
+
caps["reasoning_supported"] = bool(raw)
|
|
13020
|
+
break
|
|
13021
|
+
if isinstance(raw, str) and raw.strip().lower() in {
|
|
13022
|
+
"true", "yes", "on", "enabled", "supported"
|
|
13023
|
+
}:
|
|
13024
|
+
caps["reasoning_supported"] = True
|
|
13025
|
+
break
|
|
13026
|
+
style = node.get("reasoning_style") or node.get("reasoning_dialect")
|
|
13027
|
+
if isinstance(style, str) and style.strip():
|
|
13028
|
+
normalized_style = style.strip().lower()
|
|
13029
|
+
if normalized_style in {"reasoning_effort", "reasoning-effort", "reasoning"}:
|
|
13030
|
+
normalized_style = "openai"
|
|
13031
|
+
caps["reasoning_style"] = normalized_style
|
|
13032
|
+
caps.setdefault("reasoning_supported", True)
|
|
13033
|
+
for key in ("supported_parameters", "supported_features", "features"):
|
|
13034
|
+
values = node.get(key)
|
|
13035
|
+
if isinstance(values, dict):
|
|
13036
|
+
values = values.keys()
|
|
13037
|
+
if isinstance(values, (list, tuple, set)):
|
|
13038
|
+
names = {str(value or "").strip().lower() for value in values}
|
|
13039
|
+
if any(
|
|
13040
|
+
any(token in value for token in ("reasoning", "thinking"))
|
|
13041
|
+
for value in names
|
|
13042
|
+
):
|
|
13043
|
+
caps.setdefault("reasoning_supported", True)
|
|
13044
|
+
if any("reasoning_effort" in value for value in names):
|
|
13045
|
+
caps.setdefault("reasoning_style", "openai")
|
|
13046
|
+
break
|
|
13047
|
+
raw_caps = node.get("capabilities")
|
|
13048
|
+
if isinstance(raw_caps, dict):
|
|
13049
|
+
nested = capability_hints(raw_caps)
|
|
13050
|
+
caps.update(nested)
|
|
13051
|
+
elif isinstance(raw_caps, (list, tuple, set)):
|
|
13052
|
+
nested = capability_hints({"supported_features": raw_caps})
|
|
13053
|
+
caps.update(nested)
|
|
13054
|
+
return caps
|
|
13055
|
+
|
|
13056
|
+
def walk(node: object, depth: int = 0):
|
|
13057
|
+
if depth > 5 or node is None:
|
|
13058
|
+
return
|
|
13059
|
+
if isinstance(node, list):
|
|
13060
|
+
for item in node:
|
|
13061
|
+
walk(item, depth + 1)
|
|
13062
|
+
return
|
|
13063
|
+
if not isinstance(node, dict):
|
|
13064
|
+
return
|
|
13065
|
+
model_id = ""
|
|
13066
|
+
for key in ("id", "model", "name"):
|
|
13067
|
+
value = node.get(key)
|
|
13068
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
13069
|
+
model_id = trim(str(value).strip(), 240)
|
|
13070
|
+
break
|
|
13071
|
+
if model_id and model_id.lower() not in {"list", "model", "models", "object", "data"}:
|
|
13072
|
+
low = model_id.lower()
|
|
13073
|
+
if low not in seen:
|
|
13074
|
+
seen.add(low)
|
|
13075
|
+
caps = capability_hints(node)
|
|
13076
|
+
records.append({"id": model_id, "capabilities": caps, "raw": node})
|
|
13077
|
+
for key in ("data", "models", "items", "result", "results", "value"):
|
|
13078
|
+
if key in node:
|
|
13079
|
+
walk(node.get(key), depth + 1)
|
|
13080
|
+
|
|
13081
|
+
walk(payload)
|
|
13082
|
+
return records
|
|
13083
|
+
|
|
13084
|
+
|
|
13085
|
+
_PROVIDER_MODEL_CACHE_LOCK = threading.Lock()
|
|
13086
|
+
_PROVIDER_MODEL_CACHE: dict[str, dict] = {}
|
|
13087
|
+
|
|
13088
|
+
|
|
13089
|
+
def _fetch_provider_models_cached(
|
|
13090
|
+
key: str,
|
|
13091
|
+
profile: dict,
|
|
13092
|
+
cached_models: list[dict],
|
|
13093
|
+
) -> None:
|
|
13094
|
+
"""Fetch provider models and publish one cache snapshot."""
|
|
13095
|
+
if not isinstance(profile, dict):
|
|
13096
|
+
return
|
|
13097
|
+
provider = str(profile.get("provider", "") or "").strip().lower()
|
|
13098
|
+
base_url = str(profile.get("base_url", "") or "").strip().rstrip("/")
|
|
13099
|
+
endpoint = str(profile.get("endpoint", "") or "").strip()
|
|
13100
|
+
api_key = str(profile.get("api_key", "") or "").strip()
|
|
13101
|
+
custom_headers = profile.get("headers", {})
|
|
13102
|
+
custom_headers = (
|
|
13103
|
+
{str(k): str(v) for k, v in custom_headers.items() if str(k).strip()}
|
|
13104
|
+
if isinstance(custom_headers, dict)
|
|
13105
|
+
else {}
|
|
13106
|
+
)
|
|
13107
|
+
records: list[dict] = []
|
|
13108
|
+
try:
|
|
13109
|
+
if provider == "ollama" and base_url:
|
|
13110
|
+
records = probe_ollama_model_records(base_url, timeout=1.5)
|
|
13111
|
+
elif provider == "anthropic":
|
|
13112
|
+
models_url = anthropic_model_list_url(base_url or endpoint)
|
|
13113
|
+
headers = openai_compat_probe_headers(provider, api_key)
|
|
13114
|
+
headers.update(custom_headers)
|
|
13115
|
+
seen: set[str] = set()
|
|
13116
|
+
after_id = ""
|
|
13117
|
+
for _page in range(20):
|
|
13118
|
+
if not models_url:
|
|
13119
|
+
break
|
|
13120
|
+
separator = "&" if "?" in models_url else "?"
|
|
13121
|
+
page_url = f"{models_url}{separator}limit=1000"
|
|
13122
|
+
if after_id:
|
|
13123
|
+
page_url += f"&after_id={quote(after_id, safe='')}"
|
|
13124
|
+
req = Request(page_url, method="GET", headers=headers)
|
|
13125
|
+
with urlopen(req, timeout=3) as resp:
|
|
13126
|
+
payload = json.loads(resp.read().decode("utf-8", errors="replace"))
|
|
13127
|
+
page_records = extract_openai_compat_model_records(payload)
|
|
13128
|
+
for record in page_records:
|
|
13129
|
+
model_id = str(record.get("id", "") or "").strip()
|
|
13130
|
+
if model_id and model_id not in seen:
|
|
13131
|
+
seen.add(model_id)
|
|
13132
|
+
records.append(record)
|
|
13133
|
+
if not isinstance(payload, dict) or not bool(payload.get("has_more")):
|
|
13134
|
+
break
|
|
13135
|
+
next_after = str(payload.get("last_id", "") or "").strip()
|
|
13136
|
+
if not next_after or next_after == after_id:
|
|
13137
|
+
break
|
|
13138
|
+
after_id = next_after
|
|
13139
|
+
elif is_openai_compat_provider(provider) or provider == "custom_http":
|
|
13140
|
+
headers = openai_compat_probe_headers(provider, api_key)
|
|
13141
|
+
headers.update(custom_headers)
|
|
13142
|
+
for url in openai_compat_model_list_urls(base_url or endpoint, provider):
|
|
13143
|
+
try:
|
|
13144
|
+
req = Request(url, method="GET", headers=headers)
|
|
13145
|
+
with urlopen(req, timeout=3) as resp:
|
|
13146
|
+
payload = json.loads(resp.read().decode("utf-8", errors="replace"))
|
|
13147
|
+
records = extract_openai_compat_model_records(payload)
|
|
13148
|
+
if records:
|
|
13149
|
+
break
|
|
13150
|
+
except Exception:
|
|
13151
|
+
continue
|
|
13152
|
+
except Exception:
|
|
13153
|
+
records = []
|
|
13154
|
+
finally:
|
|
13155
|
+
with _PROVIDER_MODEL_CACHE_LOCK:
|
|
13156
|
+
previous = _PROVIDER_MODEL_CACHE.get(key, {})
|
|
13157
|
+
_PROVIDER_MODEL_CACHE[key] = {
|
|
13158
|
+
"ts": time.time(),
|
|
13159
|
+
"models": records or previous.get("models", cached_models),
|
|
13160
|
+
}
|
|
13161
|
+
|
|
13162
|
+
|
|
13163
|
+
def probe_provider_models(
|
|
13164
|
+
profile: dict,
|
|
13165
|
+
*,
|
|
13166
|
+
force_refresh: bool = False,
|
|
13167
|
+
ttl_seconds: int = 30,
|
|
13168
|
+
background: bool = False,
|
|
13169
|
+
cached_only: bool = False,
|
|
13170
|
+
) -> list[dict]:
|
|
13171
|
+
"""Return cached provider models and optionally refresh them in the background.
|
|
13172
|
+
|
|
13173
|
+
Direct callers retain synchronous probing by default. Model catalog reads
|
|
13174
|
+
pass ``background=True`` so a cold network cache never blocks a UI request.
|
|
13175
|
+
"""
|
|
13176
|
+
if not isinstance(profile, dict):
|
|
13177
|
+
return []
|
|
13178
|
+
provider = str(profile.get("provider", "") or "").strip().lower()
|
|
13179
|
+
base_url = str(profile.get("base_url", "") or "").strip().rstrip("/")
|
|
13180
|
+
endpoint = str(profile.get("endpoint", "") or "").strip()
|
|
13181
|
+
api_key = str(profile.get("api_key", "") or "").strip()
|
|
13182
|
+
raw_headers = profile.get("headers", {})
|
|
13183
|
+
header_secret = (
|
|
13184
|
+
json.dumps(
|
|
13185
|
+
{str(k): str(v) for k, v in raw_headers.items()},
|
|
13186
|
+
sort_keys=True,
|
|
13187
|
+
ensure_ascii=False,
|
|
13188
|
+
)
|
|
13189
|
+
if isinstance(raw_headers, dict)
|
|
13190
|
+
else ""
|
|
13191
|
+
)
|
|
13192
|
+
auth_material = f"{api_key}\0{header_secret}"
|
|
13193
|
+
key_fingerprint = (
|
|
13194
|
+
hashlib.sha256(auth_material.encode("utf-8")).hexdigest()[:12]
|
|
13195
|
+
if api_key or header_secret
|
|
13196
|
+
else "-"
|
|
13197
|
+
)
|
|
13198
|
+
key = hashlib.sha256(
|
|
13199
|
+
f"{provider}|{base_url}|{endpoint}|{key_fingerprint}".encode()
|
|
13200
|
+
).hexdigest()[:24]
|
|
13201
|
+
now = time.time()
|
|
13202
|
+
with _PROVIDER_MODEL_CACHE_LOCK:
|
|
13203
|
+
cached = _PROVIDER_MODEL_CACHE.get(key, {})
|
|
13204
|
+
cached_models = [
|
|
13205
|
+
dict(x) for x in cached.get("models", []) if isinstance(x, dict)
|
|
13206
|
+
]
|
|
13207
|
+
if cached_only:
|
|
13208
|
+
return cached_models
|
|
13209
|
+
if cached and not force_refresh and now - float(cached.get("ts", 0.0) or 0.0) <= ttl_seconds:
|
|
13210
|
+
return cached_models
|
|
13211
|
+
if cached.get("fetching"):
|
|
13212
|
+
return cached_models
|
|
13213
|
+
cached["fetching"] = True
|
|
13214
|
+
_PROVIDER_MODEL_CACHE[key] = cached
|
|
13215
|
+
profile_snapshot = dict(profile)
|
|
13216
|
+
if background:
|
|
13217
|
+
threading.Thread(
|
|
13218
|
+
target=_fetch_provider_models_cached,
|
|
13219
|
+
args=(key, profile_snapshot, cached_models),
|
|
13220
|
+
name=f"model-probe-{provider or 'provider'}",
|
|
13221
|
+
daemon=True,
|
|
13222
|
+
).start()
|
|
13223
|
+
return cached_models
|
|
13224
|
+
_fetch_provider_models_cached(key, profile_snapshot, cached_models)
|
|
13225
|
+
with _PROVIDER_MODEL_CACHE_LOCK:
|
|
13226
|
+
row = _PROVIDER_MODEL_CACHE.get(key, {})
|
|
13227
|
+
models = row.get("models", []) if isinstance(row, dict) else []
|
|
13228
|
+
return [dict(x) for x in models if isinstance(x, dict)]
|
|
13229
|
+
|
|
13230
|
+
|
|
13231
|
+
MODEL_RUNTIME_SETTING_KEYS = {
|
|
13232
|
+
"effort", "max_effort", "thinking_stream", "response_stream",
|
|
13233
|
+
"reasoning_supported", "reasoning_style", "temperature", "request_timeout",
|
|
13234
|
+
}
|
|
13235
|
+
|
|
13236
|
+
|
|
13237
|
+
def merge_probed_models_into_profile(
|
|
13238
|
+
profile: dict,
|
|
13239
|
+
records: object = None,
|
|
13240
|
+
model_ids: object = None,
|
|
13241
|
+
) -> bool:
|
|
13242
|
+
"""Persist the provider's discovered model directory and per-model hints."""
|
|
13243
|
+
if not isinstance(profile, dict):
|
|
13244
|
+
return False
|
|
13245
|
+
existing = [str(value).strip() for value in profile.get("models", []) if str(value).strip()]
|
|
13246
|
+
ids = list(existing)
|
|
13247
|
+
for raw in model_ids or []:
|
|
13248
|
+
value = str(raw or "").strip()
|
|
13249
|
+
if value and value not in ids:
|
|
13250
|
+
ids.append(value)
|
|
13251
|
+
settings = dict(profile.get("model_settings", {}) or {}) if isinstance(profile.get("model_settings"), dict) else {}
|
|
13252
|
+
for record in records or []:
|
|
13253
|
+
if not isinstance(record, dict):
|
|
13254
|
+
continue
|
|
13255
|
+
model_id = str(record.get("id") or record.get("model") or record.get("name") or "").strip()
|
|
13256
|
+
if not model_id:
|
|
13257
|
+
continue
|
|
13258
|
+
if model_id not in ids:
|
|
13259
|
+
ids.append(model_id)
|
|
13260
|
+
caps = record.get("capabilities") if isinstance(record.get("capabilities"), dict) else {}
|
|
13261
|
+
if not isinstance(caps, dict):
|
|
13262
|
+
caps = {}
|
|
13263
|
+
if caps.get("reasoning_style") is None and caps.get("reasoning_dialect"):
|
|
13264
|
+
caps = dict(caps)
|
|
13265
|
+
caps["reasoning_style"] = caps.get("reasoning_dialect")
|
|
13266
|
+
normalized = normalize_model_runtime_settings(caps)
|
|
13267
|
+
if normalized:
|
|
13268
|
+
prior = normalize_model_runtime_settings(settings.get(model_id, {}))
|
|
13269
|
+
settings[model_id] = {**prior, **normalized}
|
|
13270
|
+
changed = ids != existing or settings != (profile.get("model_settings") if isinstance(profile.get("model_settings"), dict) else {})
|
|
13271
|
+
if ids:
|
|
13272
|
+
profile["models"] = ids
|
|
13273
|
+
if not str(profile.get("model", "") or "").strip() or str(profile.get("model", "")).strip().lower() in {"auto", "custom-model"}:
|
|
13274
|
+
profile["model"] = ids[0]
|
|
13275
|
+
profile["selection"] = f"{profile.get('id', '')}::{profile.get('model', '')}"
|
|
13276
|
+
if settings:
|
|
13277
|
+
profile["model_settings"] = settings
|
|
13278
|
+
return changed
|
|
13279
|
+
|
|
13280
|
+
|
|
13281
|
+
def probe_and_merge_model_profiles(
|
|
13282
|
+
profiles: object,
|
|
13283
|
+
*,
|
|
13284
|
+
force_refresh: bool = True,
|
|
13285
|
+
) -> bool:
|
|
13286
|
+
"""Probe every configured provider and merge its complete model directory.
|
|
13287
|
+
|
|
13288
|
+
Import/reset paths use synchronous probing so a newly imported profile is
|
|
13289
|
+
immediately usable with every model the provider exposes. Network errors
|
|
13290
|
+
leave the explicitly configured models intact; the catalog can retry later.
|
|
13291
|
+
"""
|
|
13292
|
+
if isinstance(profiles, dict):
|
|
13293
|
+
rows = list(profiles.values())
|
|
13294
|
+
elif isinstance(profiles, list):
|
|
13295
|
+
rows = profiles
|
|
13296
|
+
else:
|
|
13297
|
+
return False
|
|
13298
|
+
valid_rows = [profile for profile in rows if isinstance(profile, dict)]
|
|
13299
|
+
if not valid_rows:
|
|
13300
|
+
return False
|
|
13301
|
+
records_by_index: dict[int, list[dict]] = {}
|
|
13302
|
+
|
|
13303
|
+
def _probe(profile: dict) -> list[dict]:
|
|
13304
|
+
try:
|
|
13305
|
+
return probe_provider_models(
|
|
13306
|
+
profile,
|
|
13307
|
+
force_refresh=bool(force_refresh),
|
|
13308
|
+
background=False,
|
|
13309
|
+
)
|
|
13310
|
+
except Exception:
|
|
13311
|
+
return []
|
|
13312
|
+
|
|
13313
|
+
# Providers are independent network services. Probe them concurrently so
|
|
13314
|
+
# an unavailable vendor contributes one timeout instead of serially
|
|
13315
|
+
# delaying every other imported profile.
|
|
13316
|
+
with concurrent.futures.ThreadPoolExecutor(
|
|
13317
|
+
max_workers=min(8, len(valid_rows)),
|
|
13318
|
+
thread_name_prefix="provider-model-import",
|
|
13319
|
+
) as executor:
|
|
13320
|
+
future_map = {
|
|
13321
|
+
executor.submit(_probe, profile): index
|
|
13322
|
+
for index, profile in enumerate(valid_rows)
|
|
13323
|
+
}
|
|
13324
|
+
for future in concurrent.futures.as_completed(future_map):
|
|
13325
|
+
index = future_map[future]
|
|
13326
|
+
try:
|
|
13327
|
+
records_by_index[index] = future.result()
|
|
13328
|
+
except Exception:
|
|
13329
|
+
records_by_index[index] = []
|
|
13330
|
+
|
|
13331
|
+
changed = False
|
|
13332
|
+
for index, profile in enumerate(valid_rows):
|
|
13333
|
+
changed = merge_probed_models_into_profile(
|
|
13334
|
+
profile, records_by_index.get(index, [])
|
|
13335
|
+
) or changed
|
|
13336
|
+
return changed
|
|
13337
|
+
|
|
13338
|
+
|
|
13339
|
+
def normalize_model_runtime_settings(raw: object) -> dict:
|
|
13340
|
+
if not isinstance(raw, dict):
|
|
13341
|
+
return {}
|
|
13342
|
+
out: dict = {}
|
|
13343
|
+
for key in MODEL_RUNTIME_SETTING_KEYS:
|
|
13344
|
+
if key not in raw:
|
|
13345
|
+
continue
|
|
13346
|
+
value = raw.get(key)
|
|
13347
|
+
if key in {"thinking_stream", "response_stream", "reasoning_supported"}:
|
|
13348
|
+
if key == "reasoning_supported" and value is None:
|
|
13349
|
+
continue
|
|
13350
|
+
out[key] = _to_bool_like(value, default=False)
|
|
13351
|
+
elif key in {"effort", "max_effort", "reasoning_style"}:
|
|
13352
|
+
text = str(value or "").strip().lower()
|
|
13353
|
+
if key == "reasoning_style" or text in EFFORT_ORDER or not text:
|
|
13354
|
+
out[key] = text
|
|
13355
|
+
elif key == "temperature":
|
|
13356
|
+
try:
|
|
13357
|
+
out[key] = max(0.0, min(2.0, float(value)))
|
|
13358
|
+
except Exception:
|
|
13359
|
+
continue
|
|
13360
|
+
elif key == "request_timeout":
|
|
13361
|
+
out[key] = normalize_timeout_seconds(value, minimum=MIN_TIMEOUT_SECONDS, maximum=MAX_TIMEOUT_SECONDS, fallback=DEFAULT_REQUEST_TIMEOUT)
|
|
13362
|
+
return out
|
|
13363
|
+
|
|
13364
|
+
|
|
13365
|
+
def apply_model_runtime_settings(profile: dict, model: str, settings: object = None) -> dict:
|
|
13366
|
+
row = dict(profile or {})
|
|
13367
|
+
target = str(model or row.get("model", "") or "").strip()
|
|
13368
|
+
per_model = row.get("model_settings")
|
|
13369
|
+
if not isinstance(per_model, dict):
|
|
13370
|
+
per_model = {}
|
|
13371
|
+
merged = normalize_model_runtime_settings(per_model.get(target, {}))
|
|
13372
|
+
merged.update(normalize_model_runtime_settings(settings))
|
|
13373
|
+
if merged:
|
|
13374
|
+
per_model[target] = dict(merged)
|
|
13375
|
+
row["model_settings"] = per_model
|
|
13376
|
+
row["model"] = target or str(row.get("model", "") or "")
|
|
13377
|
+
row["selection"] = f"{row.get('id', '')}::{row.get('model', '')}"
|
|
13378
|
+
return row
|
|
13379
|
+
|
|
13380
|
+
|
|
13381
|
+
def model_runtime_settings_for(profile: dict, model: str = "") -> dict:
|
|
13382
|
+
"""Return the effective settings for one model without exposing secrets."""
|
|
13383
|
+
row = profile if isinstance(profile, dict) else {}
|
|
13384
|
+
target = str(model or row.get("model", "") or "").strip()
|
|
13385
|
+
out = normalize_model_runtime_settings(row)
|
|
13386
|
+
per_model = row.get("model_settings")
|
|
13387
|
+
if isinstance(per_model, dict):
|
|
13388
|
+
out.update(normalize_model_runtime_settings(per_model.get(target, {})))
|
|
13389
|
+
return out
|
|
13390
|
+
|
|
13391
|
+
|
|
13392
|
+
def apply_model_option_runtime_fields(option: dict, profile: dict, model: str = "") -> dict:
|
|
13393
|
+
"""Overlay per-model runtime settings on a public catalog option."""
|
|
13394
|
+
target = str(model or option.get("model", "") or "").strip()
|
|
13395
|
+
settings = model_runtime_settings_for(profile, target)
|
|
13396
|
+
for key in MODEL_RUNTIME_SETTING_KEYS:
|
|
13397
|
+
if key in settings:
|
|
13398
|
+
option[key] = settings[key]
|
|
13399
|
+
if isinstance(profile, dict):
|
|
13400
|
+
option.setdefault("display_name", str(profile.get("display_name", profile.get("label", "")) or ""))
|
|
13401
|
+
option.setdefault("title", str(profile.get("title", profile.get("label", "")) or ""))
|
|
13402
|
+
return option
|
|
13403
|
+
|
|
12791
13404
|
# ============================================================================
|
|
12792
13405
|
# Architecture / 架构 / アーキテクチャ
|
|
12793
13406
|
# Layer 2: Model/provider profile loading and normalization.
|
|
@@ -12863,6 +13476,19 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
12863
13476
|
if not isinstance(model_caps_map, dict):
|
|
12864
13477
|
model_caps_map = {}
|
|
12865
13478
|
|
|
13479
|
+
# Reasoning metadata is kept separate from multimodal capabilities. The
|
|
13480
|
+
# importer writes this map from provider model probes, and it must survive
|
|
13481
|
+
# a config round-trip so model-specific effort controls remain available
|
|
13482
|
+
# after restart.
|
|
13483
|
+
raw_reasoning_map = config.get(
|
|
13484
|
+
"model_reasoning_capabilities",
|
|
13485
|
+
config.get("reasoning_capabilities", {}),
|
|
13486
|
+
)
|
|
13487
|
+
if isinstance(raw_reasoning_map, str):
|
|
13488
|
+
raw_reasoning_map = parse_json_object(raw_reasoning_map, {})
|
|
13489
|
+
if not isinstance(raw_reasoning_map, dict):
|
|
13490
|
+
raw_reasoning_map = {}
|
|
13491
|
+
|
|
12866
13492
|
raw_global_caps = config.get("multimodal_capabilities", config.get("capabilities", {}))
|
|
12867
13493
|
if isinstance(raw_global_caps, str):
|
|
12868
13494
|
raw_global_caps = parse_json_object(raw_global_caps, {})
|
|
@@ -12963,6 +13589,30 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
12963
13589
|
return {str(k): str(v) for k, v in parsed.items()}
|
|
12964
13590
|
return {}
|
|
12965
13591
|
|
|
13592
|
+
def parse_profile_model_settings(raw: object, model_ids: list[str] | None = None) -> dict:
|
|
13593
|
+
"""Normalize per-model runtime settings from config without secrets."""
|
|
13594
|
+
if isinstance(raw, str):
|
|
13595
|
+
raw = parse_json_object(raw, {})
|
|
13596
|
+
if not isinstance(raw, dict):
|
|
13597
|
+
return {}
|
|
13598
|
+
settings: dict[str, dict] = {}
|
|
13599
|
+
# A flat settings object applies to the profile's default model.
|
|
13600
|
+
if any(key in MODEL_RUNTIME_SETTING_KEYS for key in raw):
|
|
13601
|
+
target = str((model_ids or [""])[0] or "").strip()
|
|
13602
|
+
if target:
|
|
13603
|
+
normalized = normalize_model_runtime_settings(raw)
|
|
13604
|
+
if normalized:
|
|
13605
|
+
settings[target] = normalized
|
|
13606
|
+
return settings
|
|
13607
|
+
for target, value in raw.items():
|
|
13608
|
+
model_id = str(target or "").strip()
|
|
13609
|
+
if not model_id or not isinstance(value, dict):
|
|
13610
|
+
continue
|
|
13611
|
+
normalized = normalize_model_runtime_settings(value)
|
|
13612
|
+
if normalized:
|
|
13613
|
+
settings[model_id] = normalized
|
|
13614
|
+
return settings
|
|
13615
|
+
|
|
12966
13616
|
def add_profile(
|
|
12967
13617
|
out: list[dict],
|
|
12968
13618
|
*,
|
|
@@ -12970,6 +13620,9 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
12970
13620
|
provider: str,
|
|
12971
13621
|
label: str,
|
|
12972
13622
|
model: str = "",
|
|
13623
|
+
models: list[str] | None = None,
|
|
13624
|
+
display_name: str = "",
|
|
13625
|
+
title: str = "",
|
|
12973
13626
|
base_url: str = "",
|
|
12974
13627
|
endpoint: str = "",
|
|
12975
13628
|
api_key: str = "",
|
|
@@ -12984,6 +13637,9 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
12984
13637
|
source: str = "config",
|
|
12985
13638
|
effort: str = "",
|
|
12986
13639
|
max_effort: str = "",
|
|
13640
|
+
reasoning_supported: bool | None = None,
|
|
13641
|
+
reasoning_style: str = "",
|
|
13642
|
+
model_settings: dict | None = None,
|
|
12987
13643
|
):
|
|
12988
13644
|
out.append(
|
|
12989
13645
|
{
|
|
@@ -12991,6 +13647,9 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
12991
13647
|
"provider": provider,
|
|
12992
13648
|
"label": label,
|
|
12993
13649
|
"model": (model or "").strip(),
|
|
13650
|
+
"models": [str(x).strip() for x in (models or []) if str(x).strip()],
|
|
13651
|
+
"display_name": str(display_name or label or "").strip(),
|
|
13652
|
+
"title": str(title or label or "").strip(),
|
|
12994
13653
|
"base_url": (base_url or "").strip(),
|
|
12995
13654
|
"endpoint": (endpoint or "").strip(),
|
|
12996
13655
|
"api_key": (api_key or "").strip(),
|
|
@@ -13009,12 +13668,20 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13009
13668
|
"media_endpoints": media_endpoints or {},
|
|
13010
13669
|
"effort": str(effort or "").strip().lower(),
|
|
13011
13670
|
"max_effort": str(max_effort or "").strip().lower(),
|
|
13671
|
+
"reasoning_supported": reasoning_supported,
|
|
13672
|
+
"reasoning_style": str(reasoning_style or "").strip().lower(),
|
|
13673
|
+
"model_settings": {
|
|
13674
|
+
str(model_id).strip(): normalize_model_runtime_settings(values)
|
|
13675
|
+
for model_id, values in (model_settings or {}).items()
|
|
13676
|
+
if str(model_id).strip() and normalize_model_runtime_settings(values)
|
|
13677
|
+
},
|
|
13012
13678
|
"source": source,
|
|
13013
13679
|
}
|
|
13014
13680
|
)
|
|
13015
13681
|
|
|
13016
13682
|
profiles: list[dict] = []
|
|
13017
|
-
|
|
13683
|
+
config_provider = normalize_profile_provider(str(config.get("provider", "")))
|
|
13684
|
+
provider = config_provider
|
|
13018
13685
|
temp = float(config.get("temperature", 0.2) or 0.2)
|
|
13019
13686
|
timeout = normalize_timeout_seconds(
|
|
13020
13687
|
config.get("request_timeout", DEFAULT_REQUEST_TIMEOUT),
|
|
@@ -13098,6 +13765,12 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13098
13765
|
).strip()
|
|
13099
13766
|
if not provider and (base_hint or endpoint):
|
|
13100
13767
|
provider = "openai_compat"
|
|
13768
|
+
# Arbitrary provider names are transported through the custom HTTP
|
|
13769
|
+
# adapter while preserving the user supplied name for display.
|
|
13770
|
+
provider_name = str(provider_hint or "").strip()
|
|
13771
|
+
if provider and provider not in {"ollama", "anthropic", "custom_http"} and not is_openai_like_provider(provider) and (base_hint or endpoint):
|
|
13772
|
+
provider_name = provider_name or provider
|
|
13773
|
+
provider = "custom_http"
|
|
13101
13774
|
if not provider:
|
|
13102
13775
|
continue
|
|
13103
13776
|
profile_id = sanitize_profile_id(
|
|
@@ -13112,9 +13785,12 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13112
13785
|
)
|
|
13113
13786
|
label = str(
|
|
13114
13787
|
raw_profile.get("label")
|
|
13788
|
+
or raw_profile.get("custom_name")
|
|
13115
13789
|
or raw_profile.get("name")
|
|
13116
13790
|
or raw_profile.get("title")
|
|
13791
|
+
or raw_profile.get("custom_title")
|
|
13117
13792
|
or raw_profile.get("display_name")
|
|
13793
|
+
or provider_name
|
|
13118
13794
|
or profile_id
|
|
13119
13795
|
).strip() or profile_id
|
|
13120
13796
|
model = str(
|
|
@@ -13123,8 +13799,32 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13123
13799
|
or raw_profile.get("default_model")
|
|
13124
13800
|
or ""
|
|
13125
13801
|
).strip()
|
|
13802
|
+
raw_models = raw_profile.get("models", raw_profile.get("model_ids", raw_profile.get("available_models", [])))
|
|
13803
|
+
if isinstance(raw_models, str):
|
|
13804
|
+
raw_models = [x.strip() for x in re.split(r"[,\n]", raw_models) if x.strip()]
|
|
13805
|
+
if not isinstance(raw_models, list):
|
|
13806
|
+
raw_models = []
|
|
13807
|
+
model_settings: dict[str, dict] = {}
|
|
13808
|
+
model_values: list[str] = []
|
|
13809
|
+
for value in raw_models:
|
|
13810
|
+
if isinstance(value, dict):
|
|
13811
|
+
model_id = str(value.get("id") or value.get("model") or value.get("name") or "").strip()
|
|
13812
|
+
if not model_id:
|
|
13813
|
+
continue
|
|
13814
|
+
model_values.append(model_id)
|
|
13815
|
+
inline_settings = normalize_model_runtime_settings(value)
|
|
13816
|
+
if inline_settings:
|
|
13817
|
+
model_settings[model_id] = inline_settings
|
|
13818
|
+
elif str(value).strip():
|
|
13819
|
+
model_values.append(str(value).strip())
|
|
13820
|
+
models = list(dict.fromkeys(model_values))
|
|
13821
|
+
if model and model not in models:
|
|
13822
|
+
models.insert(0, model)
|
|
13126
13823
|
if not model:
|
|
13127
|
-
model
|
|
13824
|
+
# A supplied model directory is already authoritative. Do not
|
|
13825
|
+
# prepend a synthetic provider default such as ``custom-model``.
|
|
13826
|
+
model = models[0] if models else default_model_for_provider(provider)
|
|
13827
|
+
model_settings.update(parse_profile_model_settings(raw_profile.get("model_settings"), [model] + models))
|
|
13128
13828
|
base_url = extract_base_url(base_hint or endpoint)
|
|
13129
13829
|
if provider == "ollama" and not base_url:
|
|
13130
13830
|
base_url = extract_base_url(default_ollama_url)
|
|
@@ -13171,6 +13871,20 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13171
13871
|
provider=provider,
|
|
13172
13872
|
label=label,
|
|
13173
13873
|
model=model,
|
|
13874
|
+
models=models,
|
|
13875
|
+
display_name=str(
|
|
13876
|
+
raw_profile.get("display_name")
|
|
13877
|
+
or raw_profile.get("provider_name")
|
|
13878
|
+
or raw_profile.get("custom_name")
|
|
13879
|
+
or (raw_profile.get("name") if provider == "custom_http" else "")
|
|
13880
|
+
or (provider_name if provider_name and provider_name.lower() not in {"custom_http", "openai_compat"} else "")
|
|
13881
|
+
or label
|
|
13882
|
+
),
|
|
13883
|
+
title=str(
|
|
13884
|
+
raw_profile.get("title")
|
|
13885
|
+
or raw_profile.get("custom_title")
|
|
13886
|
+
or label
|
|
13887
|
+
),
|
|
13174
13888
|
base_url=base_url,
|
|
13175
13889
|
endpoint=endpoint,
|
|
13176
13890
|
api_key=api_key,
|
|
@@ -13197,6 +13911,9 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13197
13911
|
media_endpoints=media_endpoints,
|
|
13198
13912
|
effort=str(raw_profile.get("effort", effort_default) or effort_default),
|
|
13199
13913
|
max_effort=str(raw_profile.get("max_effort", max_effort_default) or max_effort_default),
|
|
13914
|
+
reasoning_supported=(raw_profile.get("reasoning_supported") if isinstance(raw_profile.get("reasoning_supported"), bool) else None),
|
|
13915
|
+
reasoning_style=str(raw_profile.get("reasoning_style") or raw_profile.get("reasoning_dialect") or ""),
|
|
13916
|
+
model_settings=model_settings,
|
|
13200
13917
|
source=str(raw_profile.get("source", "profiles") or "profiles"),
|
|
13201
13918
|
)
|
|
13202
13919
|
if bool(raw_profile.get("default")) or bool(raw_profile.get("active")) or bool(raw_profile.get("selected")):
|
|
@@ -13405,17 +14122,60 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13405
14122
|
media_endpoints=build_profile_media_endpoints("openrouter"),
|
|
13406
14123
|
)
|
|
13407
14124
|
|
|
13408
|
-
custom_url = str(
|
|
14125
|
+
custom_url = str(
|
|
14126
|
+
config.get("custom_url")
|
|
14127
|
+
or (config.get("provider_url") if config_provider == "custom_http" else "")
|
|
14128
|
+
or (config.get("url") if config_provider == "custom_http" else "")
|
|
14129
|
+
or ""
|
|
14130
|
+
).strip()
|
|
13409
14131
|
custom_key = str(config.get("custom_key", "")).strip()
|
|
13410
14132
|
custom_headers = parse_json_object(str(config.get("custom_headers", "{}") or "{}"), {})
|
|
13411
14133
|
custom_payload = str(config.get("custom_payload", "") or "").strip()
|
|
13412
14134
|
if custom_url:
|
|
14135
|
+
raw_custom_models = config.get(
|
|
14136
|
+
"custom_models",
|
|
14137
|
+
config.get(
|
|
14138
|
+
"custom_model_ids",
|
|
14139
|
+
config.get("models", []) if config_provider == "custom_http" else [],
|
|
14140
|
+
),
|
|
14141
|
+
)
|
|
14142
|
+
if isinstance(raw_custom_models, str):
|
|
14143
|
+
raw_custom_models = [x.strip() for x in re.split(r"[,\n]", raw_custom_models) if x.strip()]
|
|
14144
|
+
custom_models = [str(x).strip() for x in raw_custom_models] if isinstance(raw_custom_models, list) else []
|
|
14145
|
+
custom_model = str(
|
|
14146
|
+
config.get("custom_model", "")
|
|
14147
|
+
or (config.get("model", "") if config_provider == "custom_http" else "")
|
|
14148
|
+
or config.get("openai_model", "")
|
|
14149
|
+
).strip()
|
|
14150
|
+
if not custom_model and custom_models:
|
|
14151
|
+
custom_model = custom_models[0]
|
|
14152
|
+
if not custom_model:
|
|
14153
|
+
custom_model = "custom-model"
|
|
14154
|
+
if custom_model and custom_model not in custom_models:
|
|
14155
|
+
custom_models.insert(0, custom_model)
|
|
13413
14156
|
add_profile(
|
|
13414
14157
|
profiles,
|
|
13415
14158
|
profile_id="custom",
|
|
13416
14159
|
provider=normalize_profile_provider("custom_http"),
|
|
13417
|
-
label=
|
|
13418
|
-
|
|
14160
|
+
label=str(
|
|
14161
|
+
config.get("custom_name")
|
|
14162
|
+
or (config.get("provider_name") if config_provider == "custom_http" else "")
|
|
14163
|
+
or config.get("custom_title")
|
|
14164
|
+
or "Custom HTTP"
|
|
14165
|
+
),
|
|
14166
|
+
model=custom_model,
|
|
14167
|
+
models=custom_models,
|
|
14168
|
+
display_name=str(
|
|
14169
|
+
config.get("custom_name")
|
|
14170
|
+
or (config.get("provider_name") if config_provider == "custom_http" else "")
|
|
14171
|
+
or "Custom HTTP"
|
|
14172
|
+
),
|
|
14173
|
+
title=str(
|
|
14174
|
+
config.get("custom_title")
|
|
14175
|
+
or (config.get("provider_title") if config_provider == "custom_http" else "")
|
|
14176
|
+
or config.get("custom_name")
|
|
14177
|
+
or "Custom HTTP"
|
|
14178
|
+
),
|
|
13419
14179
|
base_url=extract_base_url(custom_url),
|
|
13420
14180
|
endpoint=custom_url,
|
|
13421
14181
|
api_key=custom_key,
|
|
@@ -13446,6 +14206,44 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13446
14206
|
source="default",
|
|
13447
14207
|
)
|
|
13448
14208
|
|
|
14209
|
+
# Legacy provider keys may carry a comma/newline separated model list.
|
|
14210
|
+
# Keep the configured default first, then let the catalog append probed
|
|
14211
|
+
# models without creating duplicate profiles.
|
|
14212
|
+
provider_model_keys = {
|
|
14213
|
+
"ollama": "ollama_models", "openai_compat": "openai_models",
|
|
14214
|
+
"siliconflow": "siliconflow_models", "vllm": "vllm_models",
|
|
14215
|
+
"lmstudio": "lmstudio_models", "anthropic": "anthropic_models",
|
|
14216
|
+
"glm": "glm_models", "kimi": "kimi_models", "openrouter": "openrouter_models",
|
|
14217
|
+
"custom_http": "custom_models",
|
|
14218
|
+
}
|
|
14219
|
+
for profile in profiles:
|
|
14220
|
+
prov = str(profile.get("provider", "") or "").lower()
|
|
14221
|
+
model_list_key = provider_model_keys.get(prov, "")
|
|
14222
|
+
raw_list = (
|
|
14223
|
+
config.get(
|
|
14224
|
+
model_list_key,
|
|
14225
|
+
config.get("models", []) if config_provider == prov else [],
|
|
14226
|
+
)
|
|
14227
|
+
if model_list_key
|
|
14228
|
+
else []
|
|
14229
|
+
)
|
|
14230
|
+
if isinstance(raw_list, str):
|
|
14231
|
+
raw_list = [x.strip() for x in re.split(r"[,\n]", raw_list) if x.strip()]
|
|
14232
|
+
existing = [str(x).strip() for x in profile.get("models", []) if str(x).strip()]
|
|
14233
|
+
if not existing and isinstance(raw_list, list):
|
|
14234
|
+
existing = [str(x).strip() for x in raw_list if str(x).strip()]
|
|
14235
|
+
default_model = str(profile.get("model", "") or "").strip()
|
|
14236
|
+
# ``auto`` is a transport placeholder for local OpenAI-compatible
|
|
14237
|
+
# servers. Once a model directory is available, make its first
|
|
14238
|
+
# discovered entry the active model so importing a directory is
|
|
14239
|
+
# immediately runnable and stable across restarts.
|
|
14240
|
+
if existing and default_model.lower() == "auto":
|
|
14241
|
+
default_model = existing[0]
|
|
14242
|
+
profile["model"] = default_model
|
|
14243
|
+
if default_model and default_model not in existing:
|
|
14244
|
+
existing.insert(0, default_model)
|
|
14245
|
+
profile["models"] = existing
|
|
14246
|
+
|
|
13449
14247
|
active_map = {
|
|
13450
14248
|
"ollama": "ollama",
|
|
13451
14249
|
"openai": "openai",
|
|
@@ -13466,7 +14264,7 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13466
14264
|
default_profile_id = candidate
|
|
13467
14265
|
break
|
|
13468
14266
|
if not default_profile_id:
|
|
13469
|
-
default_profile_id = active_map.get(provider, "")
|
|
14267
|
+
default_profile_id = active_map.get(config_provider or provider, "")
|
|
13470
14268
|
if not default_profile_id or default_profile_id not in profile_ids:
|
|
13471
14269
|
# Fallback: first non-ollama profile that was explicitly configured
|
|
13472
14270
|
for p in profiles:
|
|
@@ -13485,6 +14283,51 @@ def parse_llm_config_profiles(config: dict, default_ollama_url: str, default_oll
|
|
|
13485
14283
|
p["effort"] = effort_default
|
|
13486
14284
|
if max_effort_default and not str(p.get("max_effort", "") or "").strip():
|
|
13487
14285
|
p["max_effort"] = max_effort_default
|
|
14286
|
+
raw_runtime_settings = config.get(
|
|
14287
|
+
"model_settings",
|
|
14288
|
+
config.get("model_runtime_settings", config.get("runtime_settings", {})),
|
|
14289
|
+
)
|
|
14290
|
+
if isinstance(raw_runtime_settings, str):
|
|
14291
|
+
raw_runtime_settings = parse_json_object(raw_runtime_settings, {})
|
|
14292
|
+
if isinstance(raw_runtime_settings, dict):
|
|
14293
|
+
for profile in profiles:
|
|
14294
|
+
current = dict(profile.get("model_settings", {}) or {})
|
|
14295
|
+
profile_model = str(profile.get("model", "") or "").strip()
|
|
14296
|
+
provider_id = str(profile.get("id", "") or "").strip()
|
|
14297
|
+
provider_name = str(profile.get("provider", "") or "").strip()
|
|
14298
|
+
for model_id, value in raw_reasoning_map.items():
|
|
14299
|
+
normalized = normalize_model_runtime_settings(value)
|
|
14300
|
+
target = str(model_id or "").strip()
|
|
14301
|
+
if target and normalized:
|
|
14302
|
+
current[target] = {**current.get(target, {}), **normalized}
|
|
14303
|
+
for key in (profile_model, provider_id, provider_name):
|
|
14304
|
+
value = raw_runtime_settings.get(key) if key else None
|
|
14305
|
+
if isinstance(value, dict) and any(k in MODEL_RUNTIME_SETTING_KEYS for k in value):
|
|
14306
|
+
normalized = normalize_model_runtime_settings(value)
|
|
14307
|
+
if profile_model and normalized:
|
|
14308
|
+
current[profile_model] = {**current.get(profile_model, {}), **normalized}
|
|
14309
|
+
for model_id, value in raw_runtime_settings.items():
|
|
14310
|
+
if not isinstance(value, dict):
|
|
14311
|
+
continue
|
|
14312
|
+
normalized = normalize_model_runtime_settings(value)
|
|
14313
|
+
if normalized:
|
|
14314
|
+
current[str(model_id).strip()] = {**current.get(str(model_id).strip(), {}), **normalized}
|
|
14315
|
+
elif str(model_id) in {provider_id, provider_name}:
|
|
14316
|
+
for nested_model, nested_value in value.items():
|
|
14317
|
+
nested = normalize_model_runtime_settings(nested_value)
|
|
14318
|
+
if nested:
|
|
14319
|
+
target = str(nested_model or "").strip()
|
|
14320
|
+
current[target] = {**current.get(target, {}), **nested}
|
|
14321
|
+
profile["model_settings"] = current
|
|
14322
|
+
elif raw_reasoning_map:
|
|
14323
|
+
for profile in profiles:
|
|
14324
|
+
current = dict(profile.get("model_settings", {}) or {})
|
|
14325
|
+
for model_id, value in raw_reasoning_map.items():
|
|
14326
|
+
target = str(model_id or "").strip()
|
|
14327
|
+
normalized = normalize_model_runtime_settings(value)
|
|
14328
|
+
if target and normalized:
|
|
14329
|
+
current[target] = {**current.get(target, {}), **normalized}
|
|
14330
|
+
profile["model_settings"] = current
|
|
13488
14331
|
return {"profiles": profiles, "default_profile_id": default_profile_id}
|
|
13489
14332
|
|
|
13490
14333
|
def looks_like_llm_config(config: dict) -> bool:
|
|
@@ -13526,13 +14369,23 @@ def looks_like_llm_config(config: dict) -> bool:
|
|
|
13526
14369
|
"openrouter_model",
|
|
13527
14370
|
"openrouter_key",
|
|
13528
14371
|
"custom_url",
|
|
14372
|
+
"provider_url",
|
|
14373
|
+
"provider_name",
|
|
14374
|
+
"provider_title",
|
|
14375
|
+
"name",
|
|
14376
|
+
"title",
|
|
14377
|
+
"custom_name",
|
|
14378
|
+
"custom_title",
|
|
13529
14379
|
"custom_model",
|
|
14380
|
+
"custom_models",
|
|
13530
14381
|
"custom_key",
|
|
13531
14382
|
"custom_headers",
|
|
13532
14383
|
"custom_payload",
|
|
13533
14384
|
"capabilities",
|
|
13534
14385
|
"multimodal_capabilities",
|
|
13535
14386
|
"model_capabilities",
|
|
14387
|
+
"model_reasoning_capabilities",
|
|
14388
|
+
"reasoning_capabilities",
|
|
13536
14389
|
"media_endpoints",
|
|
13537
14390
|
"ollama_capabilities",
|
|
13538
14391
|
"openai_capabilities",
|
|
@@ -13562,6 +14415,8 @@ def looks_like_llm_config(config: dict) -> bool:
|
|
|
13562
14415
|
"custom_video_endpoint",
|
|
13563
14416
|
"temperature",
|
|
13564
14417
|
"request_timeout",
|
|
14418
|
+
"model_settings",
|
|
14419
|
+
"model_runtime_settings",
|
|
13565
14420
|
}
|
|
13566
14421
|
return bool(keys & markers)
|
|
13567
14422
|
|
|
@@ -25418,6 +26273,8 @@ class OllamaClient:
|
|
|
25418
26273
|
self.thinking_stream = bool(thinking_stream)
|
|
25419
26274
|
self.response_stream = bool(response_stream)
|
|
25420
26275
|
self.capabilities = default_multimodal_capabilities()
|
|
26276
|
+
self.reasoning_supported: bool | None = None
|
|
26277
|
+
self.reasoning_style: str = ""
|
|
25421
26278
|
self.media_endpoints: dict[str, str] = {}
|
|
25422
26279
|
self.embed_model: str = "" # embedding model name, e.g. "nomic-embed-text"
|
|
25423
26280
|
self.telemetry_callback = None
|
|
@@ -25755,6 +26612,8 @@ class OllamaClient:
|
|
|
25755
26612
|
infer_model_multimodal_capabilities(self.provider, self.model),
|
|
25756
26613
|
declared_caps,
|
|
25757
26614
|
)
|
|
26615
|
+
self.reasoning_supported = profile.get("reasoning_supported") if isinstance(profile.get("reasoning_supported"), bool) else None
|
|
26616
|
+
self.reasoning_style = str(profile.get("reasoning_style", "") or "").strip().lower()
|
|
25758
26617
|
self.media_endpoints = parse_media_endpoints(profile.get("media_endpoints", {}))
|
|
25759
26618
|
if "embed_model" in profile:
|
|
25760
26619
|
self.embed_model = str(profile.get("embed_model") or "").strip()
|
|
@@ -27669,7 +28528,14 @@ class OllamaClient:
|
|
|
27669
28528
|
# Resolve provider-neutral effort into native reasoning mutations once.
|
|
27670
28529
|
# probe_mode never reasons (it is a cheap capability ping).
|
|
27671
28530
|
reasoning = {} if probe_mode else resolve_reasoning_payload(
|
|
27672
|
-
self.provider,
|
|
28531
|
+
self.provider,
|
|
28532
|
+
self.model,
|
|
28533
|
+
effort,
|
|
28534
|
+
max_tokens=max_tokens,
|
|
28535
|
+
capabilities={
|
|
28536
|
+
"reasoning_supported": self.reasoning_supported,
|
|
28537
|
+
"reasoning_style": self.reasoning_style,
|
|
28538
|
+
},
|
|
27673
28539
|
)
|
|
27674
28540
|
if reasoning.get("max_tokens"):
|
|
27675
28541
|
max_tokens = int(reasoning["max_tokens"])
|
|
@@ -27795,7 +28661,11 @@ class OllamaClient:
|
|
|
27795
28661
|
# thinking by default when the field is omitted. Send the explicit
|
|
27796
28662
|
# boolean for models that advertise the native switch so a bounded
|
|
27797
28663
|
# no-thinking compatibility turn can actually produce a tool call.
|
|
27798
|
-
if
|
|
28664
|
+
if provider == "ollama" and self.reasoning_supported is not False:
|
|
28665
|
+
# Ollama accepts an explicit boolean on native chat. Unknown
|
|
28666
|
+
# capability metadata stays conservative because the caller's
|
|
28667
|
+
# ``think`` value defaults to false; no model-name inference is
|
|
28668
|
+
# used to turn reasoning on.
|
|
27799
28669
|
native_payload["think"] = bool(think)
|
|
27800
28670
|
if tools:
|
|
27801
28671
|
native_payload["tools"] = tools
|
|
@@ -29472,7 +30342,7 @@ class SessionState:
|
|
|
29472
30342
|
l2_todo_policy if l2_todo_policy is not None else (cfg_l2_todo_policy or DEFAULT_L2_TODO_POLICY)
|
|
29473
30343
|
)
|
|
29474
30344
|
self.runtime_requires_todos: bool | None = None
|
|
29475
|
-
self._todowrite_step_counter: dict[str, int] = {} #
|
|
30345
|
+
self._todowrite_step_counter: dict[str, int] = {} # Consecutive no-progress writes by task, step, and role.
|
|
29476
30346
|
self.runtime_scale_preference = "balanced"
|
|
29477
30347
|
self.runtime_direct_objective = ""
|
|
29478
30348
|
# Full, lossless source request for the active task. Derived runtime
|
|
@@ -30405,7 +31275,14 @@ class SessionState:
|
|
|
30405
31275
|
profile = self.model_profiles.get(self.active_profile_id)
|
|
30406
31276
|
if not profile:
|
|
30407
31277
|
return
|
|
30408
|
-
|
|
31278
|
+
stored = dict(profile)
|
|
31279
|
+
row = dict(stored)
|
|
31280
|
+
# Restore the selected model's persisted runtime controls before the
|
|
31281
|
+
# client is configured. This keeps effort/streaming identical after a
|
|
31282
|
+
# restart and when a session is loaded by the IDE.
|
|
31283
|
+
active_model = str(row.get("model", "") or "").strip()
|
|
31284
|
+
for key, value in model_runtime_settings_for(row, active_model).items():
|
|
31285
|
+
row[key] = value
|
|
30409
31286
|
timeout_floor = max(MIN_TIMEOUT_SECONDS, int(self.max_run_seconds or 0))
|
|
30410
31287
|
row["request_timeout"] = normalize_timeout_seconds(
|
|
30411
31288
|
row.get("request_timeout", DEFAULT_REQUEST_TIMEOUT),
|
|
@@ -30420,9 +31297,13 @@ class SessionState:
|
|
|
30420
31297
|
if cached_caps:
|
|
30421
31298
|
merged = merge_multimodal_capabilities(self._capabilities_from_profile(row), cached_caps)
|
|
30422
31299
|
row["capabilities"] = merged
|
|
30423
|
-
|
|
30424
|
-
|
|
30425
|
-
self.
|
|
31300
|
+
stored["capabilities"] = merged
|
|
31301
|
+
stored["request_timeout"] = row["request_timeout"]
|
|
31302
|
+
self.model_profiles[self.active_profile_id] = stored
|
|
31303
|
+
# Keep per-model controls in ``model_settings``. Applying them to the
|
|
31304
|
+
# client must not promote them to profile-level defaults, otherwise a
|
|
31305
|
+
# later model switch would inherit the previous model's settings.
|
|
31306
|
+
self.ollama.apply_profile(row)
|
|
30426
31307
|
self.thinking = False
|
|
30427
31308
|
|
|
30428
31309
|
def _profile_is_runnable(self, profile: dict) -> bool:
|
|
@@ -30488,6 +31369,7 @@ class SessionState:
|
|
|
30488
31369
|
if force_probe:
|
|
30489
31370
|
self._ensure_active_profile_capabilities(force_probe=True)
|
|
30490
31371
|
opts = []
|
|
31372
|
+
profiles_changed = False
|
|
30491
31373
|
ollama_profile_id = ""
|
|
30492
31374
|
ollama_base = self.ollama.base_url
|
|
30493
31375
|
for pid, profile in sorted(self.model_profiles.items(), key=lambda x: x[0]):
|
|
@@ -30511,6 +31393,29 @@ class SessionState:
|
|
|
30511
31393
|
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
30512
31394
|
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
30513
31395
|
"response_stream": bool(profile.get("response_stream", False)),
|
|
31396
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
31397
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
31398
|
+
"reasoning_supported": (
|
|
31399
|
+
profile.get("reasoning_supported")
|
|
31400
|
+
if profile.get("reasoning_supported") is not None
|
|
31401
|
+
else model_reasoning_style(
|
|
31402
|
+
str(profile.get("provider", "")),
|
|
31403
|
+
model,
|
|
31404
|
+
caps,
|
|
31405
|
+
)
|
|
31406
|
+
!= "none"
|
|
31407
|
+
),
|
|
31408
|
+
"reasoning_style": str(
|
|
31409
|
+
profile.get("reasoning_style")
|
|
31410
|
+
or model_reasoning_style(
|
|
31411
|
+
str(profile.get("provider", "")),
|
|
31412
|
+
model,
|
|
31413
|
+
caps,
|
|
31414
|
+
)
|
|
31415
|
+
or ""
|
|
31416
|
+
),
|
|
31417
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
31418
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
30514
31419
|
"capabilities": caps,
|
|
30515
31420
|
"capabilities_probed": bool(cache_entry),
|
|
30516
31421
|
"capabilities_probed_at": float(cache_entry.get("updated_at", 0.0) or 0.0)
|
|
@@ -30527,7 +31432,24 @@ class SessionState:
|
|
|
30527
31432
|
base = str(profile.get("base_url", self.ollama.base_url) or self.ollama.base_url).strip()
|
|
30528
31433
|
if not base:
|
|
30529
31434
|
continue
|
|
30530
|
-
tags = list_ollama_models_cached(
|
|
31435
|
+
tags = list_ollama_models_cached(
|
|
31436
|
+
base,
|
|
31437
|
+
force_refresh=bool(force_probe),
|
|
31438
|
+
background=not force_probe,
|
|
31439
|
+
)
|
|
31440
|
+
ollama_records = probe_provider_models(
|
|
31441
|
+
profile,
|
|
31442
|
+
force_refresh=bool(force_probe),
|
|
31443
|
+
background=not force_probe,
|
|
31444
|
+
)
|
|
31445
|
+
profiles_changed = merge_probed_models_into_profile(
|
|
31446
|
+
profile, ollama_records, model_ids=tags
|
|
31447
|
+
) or profiles_changed
|
|
31448
|
+
ollama_record_map = {
|
|
31449
|
+
str(record.get("id", "")): record
|
|
31450
|
+
for record in ollama_records
|
|
31451
|
+
if isinstance(record, dict) and str(record.get("id", "")).strip()
|
|
31452
|
+
}
|
|
30531
31453
|
if tags:
|
|
30532
31454
|
if pid == self.active_profile_id:
|
|
30533
31455
|
self.ollama_env_tags = list(tags)
|
|
@@ -30543,6 +31465,13 @@ class SessionState:
|
|
|
30543
31465
|
tag_cache_key = self._profile_cache_key(tag_profile, tag)
|
|
30544
31466
|
tag_cache_entry = self.multimodal_capability_cache.get(tag_cache_key, {})
|
|
30545
31467
|
tag_caps = self._capabilities_from_profile(tag_profile, model_override=tag)
|
|
31468
|
+
tag_record = ollama_record_map.get(tag)
|
|
31469
|
+
tag_record_caps = (
|
|
31470
|
+
tag_record.get("capabilities", {})
|
|
31471
|
+
if isinstance(tag_record, dict)
|
|
31472
|
+
else {}
|
|
31473
|
+
)
|
|
31474
|
+
tag_record_probed = isinstance(tag_record, dict)
|
|
30546
31475
|
opts.append(
|
|
30547
31476
|
{
|
|
30548
31477
|
"selection": selection,
|
|
@@ -30554,6 +31483,35 @@ class SessionState:
|
|
|
30554
31483
|
"thinking_hint": bool(profile.get("thinking_hint", self.thinking)),
|
|
30555
31484
|
"thinking_stream": bool(profile.get("thinking_stream", self.ollama.thinking_stream)),
|
|
30556
31485
|
"response_stream": bool(profile.get("response_stream", self.ollama.response_stream)),
|
|
31486
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
31487
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
31488
|
+
"reasoning_supported": (
|
|
31489
|
+
profile.get("reasoning_supported")
|
|
31490
|
+
if profile.get("reasoning_supported") is not None
|
|
31491
|
+
else tag_record_caps.get("reasoning_supported")
|
|
31492
|
+
if isinstance(tag_record_caps, dict)
|
|
31493
|
+
and tag_record_caps.get("reasoning_supported") is not None
|
|
31494
|
+
else model_reasoning_style(
|
|
31495
|
+
str(profile.get("provider", "")),
|
|
31496
|
+
tag,
|
|
31497
|
+
tag_caps,
|
|
31498
|
+
capabilities_probed=tag_record_probed,
|
|
31499
|
+
)
|
|
31500
|
+
!= "none"
|
|
31501
|
+
),
|
|
31502
|
+
"reasoning_style": str(
|
|
31503
|
+
profile.get("reasoning_style")
|
|
31504
|
+
or tag_record_caps.get("reasoning_style", "")
|
|
31505
|
+
or model_reasoning_style(
|
|
31506
|
+
str(profile.get("provider", "")),
|
|
31507
|
+
tag,
|
|
31508
|
+
tag_caps,
|
|
31509
|
+
capabilities_probed=tag_record_probed,
|
|
31510
|
+
)
|
|
31511
|
+
or ""
|
|
31512
|
+
),
|
|
31513
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
31514
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
30557
31515
|
"capabilities": tag_caps,
|
|
30558
31516
|
"capabilities_probed": bool(tag_cache_entry),
|
|
30559
31517
|
"capabilities_probed_at": float(tag_cache_entry.get("updated_at", 0.0) or 0.0)
|
|
@@ -30561,6 +31519,69 @@ class SessionState:
|
|
|
30561
31519
|
else 0.0,
|
|
30562
31520
|
}
|
|
30563
31521
|
)
|
|
31522
|
+
# Expand every provider from configured model lists and bounded probes.
|
|
31523
|
+
# The cache prevents status polling from repeatedly hitting vendors.
|
|
31524
|
+
for pid, profile in sorted(self.model_profiles.items(), key=lambda x: x[0]):
|
|
31525
|
+
configured = [str(x).strip() for x in profile.get("models", []) if str(x).strip()]
|
|
31526
|
+
records = probe_provider_models(
|
|
31527
|
+
profile,
|
|
31528
|
+
force_refresh=bool(force_probe),
|
|
31529
|
+
background=not force_probe,
|
|
31530
|
+
)
|
|
31531
|
+
profiles_changed = merge_probed_models_into_profile(profile, records) or profiles_changed
|
|
31532
|
+
for rec in records:
|
|
31533
|
+
model_id = str(rec.get("id", "") or "").strip()
|
|
31534
|
+
if model_id and model_id not in configured:
|
|
31535
|
+
configured.append(model_id)
|
|
31536
|
+
for model_id in configured:
|
|
31537
|
+
selection = f"{pid}::{model_id}"
|
|
31538
|
+
if not model_id or selection in seen:
|
|
31539
|
+
continue
|
|
31540
|
+
seen.add(selection)
|
|
31541
|
+
rec = next((r for r in records if str(r.get("id", "")) == model_id), {})
|
|
31542
|
+
rcaps = rec.get("capabilities", {}) if isinstance(rec, dict) else {}
|
|
31543
|
+
opts.append({
|
|
31544
|
+
"selection": selection, "profile_id": pid,
|
|
31545
|
+
"provider": profile.get("provider", "unknown"), "model": model_id,
|
|
31546
|
+
"label": f"{profile.get('label', pid)} | {model_id}",
|
|
31547
|
+
"source": "provider-probe" if rec else profile.get("source", ""),
|
|
31548
|
+
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
31549
|
+
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
31550
|
+
"response_stream": bool(profile.get("response_stream", False)),
|
|
31551
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
31552
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
31553
|
+
"reasoning_supported": (
|
|
31554
|
+
rcaps.get("reasoning_supported")
|
|
31555
|
+
if isinstance(rcaps, dict) and rcaps.get("reasoning_supported") is not None
|
|
31556
|
+
else profile.get("reasoning_supported")
|
|
31557
|
+
if profile.get("reasoning_supported") is not None
|
|
31558
|
+
else model_reasoning_style(
|
|
31559
|
+
str(profile.get("provider", "")),
|
|
31560
|
+
model_id,
|
|
31561
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
31562
|
+
capabilities_probed=bool(rec),
|
|
31563
|
+
)
|
|
31564
|
+
!= "none"
|
|
31565
|
+
),
|
|
31566
|
+
"reasoning_style": str(
|
|
31567
|
+
(rcaps.get("reasoning_style") if isinstance(rcaps, dict) else "")
|
|
31568
|
+
or profile.get("reasoning_style", "")
|
|
31569
|
+
or model_reasoning_style(
|
|
31570
|
+
str(profile.get("provider", "")),
|
|
31571
|
+
model_id,
|
|
31572
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
31573
|
+
capabilities_probed=bool(rec),
|
|
31574
|
+
)
|
|
31575
|
+
or ""
|
|
31576
|
+
),
|
|
31577
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
31578
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
31579
|
+
"capabilities": self._capabilities_from_profile(dict(profile, model=model_id), model_override=model_id),
|
|
31580
|
+
})
|
|
31581
|
+
for _option in opts:
|
|
31582
|
+
_pid = str(_option.get("profile_id", "") or "")
|
|
31583
|
+
_profile = self.model_profiles.get(_pid, {})
|
|
31584
|
+
apply_model_option_runtime_fields(_option, _profile, str(_option.get("model", "") or ""))
|
|
30564
31585
|
runnable_opts = [x for x in opts if self._option_is_runnable(x)]
|
|
30565
31586
|
if runnable_opts:
|
|
30566
31587
|
opts = runnable_opts
|
|
@@ -30582,6 +31603,27 @@ class SessionState:
|
|
|
30582
31603
|
"thinking_hint": bool(active.get("thinking_hint", False)),
|
|
30583
31604
|
"thinking_stream": bool(active.get("thinking_stream", False)),
|
|
30584
31605
|
"response_stream": bool(active.get("response_stream", False)),
|
|
31606
|
+
"effort": str(active.get("effort", "") or ""),
|
|
31607
|
+
"max_effort": str(active.get("max_effort", "") or ""),
|
|
31608
|
+
"reasoning_supported": (
|
|
31609
|
+
active.get("reasoning_supported")
|
|
31610
|
+
if active.get("reasoning_supported") is not None
|
|
31611
|
+
else model_reasoning_style(
|
|
31612
|
+
str(active.get("provider", "")),
|
|
31613
|
+
str(active.get("model", "") or ""),
|
|
31614
|
+
active.get("capabilities", {}),
|
|
31615
|
+
)
|
|
31616
|
+
!= "none"
|
|
31617
|
+
),
|
|
31618
|
+
"reasoning_style": str(
|
|
31619
|
+
active.get("reasoning_style")
|
|
31620
|
+
or model_reasoning_style(
|
|
31621
|
+
str(active.get("provider", "")),
|
|
31622
|
+
str(active.get("model", "") or ""),
|
|
31623
|
+
active.get("capabilities", {}),
|
|
31624
|
+
)
|
|
31625
|
+
or ""
|
|
31626
|
+
),
|
|
30585
31627
|
"capabilities": self._capabilities_from_profile(active if isinstance(active, dict) else {}),
|
|
30586
31628
|
"capabilities_probed": bool(active_cache_entry),
|
|
30587
31629
|
"capabilities_probed_at": float(active_cache_entry.get("updated_at", 0.0) or 0.0)
|
|
@@ -30589,11 +31631,15 @@ class SessionState:
|
|
|
30589
31631
|
else 0.0,
|
|
30590
31632
|
},
|
|
30591
31633
|
)
|
|
31634
|
+
apply_model_option_runtime_fields(opts[0], active, str(active.get("model", "") or ""))
|
|
30592
31635
|
option_map.add(selected)
|
|
30593
31636
|
if opts and selected not in option_map:
|
|
30594
31637
|
selected = str(opts[0].get("selection", ""))
|
|
30595
31638
|
active_caps = self._capabilities_from_profile(active if isinstance(active, dict) else {})
|
|
30596
31639
|
active_cache = self.multimodal_capability_cache.get(self._profile_cache_key(active if isinstance(active, dict) else {}), {})
|
|
31640
|
+
if profiles_changed:
|
|
31641
|
+
self.updated_at = now_ts()
|
|
31642
|
+
self._persist()
|
|
30597
31643
|
return {
|
|
30598
31644
|
"provider": active.get("provider", "ollama"),
|
|
30599
31645
|
"selected": selected,
|
|
@@ -30615,6 +31661,7 @@ class SessionState:
|
|
|
30615
31661
|
model_override: str | None = None,
|
|
30616
31662
|
*,
|
|
30617
31663
|
reset_failures: bool = True,
|
|
31664
|
+
settings: dict | None = None,
|
|
30618
31665
|
) -> dict:
|
|
30619
31666
|
raw = str(selection or "").strip()
|
|
30620
31667
|
explicit_profile = "::" in raw
|
|
@@ -30645,6 +31692,11 @@ class SessionState:
|
|
|
30645
31692
|
profile["model"] = str(model_override).strip()
|
|
30646
31693
|
elif str(selected_model).strip():
|
|
30647
31694
|
profile["model"] = str(selected_model).strip()
|
|
31695
|
+
profile = apply_model_runtime_settings(profile, str(profile.get("model", "") or ""), settings)
|
|
31696
|
+
for record in probe_provider_models(profile, background=True):
|
|
31697
|
+
if str(record.get("id", "")) == str(profile.get("model", "")):
|
|
31698
|
+
merge_probed_models_into_profile(profile, [record])
|
|
31699
|
+
break
|
|
30648
31700
|
if not self._profile_is_runnable(profile) and not explicit_profile and str(selected_model).strip():
|
|
30649
31701
|
fallback = self._pick_runnable_selection(preferred_provider="ollama")
|
|
30650
31702
|
if fallback:
|
|
@@ -30725,6 +31777,9 @@ class SessionState:
|
|
|
30725
31777
|
if cfg_user_memory_mode is not None:
|
|
30726
31778
|
self.user_memory_mode = normalize_user_memory_mode(cfg_user_memory_mode)
|
|
30727
31779
|
parsed = parse_llm_config_profiles(config, self.ollama.base_url, self.ollama.model)
|
|
31780
|
+
# A config import is also a model-directory import. Probe each provider
|
|
31781
|
+
# once so all advertised models are available to both UIs immediately.
|
|
31782
|
+
probe_and_merge_model_profiles(parsed.get("profiles", []), force_refresh=True)
|
|
30728
31783
|
self.multimodal_capability_cache = {}
|
|
30729
31784
|
OllamaClient.clear_global_probe_cache()
|
|
30730
31785
|
self.ollama.clear_probe_cache()
|
|
@@ -49486,6 +50541,7 @@ body{padding:18px}
|
|
|
49486
50541
|
self.set_runtime_selection(
|
|
49487
50542
|
selection,
|
|
49488
50543
|
model_override if isinstance(model_override, str) else None,
|
|
50544
|
+
settings=normalize_model_runtime_settings(payload.get("settings", payload)),
|
|
49489
50545
|
)
|
|
49490
50546
|
applied_notes.append(f"deferred model switch applied: {trim(selection, 120)}")
|
|
49491
50547
|
sync_needed = True
|
|
@@ -49620,8 +50676,9 @@ body{padding:18px}
|
|
|
49620
50676
|
prof = {}
|
|
49621
50677
|
if not isinstance(prof, dict):
|
|
49622
50678
|
prof = {}
|
|
49623
|
-
|
|
49624
|
-
|
|
50679
|
+
effective = model_runtime_settings_for(prof, str(prof.get("model", "") or ""))
|
|
50680
|
+
default = str(effective.get("effort", "") or "").strip().lower()
|
|
50681
|
+
ceiling = str(effective.get("max_effort", "") or "").strip().lower()
|
|
49625
50682
|
if ceiling not in EFFORT_ORDER:
|
|
49626
50683
|
ceiling = EFFORT_MAX
|
|
49627
50684
|
if default not in EFFORT_ORDER:
|
|
@@ -49735,6 +50792,13 @@ body{padding:18px}
|
|
|
49735
50792
|
except Exception as exc:
|
|
49736
50793
|
self.kernel_runtime_degraded = True
|
|
49737
50794
|
self._emit("status", {"summary": f"liquid kernel hook degraded: {trim(str(exc), 160)}"})
|
|
50795
|
+
if tools is not None:
|
|
50796
|
+
guarded_tools = self._filter_todo_progress_tools(tools, role=context_role_hint)
|
|
50797
|
+
if len(guarded_tools) != len(tools):
|
|
50798
|
+
system = f"{system}\n\n{self._todo_no_progress_instruction()}"
|
|
50799
|
+
if canonicalize_tool_name(tool_choice) in {"TodoWrite", "TodoWriteRescue"}:
|
|
50800
|
+
tool_choice = ""
|
|
50801
|
+
tools = guarded_tools
|
|
49738
50802
|
system = self._inject_runtime_environment_context(system)
|
|
49739
50803
|
estimated_prompt_tokens = self._estimate_model_call_prompt_tokens(
|
|
49740
50804
|
messages,
|
|
@@ -63566,6 +64630,15 @@ body{padding:18px}
|
|
|
63566
64630
|
return {}
|
|
63567
64631
|
bb = board if isinstance(board, dict) else self._ensure_blackboard()
|
|
63568
64632
|
step_id = trim(str(plan_step.get("id", "") or ""), 60)
|
|
64633
|
+
# An unchanged, uniquely identified child needs no model classification.
|
|
64634
|
+
# Providers often resend the whole list without parent/row IDs.
|
|
64635
|
+
existing_rows = self._active_plan_worker_todo_rows(step_id, role="")
|
|
64636
|
+
if sum(
|
|
64637
|
+
normalize_work_text(str(existing.get("content", "") or "")).strip().casefold()
|
|
64638
|
+
== normalize_work_text(content).strip().casefold()
|
|
64639
|
+
for existing in existing_rows
|
|
64640
|
+
) == 1:
|
|
64641
|
+
return {}
|
|
63569
64642
|
prompt = (
|
|
63570
64643
|
"/no_think\n"
|
|
63571
64644
|
"Decide the intent of one unscoped TodoWrite row inside the active execution-plan step. "
|
|
@@ -63661,6 +64734,12 @@ body{padding:18px}
|
|
|
63661
64734
|
content = trim(str(incoming.get("content", "") or "").strip(), 900)
|
|
63662
64735
|
if not content:
|
|
63663
64736
|
return {}
|
|
64737
|
+
exact_content = normalize_work_text(str(incoming.get("content", "") or "")).strip().casefold()
|
|
64738
|
+
if sum(
|
|
64739
|
+
normalize_work_text(str(row.get("content", "") or "")).strip().casefold() == exact_content
|
|
64740
|
+
for row in existing_rows if isinstance(row, dict)
|
|
64741
|
+
) == 1:
|
|
64742
|
+
return {}
|
|
63664
64743
|
bb = board if isinstance(board, dict) else self._ensure_blackboard()
|
|
63665
64744
|
rows_text = "\n".join(
|
|
63666
64745
|
f"- id={trim(str(row.get('subtask_id', '') or row.get('key', '') or ''), 100)} "
|
|
@@ -77697,7 +78776,79 @@ body{padding:18px}
|
|
|
77697
78776
|
resume=is_resume,
|
|
77698
78777
|
)
|
|
77699
78778
|
|
|
78779
|
+
def _todo_progress_guard_key(self, role: str = "") -> str:
|
|
78780
|
+
bb = self._ensure_blackboard()
|
|
78781
|
+
step = self._get_active_plan_step(bb) or {}
|
|
78782
|
+
role_key = self._sanitize_agent_role(role) or self._current_plan_worker_owner(bb)
|
|
78783
|
+
return json.dumps([
|
|
78784
|
+
getattr(self, "run_generation", 0), bb.get("task_epoch", 0),
|
|
78785
|
+
step.get("id", ""), role_key,
|
|
78786
|
+
], ensure_ascii=False)
|
|
78787
|
+
|
|
78788
|
+
@staticmethod
|
|
78789
|
+
def _todo_material_signature(rows: list[dict]) -> str:
|
|
78790
|
+
# Timestamp-only writes are not task progress. Evidence, identities,
|
|
78791
|
+
# statuses and scope changes still pass through the normal audit.
|
|
78792
|
+
fields = (
|
|
78793
|
+
"content", "status", "owner", "parent_step_id", "key", "subtask_id",
|
|
78794
|
+
"external_subtask_id", "root_group_id", "evidence", "evidence_binding", "evidence_ids",
|
|
78795
|
+
"id", "full_content", "category",
|
|
78796
|
+
)
|
|
78797
|
+
return json.dumps(
|
|
78798
|
+
[{field: row[field] for field in fields if row.get(field) not in (None, "", [])}
|
|
78799
|
+
for row in rows if isinstance(row, dict)],
|
|
78800
|
+
ensure_ascii=False, sort_keys=True, default=str,
|
|
78801
|
+
)
|
|
78802
|
+
|
|
78803
|
+
@staticmethod
|
|
78804
|
+
def _todo_no_progress_instruction() -> str:
|
|
78805
|
+
return (
|
|
78806
|
+
"Todo state has not changed. Do not repeat TodoWrite, switch to TodoWriteRescue, "
|
|
78807
|
+
"or rewrite the same list. Execute the current in_progress task using a work or "
|
|
78808
|
+
"evidence-gathering tool, then update only the statuses supported by its result. "
|
|
78809
|
+
"Repeated no-progress Todo writes are temporarily unavailable until a non-Todo "
|
|
78810
|
+
"work action runs. Existing tasks and completion/evidence checks remain intact."
|
|
78811
|
+
)
|
|
78812
|
+
|
|
78813
|
+
def _filter_todo_progress_tools(self, tools: list[dict], *, role: str = "") -> list[dict]:
|
|
78814
|
+
counts = getattr(self, "_todowrite_step_counter", {})
|
|
78815
|
+
if not counts or counts.get(self._todo_progress_guard_key(role), 0) < 2:
|
|
78816
|
+
return tools
|
|
78817
|
+
return [
|
|
78818
|
+
tool for tool in tools
|
|
78819
|
+
if canonicalize_tool_name(tool.get("function", {}).get("name", ""))
|
|
78820
|
+
not in {"TodoWrite", "TodoWriteRescue"}
|
|
78821
|
+
]
|
|
78822
|
+
|
|
77700
78823
|
def _dispatch_todo_update(self, args: dict, *, role: str = "", resume: bool = False) -> str:
|
|
78824
|
+
key = self._todo_progress_guard_key(role)
|
|
78825
|
+
counts = getattr(self, "_todowrite_step_counter", None)
|
|
78826
|
+
if not isinstance(counts, dict):
|
|
78827
|
+
counts = self._todowrite_step_counter = {}
|
|
78828
|
+
if counts.get(key, 0) >= 2:
|
|
78829
|
+
return self._plan_control_feedback("todo_no_progress", self._todo_no_progress_instruction())
|
|
78830
|
+
before = self._todo_material_signature(self.todo.snapshot())
|
|
78831
|
+
plan_before = self._todo_material_signature(self._ensure_blackboard().get("project_todos", []))
|
|
78832
|
+
result = self._dispatch_todo_update_inner(args, role=role, resume=resume)
|
|
78833
|
+
if key != self._todo_progress_guard_key(role):
|
|
78834
|
+
counts.pop(key, None)
|
|
78835
|
+
return result
|
|
78836
|
+
changed = before != self._todo_material_signature(self.todo.snapshot())
|
|
78837
|
+
plan_changed = plan_before != self._todo_material_signature(
|
|
78838
|
+
self._ensure_blackboard().get("project_todos", []),
|
|
78839
|
+
)
|
|
78840
|
+
if changed or plan_changed:
|
|
78841
|
+
counts.pop(key, None)
|
|
78842
|
+
elif self._todo_runtime_has_worker_rows(role) and not str(result).startswith("Error:"):
|
|
78843
|
+
counts[key] = counts.get(key, 0) + 1
|
|
78844
|
+
if len(counts) > 40:
|
|
78845
|
+
counts.pop(next(iter(counts)))
|
|
78846
|
+
return self._plan_control_feedback(
|
|
78847
|
+
"todo_no_progress", f"{self._todo_no_progress_instruction()}\n\n{result}",
|
|
78848
|
+
)
|
|
78849
|
+
return result
|
|
78850
|
+
|
|
78851
|
+
def _dispatch_todo_update_inner(self, args: dict, *, role: str = "", resume: bool = False) -> str:
|
|
77701
78852
|
"""Canonical dispatcher shared by TodoWrite, Rescue, and Resume aliases."""
|
|
77702
78853
|
source = args if isinstance(args, dict) else {}
|
|
77703
78854
|
bb = self._ensure_blackboard()
|
|
@@ -77878,13 +79029,20 @@ body{padding:18px}
|
|
|
77878
79029
|
txt = str(output or "").strip()
|
|
77879
79030
|
low = txt.lower()
|
|
77880
79031
|
has_worker_rows = self._todo_runtime_has_worker_rows()
|
|
77881
|
-
changed =
|
|
79032
|
+
changed = (
|
|
79033
|
+
self._todo_material_signature(before_rows)
|
|
79034
|
+
!= self._todo_material_signature(after_rows if after_rows is not None else self.todo.snapshot())
|
|
79035
|
+
) if before_rows is not None else False
|
|
77882
79036
|
if not txt:
|
|
77883
79037
|
return ("failed", "empty output")
|
|
77884
79038
|
if txt.startswith("Error:"):
|
|
77885
79039
|
return ("failed", txt[6:].strip() or "unknown error")
|
|
79040
|
+
if self._tool_control_feedback_outcome(tool_name, txt) == "todo_no_progress":
|
|
79041
|
+
return ("no_progress", "canonical todo state unchanged; execute the current task")
|
|
77886
79042
|
if changed:
|
|
77887
79043
|
return ("ok", "todo updated")
|
|
79044
|
+
if before_rows is not None and has_worker_rows:
|
|
79045
|
+
return ("no_progress", "canonical todo state unchanged; execute the current task")
|
|
77888
79046
|
if txt == self.todo.no_changes_text() or "no todo changes" in low:
|
|
77889
79047
|
if has_worker_rows:
|
|
77890
79048
|
return ("ok", "todo already up to date")
|
|
@@ -79508,6 +80666,13 @@ body{padding:18px}
|
|
|
79508
80666
|
f"Error: tool '{name}' is unavailable to remote Program sessions because it can access "
|
|
79509
80667
|
"resources outside the isolated session workspace."
|
|
79510
80668
|
)
|
|
80669
|
+
# A real work/evidence action releases this role's Todo cooldown, also
|
|
80670
|
+
# for root Todos and external MCP tools. Bookkeeping alone does not.
|
|
80671
|
+
if getattr(self, "_todowrite_step_counter", {}) and canonicalize_tool_name(name) not in {
|
|
80672
|
+
"TodoWrite", "TodoWriteRescue", "compress", "tool_memory", "task_list",
|
|
80673
|
+
"task_get", "read_from_blackboard", "read_inbox", "route_to_next_agent",
|
|
80674
|
+
}:
|
|
80675
|
+
self._todowrite_step_counter.pop(self._todo_progress_guard_key(role_key), None)
|
|
79511
80676
|
# External MCP tools (mcp__<server>__<tool>): route to the owning
|
|
79512
80677
|
# subprocess. Handled before any built-in branch so an MCP name can
|
|
79513
80678
|
# never be shadowed by a builtin, and so every role/mode reaches it.
|
|
@@ -79517,16 +80682,6 @@ body{padding:18px}
|
|
|
79517
80682
|
return f"Error: MCP is not available in this session for tool '{name}'"
|
|
79518
80683
|
self._emit("status", {"summary": f"calling MCP tool {name}"})
|
|
79519
80684
|
return mgr.call(name, args if isinstance(args, dict) else {})
|
|
79520
|
-
# Fix 5d: Reset TodoWrite loop counter on non-TodoWrite tool calls
|
|
79521
|
-
if name not in ("TodoWrite", "TodoWriteRescue") and hasattr(self, '_todowrite_step_counter'):
|
|
79522
|
-
try:
|
|
79523
|
-
_rst_step = self._get_active_plan_step()
|
|
79524
|
-
if isinstance(_rst_step, dict):
|
|
79525
|
-
_rst_id = str(_rst_step.get("id", "") or "")
|
|
79526
|
-
if _rst_id:
|
|
79527
|
-
self._todowrite_step_counter.pop(_rst_id, None)
|
|
79528
|
-
except Exception:
|
|
79529
|
-
pass
|
|
79530
80685
|
if name == "bash":
|
|
79531
80686
|
guard_error = self._guard_shell_write_scope(str(args.get("command", "") or ""), self.files_root)
|
|
79532
80687
|
if guard_error:
|
|
@@ -87497,6 +88652,8 @@ body{padding:18px}
|
|
|
87497
88652
|
"status",
|
|
87498
88653
|
{
|
|
87499
88654
|
"summary": (
|
|
88655
|
+
"todo unchanged; continue current task execution"
|
|
88656
|
+
if result_item.get("control_outcome") == "todo_no_progress" else
|
|
87500
88657
|
"todo control gate preserved canonical current-step subtasks; "
|
|
87501
88658
|
"continue the existing in_progress item"
|
|
87502
88659
|
)
|
|
@@ -91450,6 +92607,7 @@ class SessionManager:
|
|
|
91450
92607
|
except Exception:
|
|
91451
92608
|
pass
|
|
91452
92609
|
opts = []
|
|
92610
|
+
profiles_changed = False
|
|
91453
92611
|
ollama_profile_id = ""
|
|
91454
92612
|
ollama_base = self.ollama_base
|
|
91455
92613
|
for pid, profile in sorted(self.user_model_profiles.items(), key=lambda x: x[0]):
|
|
@@ -91472,6 +92630,23 @@ class SessionManager:
|
|
|
91472
92630
|
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
91473
92631
|
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
91474
92632
|
"response_stream": bool(profile.get("response_stream", False)),
|
|
92633
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
92634
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
92635
|
+
"reasoning_supported": (
|
|
92636
|
+
profile.get("reasoning_supported")
|
|
92637
|
+
if profile.get("reasoning_supported") is not None
|
|
92638
|
+
else model_reasoning_style(
|
|
92639
|
+
str(profile.get("provider", "")), model, caps
|
|
92640
|
+
)
|
|
92641
|
+
!= "none"
|
|
92642
|
+
),
|
|
92643
|
+
"reasoning_style": str(
|
|
92644
|
+
profile.get("reasoning_style")
|
|
92645
|
+
or model_reasoning_style(str(profile.get("provider", "")), model, caps)
|
|
92646
|
+
or ""
|
|
92647
|
+
),
|
|
92648
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
92649
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
91475
92650
|
"capabilities": caps,
|
|
91476
92651
|
}
|
|
91477
92652
|
)
|
|
@@ -91484,7 +92659,24 @@ class SessionManager:
|
|
|
91484
92659
|
base = str(profile.get("base_url", self.ollama_base) or self.ollama_base).strip()
|
|
91485
92660
|
if not base:
|
|
91486
92661
|
continue
|
|
91487
|
-
tags = list_ollama_models_cached(
|
|
92662
|
+
tags = list_ollama_models_cached(
|
|
92663
|
+
base,
|
|
92664
|
+
force_refresh=bool(force_probe),
|
|
92665
|
+
background=not force_probe,
|
|
92666
|
+
)
|
|
92667
|
+
ollama_records = probe_provider_models(
|
|
92668
|
+
profile,
|
|
92669
|
+
force_refresh=bool(force_probe),
|
|
92670
|
+
background=not force_probe,
|
|
92671
|
+
)
|
|
92672
|
+
profiles_changed = merge_probed_models_into_profile(
|
|
92673
|
+
profile, ollama_records, model_ids=tags
|
|
92674
|
+
) or profiles_changed
|
|
92675
|
+
ollama_record_map = {
|
|
92676
|
+
str(record.get("id", "")): record
|
|
92677
|
+
for record in ollama_records
|
|
92678
|
+
if isinstance(record, dict) and str(record.get("id", "")).strip()
|
|
92679
|
+
}
|
|
91488
92680
|
if tags:
|
|
91489
92681
|
if pid == self.user_active_profile_id:
|
|
91490
92682
|
self.ollama_env_tags = list(tags)
|
|
@@ -91499,6 +92691,13 @@ class SessionManager:
|
|
|
91499
92691
|
infer_model_multimodal_capabilities("ollama", tag),
|
|
91500
92692
|
parse_capability_overrides(profile.get("capabilities", {})),
|
|
91501
92693
|
)
|
|
92694
|
+
tag_record = ollama_record_map.get(tag)
|
|
92695
|
+
tag_record_caps = (
|
|
92696
|
+
tag_record.get("capabilities", {})
|
|
92697
|
+
if isinstance(tag_record, dict)
|
|
92698
|
+
else {}
|
|
92699
|
+
)
|
|
92700
|
+
tag_record_probed = isinstance(tag_record, dict)
|
|
91502
92701
|
opts.append(
|
|
91503
92702
|
{
|
|
91504
92703
|
"selection": selection,
|
|
@@ -91510,9 +92709,99 @@ class SessionManager:
|
|
|
91510
92709
|
"thinking_hint": bool(profile.get("thinking_hint", self.thinking)),
|
|
91511
92710
|
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
91512
92711
|
"response_stream": bool(profile.get("response_stream", False)),
|
|
92712
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
92713
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
92714
|
+
"reasoning_supported": (
|
|
92715
|
+
profile.get("reasoning_supported")
|
|
92716
|
+
if profile.get("reasoning_supported") is not None
|
|
92717
|
+
else tag_record_caps.get("reasoning_supported")
|
|
92718
|
+
if isinstance(tag_record_caps, dict)
|
|
92719
|
+
and tag_record_caps.get("reasoning_supported") is not None
|
|
92720
|
+
else model_reasoning_style(
|
|
92721
|
+
"ollama",
|
|
92722
|
+
tag,
|
|
92723
|
+
tag_caps,
|
|
92724
|
+
capabilities_probed=tag_record_probed,
|
|
92725
|
+
)
|
|
92726
|
+
!= "none"
|
|
92727
|
+
),
|
|
92728
|
+
"reasoning_style": str(
|
|
92729
|
+
profile.get("reasoning_style")
|
|
92730
|
+
or tag_record_caps.get("reasoning_style", "")
|
|
92731
|
+
or model_reasoning_style(
|
|
92732
|
+
"ollama",
|
|
92733
|
+
tag,
|
|
92734
|
+
tag_caps,
|
|
92735
|
+
capabilities_probed=tag_record_probed,
|
|
92736
|
+
)
|
|
92737
|
+
or ""
|
|
92738
|
+
),
|
|
92739
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
92740
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
91513
92741
|
"capabilities": tag_caps,
|
|
91514
92742
|
}
|
|
91515
92743
|
)
|
|
92744
|
+
for pid, profile in sorted(self.user_model_profiles.items(), key=lambda x: x[0]):
|
|
92745
|
+
configured = [str(x).strip() for x in profile.get("models", []) if str(x).strip()]
|
|
92746
|
+
records = probe_provider_models(
|
|
92747
|
+
profile,
|
|
92748
|
+
force_refresh=bool(force_probe),
|
|
92749
|
+
background=not force_probe,
|
|
92750
|
+
)
|
|
92751
|
+
profiles_changed = merge_probed_models_into_profile(profile, records) or profiles_changed
|
|
92752
|
+
for rec in records:
|
|
92753
|
+
model_id = str(rec.get("id", "") or "").strip()
|
|
92754
|
+
if model_id and model_id not in configured:
|
|
92755
|
+
configured.append(model_id)
|
|
92756
|
+
for model_id in configured:
|
|
92757
|
+
selection = f"{pid}::{model_id}"
|
|
92758
|
+
if not model_id or selection in seen:
|
|
92759
|
+
continue
|
|
92760
|
+
seen.add(selection)
|
|
92761
|
+
rec = next((r for r in records if str(r.get("id", "")) == model_id), {})
|
|
92762
|
+
rcaps = rec.get("capabilities", {}) if isinstance(rec, dict) else {}
|
|
92763
|
+
opts.append({
|
|
92764
|
+
"selection": selection, "profile_id": pid,
|
|
92765
|
+
"provider": profile.get("provider", "unknown"), "model": model_id,
|
|
92766
|
+
"label": f"{profile.get('label', pid)} | {model_id}",
|
|
92767
|
+
"source": "provider-probe" if rec else profile.get("source", ""),
|
|
92768
|
+
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
92769
|
+
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
92770
|
+
"response_stream": bool(profile.get("response_stream", False)),
|
|
92771
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
92772
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
92773
|
+
"reasoning_supported": (
|
|
92774
|
+
rcaps.get("reasoning_supported")
|
|
92775
|
+
if isinstance(rcaps, dict) and rcaps.get("reasoning_supported") is not None
|
|
92776
|
+
else profile.get("reasoning_supported")
|
|
92777
|
+
if profile.get("reasoning_supported") is not None
|
|
92778
|
+
else model_reasoning_style(
|
|
92779
|
+
str(profile.get("provider", "")),
|
|
92780
|
+
model_id,
|
|
92781
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
92782
|
+
capabilities_probed=bool(rec),
|
|
92783
|
+
)
|
|
92784
|
+
!= "none"
|
|
92785
|
+
),
|
|
92786
|
+
"reasoning_style": str(
|
|
92787
|
+
(rcaps.get("reasoning_style") if isinstance(rcaps, dict) else "")
|
|
92788
|
+
or profile.get("reasoning_style", "")
|
|
92789
|
+
or model_reasoning_style(
|
|
92790
|
+
str(profile.get("provider", "")),
|
|
92791
|
+
model_id,
|
|
92792
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
92793
|
+
capabilities_probed=bool(rec),
|
|
92794
|
+
)
|
|
92795
|
+
or ""
|
|
92796
|
+
),
|
|
92797
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
92798
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
92799
|
+
"capabilities": merge_multimodal_capabilities(infer_model_multimodal_capabilities(str(profile.get("provider", "")), model_id), parse_capability_overrides(profile.get("capabilities", {}))),
|
|
92800
|
+
})
|
|
92801
|
+
for _option in opts:
|
|
92802
|
+
_pid = str(_option.get("profile_id", "") or "")
|
|
92803
|
+
_profile = self.user_model_profiles.get(_pid, {})
|
|
92804
|
+
apply_model_option_runtime_fields(_option, _profile, str(_option.get("model", "") or ""))
|
|
91516
92805
|
runnable_opts = [x for x in opts if self._option_is_runnable(x)]
|
|
91517
92806
|
if runnable_opts:
|
|
91518
92807
|
opts = runnable_opts
|
|
@@ -91536,9 +92825,31 @@ class SessionManager:
|
|
|
91536
92825
|
"thinking_hint": bool(active.get("thinking_hint", False)),
|
|
91537
92826
|
"thinking_stream": bool(active.get("thinking_stream", False)),
|
|
91538
92827
|
"response_stream": bool(active.get("response_stream", False)),
|
|
92828
|
+
"effort": str(active.get("effort", "") or ""),
|
|
92829
|
+
"max_effort": str(active.get("max_effort", "") or ""),
|
|
92830
|
+
"reasoning_supported": (
|
|
92831
|
+
active.get("reasoning_supported")
|
|
92832
|
+
if active.get("reasoning_supported") is not None
|
|
92833
|
+
else model_reasoning_style(
|
|
92834
|
+
str(active.get("provider", "")),
|
|
92835
|
+
str(active.get("model", "") or ""),
|
|
92836
|
+
active.get("capabilities", {}),
|
|
92837
|
+
)
|
|
92838
|
+
!= "none"
|
|
92839
|
+
),
|
|
92840
|
+
"reasoning_style": str(
|
|
92841
|
+
active.get("reasoning_style")
|
|
92842
|
+
or model_reasoning_style(
|
|
92843
|
+
str(active.get("provider", "")),
|
|
92844
|
+
str(active.get("model", "") or ""),
|
|
92845
|
+
active.get("capabilities", {}),
|
|
92846
|
+
)
|
|
92847
|
+
or ""
|
|
92848
|
+
),
|
|
91539
92849
|
"capabilities": active_caps,
|
|
91540
92850
|
},
|
|
91541
92851
|
)
|
|
92852
|
+
apply_model_option_runtime_fields(opts[0], active, str(active.get("model", "") or ""))
|
|
91542
92853
|
option_map.add(selected)
|
|
91543
92854
|
if opts and selected not in option_map:
|
|
91544
92855
|
selected = str(opts[0].get("selection", ""))
|
|
@@ -91546,6 +92857,8 @@ class SessionManager:
|
|
|
91546
92857
|
infer_model_multimodal_capabilities(str(active.get("provider", "")), str(active.get("model", ""))),
|
|
91547
92858
|
parse_capability_overrides(active.get("capabilities", {})),
|
|
91548
92859
|
)
|
|
92860
|
+
if profiles_changed:
|
|
92861
|
+
self._persist_user_prefs()
|
|
91549
92862
|
return {
|
|
91550
92863
|
"provider": active.get("provider", "ollama"),
|
|
91551
92864
|
"models": [x["selection"] for x in opts] or [self.model],
|
|
@@ -91557,7 +92870,7 @@ class SessionManager:
|
|
|
91557
92870
|
"active_capabilities": active_caps,
|
|
91558
92871
|
}
|
|
91559
92872
|
|
|
91560
|
-
def set_runtime_model(self, model: str, thinking: bool | None = None) -> dict:
|
|
92873
|
+
def set_runtime_model(self, model: str, thinking: bool | None = None, settings: dict | None = None) -> dict:
|
|
91561
92874
|
raw = str(model or "").strip()
|
|
91562
92875
|
if not raw:
|
|
91563
92876
|
raise ValueError("model required")
|
|
@@ -91585,6 +92898,11 @@ class SessionManager:
|
|
|
91585
92898
|
raise ValueError(f"profile not found: {pid}")
|
|
91586
92899
|
if selected_model.strip():
|
|
91587
92900
|
profile["model"] = selected_model.strip()
|
|
92901
|
+
profile = apply_model_runtime_settings(profile, str(profile.get("model", "") or ""), settings)
|
|
92902
|
+
for record in probe_provider_models(profile, background=True):
|
|
92903
|
+
if str(record.get("id", "")) == str(profile.get("model", "")):
|
|
92904
|
+
merge_probed_models_into_profile(profile, [record])
|
|
92905
|
+
break
|
|
91588
92906
|
if str(profile.get("provider", "")).lower() == "ollama" and selected_model.strip():
|
|
91589
92907
|
profile["capabilities"] = infer_model_multimodal_capabilities("ollama", selected_model.strip())
|
|
91590
92908
|
if not self._user_profile_is_runnable(profile) and not explicit_profile and selected_model.strip():
|
|
@@ -91644,6 +92962,7 @@ class SessionManager:
|
|
|
91644
92962
|
cfg = dict(config or {})
|
|
91645
92963
|
OllamaClient.clear_global_probe_cache()
|
|
91646
92964
|
profiles, active = self._profiles_from_config(cfg)
|
|
92965
|
+
probe_and_merge_model_profiles(profiles, force_refresh=True)
|
|
91647
92966
|
with self.lock:
|
|
91648
92967
|
normalized: dict[str, dict] = {}
|
|
91649
92968
|
for pid, row in profiles.items():
|
|
@@ -91977,9 +93296,10 @@ window.MathJax={
|
|
|
91977
93296
|
</div>
|
|
91978
93297
|
<div class="actions">
|
|
91979
93298
|
<select id="langSelect"></select>
|
|
91980
|
-
<
|
|
91981
|
-
|
|
91982
|
-
|
|
93299
|
+
<div id="modelPicker" class="model-picker" role="group" aria-label="Model selection">
|
|
93300
|
+
<select id="modelSelect"></select>
|
|
93301
|
+
<button id="modelManageBtn" class="subtle" type="button" title="Manage models" aria-label="Manage models">⚙</button>
|
|
93302
|
+
</div>
|
|
91983
93303
|
<button id="programBtn" class="subtle" type="button">Program</button>
|
|
91984
93304
|
<input id="configInput" type="file" accept=".json,application/json" tabindex="-1" aria-hidden="true" style="position:absolute;left:-10000px;top:auto;width:1px;height:1px;opacity:0;pointer-events:none">
|
|
91985
93305
|
<a id="downloadBtn" href="#">Open Skills Studio</a>
|
|
@@ -92172,6 +93492,7 @@ window.MathJax={
|
|
|
92172
93492
|
</aside>
|
|
92173
93493
|
</main>
|
|
92174
93494
|
</div>
|
|
93495
|
+
<div id="menuPopup" class="menu-popup is-hidden" role="dialog" aria-label="Model management"></div>
|
|
92175
93496
|
<div id="applicationEditor" class="application-modal" hidden>
|
|
92176
93497
|
<div class="application-dialog" role="dialog" aria-modal="true" aria-labelledby="applicationEditorTitle">
|
|
92177
93498
|
<div class="application-dialog-head">
|
|
@@ -92809,18 +94130,63 @@ const CODE_KEYWORDS={default:new Set(['if','else','for','while','switch','case',
|
|
|
92809
94130
|
S.staticMode=STATIC_UI;
|
|
92810
94131
|
async function setTaskLevel(level){if(!S.activeId)return;const lvl=parseInt(level,10);try{await api('/api/sessions/'+S.activeId+'/config/task-level',{method:'POST',body:JSON.stringify({level:lvl})});updateLevelBtn(lvl);scheduleSnapshot({forceFull:false,delayMs:80,allowWhenFrozen:true})}catch(err){showError(err.message||String(err))}}
|
|
92811
94132
|
function updateLevelBtn(level){const btn=E('levelBtn');if(!btn)return;if(!level||level===0){setTextIfChanged(btn,t('btn_level')+': '+t('level_auto'))}else{const labels={1:'L1',2:'L2',3:'L3',4:'L4',5:'L5'};setTextIfChanged(btn,t('btn_level')+': '+(labels[level]||t('level_auto')))}}
|
|
92812
|
-
const LLM_PROVIDER_FIELDS={ollama:[{key:'ollama_url',label:'Ollama URL',type:'url',placeholder:'http://127.0.0.1:11434',hint:'Ollama API endpoint'}],vllm:[{key:'vllm_url',label:'vLLM URL',type:'url',placeholder:'http://localhost:8000/v1',hint:'vLLM OpenAI-compat endpoint'},{key:'vllm_model',label:'Model',type:'text',placeholder:'(auto-detect)',hint:'Leave empty to auto-detect'},{key:'vllm_key',label:'API Key (optional)',type:'password',placeholder:'',hint:'Usually not required for local'}],lmstudio:[{key:'lmstudio_url',label:'LM Studio URL',type:'url',placeholder:'http://localhost:1234/v1',hint:'LM Studio server endpoint'},{key:'lmstudio_model',label:'Model',type:'text',placeholder:'(auto-detect)',hint:'Leave empty to auto-detect'}],openai_compat:[{key:'openai_url',label:'API Base URL',type:'url',placeholder:'https://api.openai.com/v1',hint:'OpenAI-compatible endpoint'},{key:'openai_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'Your API key'},{key:'openai_model',label:'Model',type:'text',placeholder:'gpt-4o-mini',hint:'e.g. gpt-4o, claude-sonnet-4-20250514'}],anthropic:[{key:'anthropic_url',label:'API URL',type:'url',placeholder:'https://api.anthropic.com',hint:'Anthropic API endpoint'},{key:'anthropic_key',label:'API Key',type:'password',placeholder:'sk-ant-...',hint:'Anthropic API key'},{key:'anthropic_model',label:'Model',type:'text',placeholder:'claude-sonnet-4-20250514',hint:'e.g. claude-sonnet-4-20250514, claude-opus-4-20250514'}],glm:[{key:'glm_url',label:'API URL',type:'url',placeholder:'https://open.bigmodel.cn/api/paas/v4',hint:'GLM API endpoint'},{key:'glm_key',label:'API Key',type:'password',placeholder:'',hint:'GLM API Key'},{key:'glm_model',label:'Model',type:'text',placeholder:'glm-4-flash',hint:'e.g. glm-4-flash, glm-4-plus, glm-4v'}],kimi:[{key:'kimi_url',label:'API URL',type:'url',placeholder:'https://api.moonshot.cn/v1',hint:'KIMI/Moonshot API endpoint'},{key:'kimi_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'Moonshot API Key'},{key:'kimi_model',label:'Model',type:'text',placeholder:'moonshot-v1-8k',hint:'e.g. moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k'}],openrouter:[{key:'openrouter_url',label:'API URL',type:'url',placeholder:'https://openrouter.ai/api/v1',hint:'OpenRouter endpoint'},{key:'openrouter_key',label:'API Key',type:'password',placeholder:'sk-or-...',hint:'OpenRouter API Key'},{key:'openrouter_model',label:'Model',type:'text',placeholder:'meta-llama/llama-3.1-8b-instruct',hint:'Full model slug from openrouter.ai/models'}],siliconflow:[{key:'siliconflow_url',label:'API URL',type:'url',placeholder:'https://api.siliconflow.cn/v1',hint:'SiliconFlow API endpoint'},{key:'siliconflow_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'SiliconFlow API key'},{key:'siliconflow_model',label:'Model',type:'text',placeholder:'Qwen/Qwen3-Next-80B-A3B-Instruct',hint:'Model identifier'}],custom_http:[{key:'custom_url',label:'API Endpoint URL',type:'url',placeholder:'https://your-api.com/v1/chat/completions',hint:'Full API endpoint URL'},{key:'custom_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'API key (optional)'},{key:'custom_model',label:'Model',type:'text',placeholder:'model-name',hint:'Model identifier'},{key:'custom_headers',label:'Custom Headers (JSON)',type:'textarea',placeholder:'{"Authorization":"Bearer token","X-Custom":"value"}',hint:'JSON object of additional HTTP headers'},{key:'custom_payload',label:'Payload Template (JSON)',type:'textarea',placeholder:'{"custom_param":"value","stream":true}',hint:'Extra fields merged into the request body'},{key:'temperature',label:'Temperature',type:'number',placeholder:'0.2',hint:'0.0-2.0, lower=deterministic'},{key:'request_timeout',label:'Request Timeout (seconds)',type:'number',placeholder:'3600',hint:'Max seconds per LLM request'}]};
|
|
92813
|
-
function modelReasoningStyle(provider,model){const p=String(provider||'').trim().toLowerCase();const
|
|
94133
|
+
const LLM_PROVIDER_FIELDS={ollama:[{key:'ollama_url',label:'Ollama URL',type:'url',placeholder:'http://127.0.0.1:11434',hint:'Ollama API endpoint'}],vllm:[{key:'vllm_url',label:'vLLM URL',type:'url',placeholder:'http://localhost:8000/v1',hint:'vLLM OpenAI-compat endpoint'},{key:'vllm_model',label:'Model',type:'text',placeholder:'(auto-detect)',hint:'Leave empty to auto-detect'},{key:'vllm_key',label:'API Key (optional)',type:'password',placeholder:'',hint:'Usually not required for local'}],lmstudio:[{key:'lmstudio_url',label:'LM Studio URL',type:'url',placeholder:'http://localhost:1234/v1',hint:'LM Studio server endpoint'},{key:'lmstudio_model',label:'Model',type:'text',placeholder:'(auto-detect)',hint:'Leave empty to auto-detect'}],openai_compat:[{key:'openai_url',label:'API Base URL',type:'url',placeholder:'https://api.openai.com/v1',hint:'OpenAI-compatible endpoint'},{key:'openai_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'Your API key'},{key:'openai_model',label:'Model',type:'text',placeholder:'gpt-4o-mini',hint:'e.g. gpt-4o, claude-sonnet-4-20250514'}],anthropic:[{key:'anthropic_url',label:'API URL',type:'url',placeholder:'https://api.anthropic.com',hint:'Anthropic API endpoint'},{key:'anthropic_key',label:'API Key',type:'password',placeholder:'sk-ant-...',hint:'Anthropic API key'},{key:'anthropic_model',label:'Model',type:'text',placeholder:'claude-sonnet-4-20250514',hint:'e.g. claude-sonnet-4-20250514, claude-opus-4-20250514'}],glm:[{key:'glm_url',label:'API URL',type:'url',placeholder:'https://open.bigmodel.cn/api/paas/v4',hint:'GLM API endpoint'},{key:'glm_key',label:'API Key',type:'password',placeholder:'',hint:'GLM API Key'},{key:'glm_model',label:'Model',type:'text',placeholder:'glm-4-flash',hint:'e.g. glm-4-flash, glm-4-plus, glm-4v'}],kimi:[{key:'kimi_url',label:'API URL',type:'url',placeholder:'https://api.moonshot.cn/v1',hint:'KIMI/Moonshot API endpoint'},{key:'kimi_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'Moonshot API Key'},{key:'kimi_model',label:'Model',type:'text',placeholder:'moonshot-v1-8k',hint:'e.g. moonshot-v1-8k, moonshot-v1-32k, moonshot-v1-128k'}],openrouter:[{key:'openrouter_url',label:'API URL',type:'url',placeholder:'https://openrouter.ai/api/v1',hint:'OpenRouter endpoint'},{key:'openrouter_key',label:'API Key',type:'password',placeholder:'sk-or-...',hint:'OpenRouter API Key'},{key:'openrouter_model',label:'Model',type:'text',placeholder:'meta-llama/llama-3.1-8b-instruct',hint:'Full model slug from openrouter.ai/models'}],siliconflow:[{key:'siliconflow_url',label:'API URL',type:'url',placeholder:'https://api.siliconflow.cn/v1',hint:'SiliconFlow API endpoint'},{key:'siliconflow_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'SiliconFlow API key'},{key:'siliconflow_model',label:'Model',type:'text',placeholder:'Qwen/Qwen3-Next-80B-A3B-Instruct',hint:'Model identifier'}],custom_http:[{key:'custom_name',label:'Provider Name',type:'text',placeholder:'My Provider',hint:'Name shown in model menus'},{key:'custom_title',label:'Provider Title',type:'text',placeholder:'Custom HTTP',hint:'Title used for display and session labels'},{key:'custom_url',label:'API Endpoint URL',type:'url',placeholder:'https://your-api.com/v1/chat/completions',hint:'Full API endpoint URL'},{key:'custom_key',label:'API Key',type:'password',placeholder:'sk-...',hint:'API key (optional)'},{key:'custom_model',label:'Model',type:'text',placeholder:'model-name',hint:'Model identifier'},{key:'custom_headers',label:'Custom Headers (JSON)',type:'textarea',placeholder:'{"Authorization":"Bearer token","X-Custom":"value"}',hint:'JSON object of additional HTTP headers'},{key:'custom_payload',label:'Payload Template (JSON)',type:'textarea',placeholder:'{"custom_param":"value","stream":true}',hint:'Extra fields merged into the request body'},{key:'temperature',label:'Temperature',type:'number',placeholder:'0.2',hint:'0.0-2.0, lower=deterministic'},{key:'request_timeout',label:'Request Timeout (seconds)',type:'number',placeholder:'3600',hint:'Max seconds per LLM request'}]};
|
|
94134
|
+
function modelReasoningStyle(provider,model,capabilities){const p=String(provider||'').trim().toLowerCase();const caps=capabilities&&typeof capabilities==='object'?capabilities:{};const explicit=String(caps.reasoning_style||caps.reasoning_dialect||'').trim().toLowerCase();if(['anthropic','openai','deepseek','glm','ollama','none'].includes(explicit))return explicit;if(caps.reasoning_supported===false)return 'none';if(caps.reasoning_supported===true){if(p==='anthropic')return 'anthropic';if(p==='ollama')return 'ollama';if(p==='glm')return 'glm';return 'openai'}return 'none'}
|
|
94135
|
+
function detectedModelCapabilities(provider,model){const key=String(provider||'').trim().toLowerCase();const target=String(model||'').trim().toLowerCase();const rows=S.llmDetectedModelRecords?.[key];if(Array.isArray(rows)){const row=rows.find(item=>String(item?.id||item?.model||'').trim().toLowerCase()===target);return row?.capabilities&&typeof row.capabilities==='object'?row.capabilities:{}}if(rows&&typeof rows==='object'){const row=rows[model]||rows[target];if(row&&typeof row==='object')return row.capabilities&&typeof row.capabilities==='object'?row.capabilities:row}return {}}
|
|
92814
94136
|
function effortModelFieldId(provider){if(provider==='ollama')return 'llmF_ollama_model';const fields=LLM_PROVIDER_FIELDS[provider]||[];const mf=fields.find(f=>f.key&&f.key.endsWith('_model'));return mf?('llmF_'+mf.key):''}
|
|
92815
94137
|
function effortCurrentModel(provider){const id=effortModelFieldId(provider);if(!id)return '';const el=E(id);return el?String(el.value||'').trim():''}
|
|
92816
|
-
function refreshEffortAvailability(){const provider=E('llmProvider')?.value||'ollama';const eff=E('llmF_effort');const mx=E('llmF_max_effort');const hint=E('llmEffortHint');if(!eff||!mx)return;const model=effortCurrentModel(provider);const style=modelReasoningStyle(provider,model);const supported=style!=='none';eff.disabled=!supported;mx.disabled=!supported;const wrap=E('llmEffortField');const wrap2=E('llmMaxEffortField');for(const w of [wrap,wrap2]){if(w)w.style.opacity=supported?'1':'0.55'}if(hint){if(!model){hint.textContent=t('llm_effort_need_model')}else if(supported){hint.textContent=t('llm_effort_supported').replace('{style}',style)}else{hint.textContent=t('llm_effort_unsupported')}}}
|
|
94138
|
+
function refreshEffortAvailability(){const provider=E('llmProvider')?.value||'ollama';const eff=E('llmF_effort');const mx=E('llmF_max_effort');const hint=E('llmEffortHint');if(!eff||!mx)return;const model=effortCurrentModel(provider);const style=modelReasoningStyle(provider,model,detectedModelCapabilities(provider,model));const supported=style!=='none';eff.disabled=!supported;mx.disabled=!supported;const wrap=E('llmEffortField');const wrap2=E('llmMaxEffortField');for(const w of [wrap,wrap2]){if(w)w.style.opacity=supported?'1':'0.55'}if(hint){if(!model){hint.textContent=t('llm_effort_need_model')}else if(supported){hint.textContent=t('llm_effort_supported').replace('{style}',style)}else{hint.textContent=t('llm_effort_unsupported')}}}
|
|
92817
94139
|
function renderLlmFields(provider){const container=E('llmFieldsContainer');if(!container)return;let html='';const openaiCompatProviders=new Set(['openai_compat','siliconflow','vllm','lmstudio','glm','kimi','openrouter','custom_http']);if(provider==='ollama'){const fields=LLM_PROVIDER_FIELDS.ollama;for(const f of fields){html+='<div class=\"llm-field\"><label>'+esc(f.label)+'</label><input type=\"'+f.type+'\" id=\"llmF_'+f.key+'\" placeholder=\"'+esc(f.placeholder||'')+'\" value=\"\"><div class=\"llm-hint\">'+esc(f.hint||'')+'</div></div>'}html+='<div class=\"llm-field\"><label>'+esc(t('llm_model'))+'</label><div style=\"display:flex;gap:8px;align-items:center\"><select id=\"llmF_ollama_model\" style=\"flex:1\"><option value=\"\">-- '+esc(t('llm_scan_first'))+' --</option></select><button type=\"button\" id=\"ollamaScanBtn\" class=\"llm-modal-btn-secondary\" style=\"flex:none;padding:6px 12px\">'+esc(t('llm_scan'))+'</button></div><div class=\"llm-hint\" id=\"ollamaScanHint\">'+esc(t('llm_scan_hint'))+'</div></div>'}else{const fields=LLM_PROVIDER_FIELDS[provider]||[];for(const f of fields){if(f.type==='textarea'){html+='<div class=\"llm-field\"><label>'+esc(f.label)+'</label><textarea id=\"llmF_'+f.key+'\" placeholder=\"'+esc(f.placeholder||'')+'\" rows=\"3\" style=\"width:100%;padding:8px 10px;border:1px solid var(--line,#d9e1ec);border-radius:8px;font-size:.84rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;resize:vertical;box-sizing:border-box\"></textarea><div class=\"llm-hint\">'+esc(f.hint||'')+'</div></div>'}else{html+='<div class=\"llm-field\"><label>'+esc(f.label)+'</label><input type=\"'+(f.type==='number'?'text':f.type)+'\" id=\"llmF_'+f.key+'\" placeholder=\"'+esc(f.placeholder||'')+'\" value=\"\"><div class=\"llm-hint\">'+esc(f.hint||'')+'</div></div>'}}if(openaiCompatProviders.has(provider)){html+='<div class=\"llm-field\"><div style=\"display:flex;gap:8px;align-items:center\"><button type=\"button\" id=\"localScanBtn\" class=\"llm-modal-btn-secondary\" style=\"flex:none;padding:6px 12px\">'+esc(t('llm_scan'))+'</button></div><div class=\"llm-hint\" id=\"localScanHint\">'+esc(t('llm_scan_hint'))+'</div></div>'}}html+='<div class=\"llm-field\"><label>'+esc(t('llm_thinking_stream'))+'</label><select id=\"llmF_thinking_stream\"><option value=\"true\">'+esc(t('llm_enabled'))+'</option><option value=\"false\" selected>'+esc(t('llm_disabled'))+'</option></select></div>';html+='<div class=\"llm-field\"><label>'+esc(t('llm_response_stream'))+'</label><select id=\"llmF_response_stream\"><option value=\"true\">'+esc(t('llm_enabled'))+'</option><option value=\"false\" selected>'+esc(t('llm_disabled'))+'</option></select><div class=\"llm-hint\">'+esc(t('llm_response_stream_hint'))+'</div></div>';html+='<div class=\"llm-field\" id=\"llmEffortField\"><label>'+esc(t('llm_effort'))+'</label><select id=\"llmF_effort\"><option value=\"\" selected>'+esc(t('llm_effort_auto'))+'</option><option value=\"off\">'+esc(t('llm_effort_off'))+'</option><option value=\"low\">'+esc(t('llm_effort_low'))+'</option><option value=\"medium\">'+esc(t('llm_effort_medium'))+'</option><option value=\"high\">'+esc(t('llm_effort_high'))+'</option><option value=\"max\">'+esc(t('llm_effort_max'))+'</option></select></div>';html+='<div class=\"llm-field\" id=\"llmMaxEffortField\"><label>'+esc(t('llm_max_effort'))+'</label><select id=\"llmF_max_effort\"><option value=\"\">'+esc(t('llm_effort_no_ceiling'))+'</option><option value=\"low\">'+esc(t('llm_effort_low'))+'</option><option value=\"medium\">'+esc(t('llm_effort_medium'))+'</option><option value=\"high\">'+esc(t('llm_effort_high'))+'</option><option value=\"max\" selected>'+esc(t('llm_effort_max'))+'</option></select><div class=\"llm-hint\" id=\"llmEffortHint\"></div></div>';container.innerHTML=html;if(provider!=='custom_http'){const defaults=LLM_PROVIDER_FIELDS[provider]||[];for(const f of defaults){if(f.type!=='url')continue;const el=E('llmF_'+f.key);if(el&&!String(el.value||'').trim())el.value=String(f.placeholder||'')}}if(provider==='ollama'){const scanBtn=E('ollamaScanBtn');if(scanBtn)scanBtn.onclick=()=>scanOllamaModels()}if(openaiCompatProviders.has(provider)){const scanBtn=E('localScanBtn');if(scanBtn)scanBtn.onclick=()=>scanOpenAICompatModels(provider)}const _mfid=effortModelFieldId(provider);if(_mfid){const _mfe=E(_mfid);if(_mfe){_mfe.addEventListener('input',refreshEffortAvailability);_mfe.addEventListener('change',refreshEffortAvailability)}}refreshEffortAvailability()}
|
|
92818
|
-
async function scanOllamaModels(){const urlEl=E('llmF_ollama_url');const sel=E('llmF_ollama_model');const hint=E('ollamaScanHint');const baseUrl=(urlEl?.value||'').trim()||'http://127.0.0.1:11434';if(hint)hint.textContent=t('llm_scanning');try{const res=await fetch('/api/ollama/models?base_url='+encodeURIComponent(baseUrl));const data=await res.json();if(!data.ok||!data.models?.length){if(hint)hint.textContent=t('llm_scan_empty')+(data.error?' ('+data.error+')':'');return}if(sel){sel.innerHTML='';for(const m of data.models){const op=document.createElement('option');op.value=m;op.textContent=m;sel.appendChild(op)}}if(hint)hint.textContent=t('llm_scan_found').replace('{n}',String(data.models.length))}catch(err){if(hint)hint.textContent=t('llm_scan_error')+': '+(err.message||String(err))}}
|
|
92819
|
-
async function scanOpenAICompatModels(provider){const scanMap={openai_compat:{urlKey:'openai_url',modelKey:'openai_model',keyKey:'openai_key',defaultUrl:'https://api.openai.com/v1'},siliconflow:{urlKey:'siliconflow_url',modelKey:'siliconflow_model',keyKey:'siliconflow_key',defaultUrl:'https://api.siliconflow.cn/v1'},vllm:{urlKey:'vllm_url',modelKey:'vllm_model',keyKey:'vllm_key',defaultUrl:'http://localhost:8000/v1'},lmstudio:{urlKey:'lmstudio_url',modelKey:'lmstudio_model',keyKey:'lmstudio_key',defaultUrl:'http://localhost:1234/v1'},glm:{urlKey:'glm_url',modelKey:'glm_model',keyKey:'glm_key',defaultUrl:'https://open.bigmodel.cn/api/paas/v4'},kimi:{urlKey:'kimi_url',modelKey:'kimi_model',keyKey:'kimi_key',defaultUrl:'https://api.moonshot.cn/v1'},openrouter:{urlKey:'openrouter_url',modelKey:'openrouter_model',keyKey:'openrouter_key',defaultUrl:'https://openrouter.ai/api/v1'},custom_http:{urlKey:'custom_url',modelKey:'custom_model',keyKey:'custom_key',defaultUrl:''}};const normalizedProvider=String(provider||'openai_compat').trim()||'openai_compat';const meta=scanMap[normalizedProvider]||scanMap.openai_compat;const urlEl=E('llmF_'+meta.urlKey);const modelEl=E('llmF_'+meta.modelKey);const hint=E('localScanHint');const baseUrl=(urlEl?.value||'').trim()||meta.defaultUrl||'';const apiKey=(E('llmF_'+meta.keyKey)?.value||'').trim();if(hint)hint.textContent=t('llm_scanning');try{let url='/api/openai_compat/models?provider='+encodeURIComponent(normalizedProvider)+'&base_url='+encodeURIComponent(baseUrl);if(apiKey)url+='&api_key='+encodeURIComponent(apiKey);const res=await fetch(url);const data=await res.json();const models=Array.isArray(data.models)?data.models.filter(Boolean):[];if(!data.ok){if(hint)hint.textContent=t('llm_scan_error')+(data.error?' ('+data.error+')':'');return}if(models.length){if(modelEl&&!String(modelEl.value||'').trim())modelEl.value=models[0];try{refreshEffortAvailability()}catch(e){}if(hint)hint.textContent=t('llm_scan_found').replace('{n}',String(models.length))+': '+models.slice(0,3).join(', ');return}if(data.reachable){if(hint)hint.textContent=t('llm_scan_reachable_manual')+(data.error?' ('+data.error+')':'');return}if(hint)hint.textContent=t('llm_scan_empty')+(data.error?' ('+data.error+')':'')}catch(err){if(hint)hint.textContent=t('llm_scan_error')+': '+(err.message||String(err))}}
|
|
94140
|
+
async function scanOllamaModels(){const urlEl=E('llmF_ollama_url');const sel=E('llmF_ollama_model');const hint=E('ollamaScanHint');const baseUrl=(urlEl?.value||'').trim()||'http://127.0.0.1:11434';if(hint)hint.textContent=t('llm_scanning');try{const res=await fetch('/api/ollama/models?base_url='+encodeURIComponent(baseUrl));const data=await res.json();if(!data.ok||!data.models?.length){if(hint)hint.textContent=t('llm_scan_empty')+(data.error?' ('+data.error+')':'');return}if(sel){sel.innerHTML='';for(const m of data.models){const op=document.createElement('option');op.value=m;op.textContent=m;sel.appendChild(op)}}S.llmDetectedModels=S.llmDetectedModels||{};S.llmDetectedModelRecords=S.llmDetectedModelRecords||{};S.llmDetectedModels.ollama=data.models.slice();S.llmDetectedModelRecords.ollama=Array.isArray(data.model_records)?data.model_records.slice():data.models.map(id=>({id,capabilities:{}}));if(hint)hint.textContent=t('llm_scan_found').replace('{n}',String(data.models.length));refreshEffortAvailability()}catch(err){if(hint)hint.textContent=t('llm_scan_error')+': '+(err.message||String(err))}}
|
|
94141
|
+
async function scanOpenAICompatModels(provider){const scanMap={openai_compat:{urlKey:'openai_url',modelKey:'openai_model',keyKey:'openai_key',defaultUrl:'https://api.openai.com/v1'},anthropic:{urlKey:'anthropic_url',modelKey:'anthropic_model',keyKey:'anthropic_key',defaultUrl:'https://api.anthropic.com'},siliconflow:{urlKey:'siliconflow_url',modelKey:'siliconflow_model',keyKey:'siliconflow_key',defaultUrl:'https://api.siliconflow.cn/v1'},vllm:{urlKey:'vllm_url',modelKey:'vllm_model',keyKey:'vllm_key',defaultUrl:'http://localhost:8000/v1'},lmstudio:{urlKey:'lmstudio_url',modelKey:'lmstudio_model',keyKey:'lmstudio_key',defaultUrl:'http://localhost:1234/v1'},glm:{urlKey:'glm_url',modelKey:'glm_model',keyKey:'glm_key',defaultUrl:'https://open.bigmodel.cn/api/paas/v4'},kimi:{urlKey:'kimi_url',modelKey:'kimi_model',keyKey:'kimi_key',defaultUrl:'https://api.moonshot.cn/v1'},openrouter:{urlKey:'openrouter_url',modelKey:'openrouter_model',keyKey:'openrouter_key',defaultUrl:'https://openrouter.ai/api/v1'},custom_http:{urlKey:'custom_url',modelKey:'custom_model',keyKey:'custom_key',defaultUrl:''}};const normalizedProvider=String(provider||'openai_compat').trim()||'openai_compat';const meta=scanMap[normalizedProvider]||scanMap.openai_compat;const urlEl=E('llmF_'+meta.urlKey);const modelEl=E('llmF_'+meta.modelKey);const hint=E('localScanHint');const baseUrl=(urlEl?.value||'').trim()||meta.defaultUrl||'';const apiKey=(E('llmF_'+meta.keyKey)?.value||'').trim();if(hint)hint.textContent=t('llm_scanning');try{let url='/api/openai_compat/models?provider='+encodeURIComponent(normalizedProvider)+'&base_url='+encodeURIComponent(baseUrl);if(apiKey)url+='&api_key='+encodeURIComponent(apiKey);const res=await fetch(url);const data=await res.json();const models=Array.isArray(data.models)?data.models.filter(Boolean):[];const records=Array.isArray(data.model_records)?data.model_records.filter(item=>item&&String(item.id||item.model||'').trim()):models.map(id=>({id,capabilities:{}}));if(!data.ok){if(hint)hint.textContent=t('llm_scan_error')+(data.error?' ('+data.error+')':'');return}if(models.length){S.llmDetectedModels=S.llmDetectedModels||{};S.llmDetectedModelRecords=S.llmDetectedModelRecords||{};S.llmDetectedModels[normalizedProvider]=models.slice();S.llmDetectedModelRecords[normalizedProvider]=records.slice();if(modelEl&&!String(modelEl.value||'').trim())modelEl.value=models[0];try{refreshEffortAvailability()}catch(e){}if(hint)hint.textContent=t('llm_scan_found').replace('{n}',String(models.length))+': '+models.slice(0,3).join(', ');return}if(data.reachable){if(hint)hint.textContent=t('llm_scan_reachable_manual')+(data.error?' ('+data.error+')':'');return}if(hint)hint.textContent=t('llm_scan_empty')+(data.error?' ('+data.error+')':'')}catch(err){if(hint)hint.textContent=t('llm_scan_error')+': '+(err.message||String(err))}}
|
|
92820
94142
|
function collectLlmConfig(){const provider=E('llmProvider')?.value||'ollama';const config={provider:provider};if(provider==='ollama'){config.ollama_url=(E('llmF_ollama_url')?.value||'').trim()||'http://127.0.0.1:11434';config.ollama_model=E('llmF_ollama_model')?.value||''}else if(provider==='custom_http'){const fields=LLM_PROVIDER_FIELDS.custom_http;for(const f of fields){const el=E('llmF_'+f.key);if(!el)continue;if(f.type==='textarea'){config[f.key]=el.value.trim()}else if(f.key==='temperature'){const v=parseFloat(el.value);if(!isNaN(v))config[f.key]=v}else if(f.key==='request_timeout'){const v=parseInt(el.value,10);if(!isNaN(v)&&v>0)config[f.key]=v}else{config[f.key]=el.value.trim()}}}else{const fields=LLM_PROVIDER_FIELDS[provider]||[];for(const f of fields){const el=E('llmF_'+f.key);if(el){const raw=el.value.trim();config[f.key]=(provider!=='custom_http'&&f.type==='url')?(raw||String(f.placeholder||'').trim()):raw}}}config.thinking_stream=E('llmF_thinking_stream')?.value==='true';config.response_stream=E('llmF_response_stream')?.value==='true';const _eff=E('llmF_effort');if(_eff&&!_eff.disabled){const _ev=String(_eff.value||'').trim();if(_ev)config.effort=_ev}const _mx=E('llmF_max_effort');if(_mx&&!_mx.disabled){const _mv=String(_mx.value||'').trim();if(_mv)config.max_effort=_mv}return config}
|
|
92821
94143
|
async function submitLlmConfig(){if(!S.activeId){showError(t('select_session_first'));return}const config=collectLlmConfig();try{const payload={filename:'LLM.config.json',mime:'application/json',content_b64:btoa(unescape(encodeURIComponent(JSON.stringify(config,null,2))))};const out=await api('/api/sessions/'+S.activeId+'/uploads',{method:'POST',body:JSON.stringify(payload)});const note=String(out?.note||out?.model_catalog?.note||'').trim();if(!out?.model_catalog){showError(t('config_uploaded_no_profiles'))}else if(note){showError(note)}else{showError('')}const cat=out?.model_catalog||await loadModelCatalog();if(!applyModelCatalog(cat)){renderModelControls()}await refreshSnapshot({forceFull:true,allowWhenFrozen:true});E('llmConfigModal').style.display='none'}catch(err){showError(err.message||String(err))}}
|
|
92822
94144
|
function openLlmConfigModal(){const modal=E('llmConfigModal');if(!modal)return;modal.style.display='flex';const prov=E('llmProvider');if(prov){renderLlmFields(prov.value)}}
|
|
94145
|
+
async function scanAnthropicModels(){const baseUrl=(E('llmF_anthropic_url')?.value||'').trim()||'https://api.anthropic.com';const apiKey=(E('llmF_anthropic_key')?.value||'').trim();const modelEl=E('llmF_anthropic_model');const hint=E('localScanHint');if(hint)hint.textContent=t('llm_scanning');try{let url='/api/openai_compat/models?provider=anthropic&base_url='+encodeURIComponent(baseUrl);if(apiKey)url+='&api_key='+encodeURIComponent(apiKey);const res=await fetch(url);const data=await res.json();const models=Array.isArray(data.models)?data.models.filter(Boolean):[];const records=Array.isArray(data.model_records)?data.model_records:models.map(id=>({id,capabilities:{}}));if(!data.ok){if(hint)hint.textContent=t('llm_scan_error')+(data.error?' ('+data.error+')':'');return}S.llmDetectedModels.anthropic=models.slice();S.llmDetectedModelRecords.anthropic=records.slice();if(modelEl&&models.length&&!String(modelEl.value||'').trim())modelEl.value=models[0];if(hint)hint.textContent=models.length?t('llm_scan_found').replace('{n}',String(models.length))+': '+models.slice(0,3).join(', '):t('llm_scan_reachable_manual');refreshEffortAvailability()}catch(err){if(hint)hint.textContent=t('llm_scan_error')+': '+(err.message||String(err))}}
|
|
92823
94146
|
const COMPACT_AUTO_REFRESH_COUNT=3;
|
|
94147
|
+
S.llmDetectedModels=S.llmDetectedModels||{};
|
|
94148
|
+
S.llmDetectedModelRecords=S.llmDetectedModelRecords||{};
|
|
94149
|
+
setTimeout(()=>{
|
|
94150
|
+
const originalScanOpenAI=scanOpenAICompatModels;
|
|
94151
|
+
const originalScanOllama=scanOllamaModels;
|
|
94152
|
+
const originalCollect=collectLlmConfig;
|
|
94153
|
+
const originalRenderLlmFields=renderLlmFields;
|
|
94154
|
+
renderLlmFields=provider=>{originalRenderLlmFields(provider);if(provider!=='anthropic')return;const container=E('llmFieldsContainer');if(!container||E('localScanBtn'))return;const field=document.createElement('div');field.className='llm-field';field.innerHTML='<div style="display:flex;gap:8px;align-items:center"><button type="button" id="localScanBtn" class="llm-modal-btn-secondary" style="flex:none;padding:6px 12px">'+esc(t('llm_scan'))+'</button></div><div class="llm-hint" id="localScanHint">'+esc(t('llm_scan_hint'))+'</div>';const effortField=E('llmEffortField');container.insertBefore(field,effortField||null);E('localScanBtn').onclick=()=>scanOpenAICompatModels('anthropic')};
|
|
94155
|
+
scanOpenAICompatModels=async provider=>{
|
|
94156
|
+
const key=String(provider||'openai_compat');
|
|
94157
|
+
if(key==='anthropic')await scanAnthropicModels();else await originalScanOpenAI(provider);
|
|
94158
|
+
const modelKey={openai_compat:'openai_model',siliconflow:'siliconflow_model',vllm:'vllm_model',lmstudio:'lmstudio_model',anthropic:'anthropic_model',glm:'glm_model',kimi:'kimi_model',openrouter:'openrouter_model',custom_http:'custom_model'}[key];
|
|
94159
|
+
const current=modelKey?String(E('llmF_'+modelKey)?.value||'').trim():'';
|
|
94160
|
+
if(current){const existing=Array.isArray(S.llmDetectedModels[key])?S.llmDetectedModels[key]:[];if(!existing.includes(current))existing.unshift(current);S.llmDetectedModels[key]=existing;const records=Array.isArray(S.llmDetectedModelRecords[key])?S.llmDetectedModelRecords[key]:[];if(!records.some(item=>String(item?.id||item?.model||'').trim()===current))records.unshift({id:current,capabilities:{}});S.llmDetectedModelRecords[key]=records}
|
|
94161
|
+
};
|
|
94162
|
+
scanOllamaModels=async()=>{await originalScanOllama();const sel=E('llmF_ollama_model');const values=[...(sel?.options||[])].map(x=>String(x.value||'').trim()).filter(Boolean);if(values.length)S.llmDetectedModels.ollama=values;if(values.length&&!Array.isArray(S.llmDetectedModelRecords.ollama))S.llmDetectedModelRecords.ollama=values.map(id=>({id,capabilities:{}}))};
|
|
94163
|
+
collectLlmConfig=()=>{const config=originalCollect();const provider=String(config.provider||'');const detected=Array.isArray(S.llmDetectedModels[provider])?S.llmDetectedModels[provider].filter(Boolean):[];const records=Array.isArray(S.llmDetectedModelRecords[provider])?S.llmDetectedModelRecords[provider]:[];if(detected.length){config.models=detected;const modelListKeys={ollama:'ollama_models',openai_compat:'openai_models',siliconflow:'siliconflow_models',vllm:'vllm_models',lmstudio:'lmstudio_models',glm:'glm_models',kimi:'kimi_models',openrouter:'openrouter_models',custom_http:'custom_models',anthropic:'anthropic_models'};const listKey=modelListKeys[provider];if(listKey)config[listKey]=detected;if(provider==='custom_http')config.custom_models=detected}if(records.length){const reasoning={};const settings=(config.model_settings&&typeof config.model_settings==='object')?{...config.model_settings}:{};for(const record of records){const id=String(record?.id||record?.model||'').trim();const caps=record?.capabilities&&typeof record.capabilities==='object'?record.capabilities:{};if(!id)continue;const clean={};for(const key of ['reasoning_supported','reasoning_style','reasoning_dialect'])if(key in caps)clean[key]=caps[key];if(!Object.keys(clean).length)continue;reasoning[id]=clean;settings[id]={...(settings[id]&&typeof settings[id]==='object'?settings[id]:{}),...clean}}if(Object.keys(reasoning).length){config.model_reasoning_capabilities=reasoning;config.model_settings=settings}}return config};
|
|
94164
|
+
const modelOption=()=>{const selected=String(S.config?.model||'');return (S.modelOptions||[]).find(x=>String(x.selection||'')===selected)||null};
|
|
94165
|
+
let showModelSettings=anchor=>{const popup=E('menuPopup');if(!popup)return;const option=modelOption()||{};const caps=option.capabilities||{};const supports=option.reasoning_supported===true||caps.reasoning_supported===true;const effort=String(option.effort||'');const maxEffort=String(option.max_effort||'');popup.classList.remove('is-hidden','prompt-budget-menu');popup.classList.add('agent-model-menu');popup.style.display='block';popup.setAttribute('aria-hidden','false');popup.innerHTML=`<div class="agent-model-summary"><strong>${esc(option.label||option.model||S.config?.model||'Model settings')}</strong><small>Runtime settings</small></div><label class="model-setting-row">Effort<select id="webModelEffort" ${supports?'':'disabled'}><option value="">Auto</option><option value="off">Off</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="max">Max</option></select></label><label class="model-setting-row">Max effort<select id="webModelMaxEffort" ${supports?'':'disabled'}><option value="">No ceiling</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="max">Max</option></select></label><label class="model-setting-check"><input id="webModelThinking" type="checkbox" ${option.thinking_stream?'checked':''}> Thinking stream</label><label class="model-setting-check"><input id="webModelResponse" type="checkbox" ${option.response_stream?'checked':''}> Response stream</label><div class="agent-model-actions"><button id="webModelSave" class="agent-model-config">Save</button></div>`;const rect=anchor.getBoundingClientRect();popup.style.left=`${Math.max(4,Math.min(rect.left,window.innerWidth-300))}px`;popup.style.top=`${rect.bottom+6}px`;const eff=E('webModelEffort'),mx=E('webModelMaxEffort');if(eff)eff.value=effort;if(mx)mx.value=maxEffort;E('webModelSave').onclick=async ev=>{ev.stopPropagation();const settings={effort:String(eff?.value||''),max_effort:String(mx?.value||''),thinking_stream:!!E('webModelThinking')?.checked,response_stream:!!E('webModelResponse')?.checked};try{const path=S.activeId?('/api/sessions/'+S.activeId+'/config/model'):'/api/config/model';const out=await api(path,{method:'POST',body:JSON.stringify({selection:S.config?.model,model:S.config?.model,...settings})});applyModelCatalog(out);popup.classList.add('is-hidden')}catch(err){showError(err.message||String(err))}}};
|
|
94166
|
+
const showModelManager=async anchor=>{const popup=E('menuPopup');if(!popup||!anchor)return;popup.classList.remove('is-hidden','prompt-budget-menu');popup.classList.add('agent-model-menu');popup.style.display='block';popup.setAttribute('aria-hidden','false');popup.style.bottom='auto';popup.style.transform='';const place=()=>{const rect=anchor.getBoundingClientRect();const width=Math.min(380,window.innerWidth-8);popup.style.width=`${width}px`;popup.style.left=`${Math.max(4,Math.min(rect.left,window.innerWidth-width-4))}px`;popup.style.top=`${Math.min(window.innerHeight-8,rect.bottom+6)}px`};place();popup.innerHTML='<div class="agent-model-summary"><strong>Loading models...</strong><small>Reading provider model catalog</small></div>';try{const cat=await loadModelCatalog(false);if(!cat||typeof cat!=='object')throw new Error('Model catalog unavailable');applyModelCatalog(cat);const options=Array.isArray(cat.options)?cat.options:[];popup.innerHTML='';const head=document.createElement('div');head.className='agent-model-summary';head.innerHTML='<strong>Model management</strong><small>Select a model or open its settings</small>';popup.appendChild(head);const actions=document.createElement('div');actions.className='agent-model-actions';const refresh=document.createElement('button');refresh.type='button';refresh.className='agent-model-config';refresh.title='Refresh provider models';refresh.setAttribute('aria-label','Refresh provider models');refresh.innerHTML='<span class="codicon codicon-refresh"></span>';refresh.onclick=async ev=>{ev.preventDefault();ev.stopPropagation();refresh.disabled=true;try{const next=await loadModelCatalog(true);applyModelCatalog(next);await showModelManager(anchor)}catch(error){showError(error.message||String(error))}finally{refresh.disabled=false}};const importBtn=document.createElement('button');importBtn.type='button';importBtn.className='agent-model-config';importBtn.title='Import LLM config';importBtn.setAttribute('aria-label','Import LLM config');importBtn.innerHTML='<span class="codicon codicon-cloud-upload"></span>';importBtn.onclick=ev=>{ev.preventDefault();ev.stopPropagation();popup.classList.add('is-hidden');openLlmConfigModal()};actions.append(refresh,importBtn);popup.appendChild(actions);const search=document.createElement('input');search.type='search';search.className='agent-model-search';search.placeholder='Search models';search.setAttribute('aria-label','Search models');popup.appendChild(search);const list=document.createElement('div');list.className='agent-model-list';popup.appendChild(list);const render=()=>{const q=String(search.value||'').trim().toLowerCase();list.innerHTML='';const shown=options.filter(o=>!q||[o.label,o.model,o.provider,o.display_name,o.title].join(' ').toLowerCase().includes(q));for(const option of shown){const row=document.createElement('div');row.className='agent-model-option';const select=document.createElement('button');select.type='button';select.classList.toggle('is-active',String(option.selection||'')===String(cat.selected||''));select.innerHTML=`<span class="codicon codicon-${String(option.selection||'')===String(cat.selected||'')?'check':'hubot'}"></span><span>${esc(option.label||option.selection)}</span>`;select.onclick=ev=>{ev.preventDefault();ev.stopPropagation();applyModelSelection(option.selection,anchor).catch(showError)};const settings=document.createElement('button');settings.type='button';settings.className='agent-model-config';settings.title='Model settings';settings.setAttribute('aria-label',`Settings for ${option.label||option.model||option.selection}`);settings.innerHTML='<span class="codicon codicon-settings-gear"></span>';settings.onclick=ev=>{ev.preventDefault();ev.stopPropagation();showModelSettings(anchor,option)};row.append(select,settings);list.appendChild(row)}if(!shown.length)list.innerHTML='<div class="agent-model-summary">No matching models.</div>'};search.oninput=render;render();place();setTimeout(()=>{try{search.focus()}catch(_){ }},0)}catch(error){popup.innerHTML=`<div class="agent-model-summary"><strong>Model management failed</strong><small>${esc(error.message||String(error))}</small></div>`;place()}};
|
|
94167
|
+
const applyModelSelection=async(selection,anchor)=>{const path=S.activeId?('/api/sessions/'+S.activeId+'/config/model'):'/api/config/model';const out=await api(path,{method:'POST',body:JSON.stringify({selection,model:selection})});applyModelCatalog(out);E('menuPopup').classList.add('is-hidden');showError(out?.queued?(out.note||'Model switch queued.'):'Model switched.');scheduleSnapshot({forceFull:true,delayMs:40,allowWhenFrozen:true})};
|
|
94168
|
+
const originalShowModelSettings=showModelSettings;showModelSettings=(anchor,option)=>{const selected=option||modelOption()||{};const popup=E('menuPopup');if(!popup)return;const caps=selected.capabilities||{};const supports=selected.reasoning_supported===true||caps.reasoning_supported===true;const effort=String(selected.effort||'');const maxEffort=String(selected.max_effort||'');popup.classList.remove('is-hidden','prompt-budget-menu');popup.classList.add('agent-model-menu');popup.style.display='block';popup.setAttribute('aria-hidden','false');popup.innerHTML=`<div class="agent-model-summary"><strong>${esc(selected.label||selected.model||'Model settings')}</strong><small>${esc(selected.display_name||selected.title||selected.provider||'')}</small></div><label class="model-setting-row">Effort<select id="webModelEffort" ${supports?'':'disabled'}><option value="">Auto</option><option value="off">Off</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="max">Max</option></select></label><label class="model-setting-row">Max effort<select id="webModelMaxEffort" ${supports?'':'disabled'}><option value="">No ceiling</option><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option><option value="max">Max</option></select></label><label class="model-setting-check"><input id="webModelThinking" type="checkbox" ${selected.thinking_stream?'checked':''}> Thinking stream</label><label class="model-setting-check"><input id="webModelResponse" type="checkbox" ${selected.response_stream?'checked':''}> Response stream</label><div class="agent-model-actions"><button id="webModelBack" class="agent-model-compact" type="button">Back</button><button id="webModelSave" class="agent-model-config" type="button" title="Save model settings" aria-label="Save model settings"><span class="codicon codicon-save"></span></button></div>`;const rect=(anchor||E('modelManageBtn')||E('modelSettingsBtn')).getBoundingClientRect();popup.style.left=`${Math.max(4,Math.min(rect.left,window.innerWidth-380))}px`;popup.style.top=`${rect.bottom+6}px`;popup.style.bottom='auto';E('webModelEffort').value=effort;E('webModelMaxEffort').value=maxEffort;E('webModelBack').onclick=ev=>{ev.stopPropagation();showModelManager(anchor||E('modelManageBtn'))};E('webModelSave').onclick=async ev=>{ev.stopPropagation();const settings={effort:String(E('webModelEffort').value||''),max_effort:String(E('webModelMaxEffort').value||''),thinking_stream:!!E('webModelThinking').checked,response_stream:!!E('webModelResponse').checked};try{const path=S.activeId?('/api/sessions/'+S.activeId+'/config/model'):'/api/config/model';const out=await api(path,{method:'POST',body:JSON.stringify({selection:selected.selection,model:selected.selection,...settings})});applyModelCatalog(out);popup.classList.add('is-hidden');showError('Model settings saved.');scheduleSnapshot({forceFull:true,delayMs:40,allowWhenFrozen:true})}catch(error){showError(error.message||String(error))}}};
|
|
94169
|
+
const bindWebModelManager=()=>{const settingsBtn=E('modelSettingsBtn');if(settingsBtn)settingsBtn.onclick=ev=>{ev.preventDefault();ev.stopPropagation();showModelSettings(settingsBtn)};const manageBtn=E('modelManageBtn');if(manageBtn)manageBtn.onclick=ev=>{ev.preventDefault();ev.stopPropagation();showModelManager(manageBtn)};};
|
|
94170
|
+
bindWebModelManager();
|
|
94171
|
+
const ensureModelConfigAction=()=>{
|
|
94172
|
+
const popup=E('menuPopup'),actions=popup?.querySelector('.agent-model-actions');
|
|
94173
|
+
if(!actions||actions.querySelector('[data-llm-config-action]'))return;
|
|
94174
|
+
const button=actions.querySelector('button[title="Import LLM config"]');
|
|
94175
|
+
if(button){
|
|
94176
|
+
button.dataset.llmConfigAction='1';
|
|
94177
|
+
button.title='Configure LLM';
|
|
94178
|
+
button.setAttribute('aria-label','Configure LLM');
|
|
94179
|
+
button.classList.add('agent-model-config-text');
|
|
94180
|
+
button.innerHTML='<span class="codicon codicon-settings-gear"></span><span>LLM Config</span>';
|
|
94181
|
+
button.style.width='auto';button.style.minWidth='104px';button.style.padding='0 8px';button.style.gap='5px';button.style.fontSize='11px';
|
|
94182
|
+
}
|
|
94183
|
+
};
|
|
94184
|
+
const modelConfigActionObserver=new MutationObserver(ensureModelConfigAction),modelPopup=E('menuPopup');
|
|
94185
|
+
if(modelPopup)modelConfigActionObserver.observe(modelPopup,{childList:true,subtree:true});
|
|
94186
|
+
// Safari may restore this document from bfcache after visiting the IDE;
|
|
94187
|
+
// rebind controls against the restored DOM so the gear remains functional.
|
|
94188
|
+
window.addEventListener('pageshow',bindWebModelManager);
|
|
94189
|
+
},0);
|
|
92824
94190
|
const COMPACT_AUTO_REFRESH_INTERVAL_MS=260;
|
|
92825
94191
|
const E=id=>document.getElementById(id);
|
|
92826
94192
|
const PREVIEW_TOKENS=new Map();
|
|
@@ -93243,9 +94609,11 @@ function setTextIfChanged(el,text){
|
|
|
93243
94609
|
}
|
|
93244
94610
|
function closePopups(except=''){
|
|
93245
94611
|
const keep=String(except||'');
|
|
93246
|
-
for(const menu of document.querySelectorAll('.popup-menu')){
|
|
94612
|
+
for(const menu of document.querySelectorAll('.popup-menu,.menu-popup')){
|
|
93247
94613
|
if(menu.id&&menu.id===keep)continue;
|
|
93248
94614
|
menu.style.display='none';
|
|
94615
|
+
menu.classList.add('is-hidden');
|
|
94616
|
+
menu.setAttribute('aria-hidden','true');
|
|
93249
94617
|
}
|
|
93250
94618
|
S.openPopup=keep;
|
|
93251
94619
|
}
|
|
@@ -93256,9 +94624,13 @@ function setPopupOpen(menuId,open){
|
|
|
93256
94624
|
if(open){
|
|
93257
94625
|
closePopups(id);
|
|
93258
94626
|
menu.style.display='block';
|
|
94627
|
+
menu.classList.remove('is-hidden');
|
|
94628
|
+
menu.setAttribute('aria-hidden','false');
|
|
93259
94629
|
S.openPopup=id;
|
|
93260
94630
|
}else{
|
|
93261
94631
|
menu.style.display='none';
|
|
94632
|
+
menu.classList.add('is-hidden');
|
|
94633
|
+
menu.setAttribute('aria-hidden','true');
|
|
93262
94634
|
if(S.openPopup===id)S.openPopup='';
|
|
93263
94635
|
}
|
|
93264
94636
|
}
|
|
@@ -97462,6 +98834,7 @@ async function refreshAll(forceProbe=false){
|
|
|
97462
98834
|
}
|
|
97463
98835
|
function bindClick(id,fn){const el=E(id);if(el)el.onclick=fn}
|
|
97464
98836
|
window.addEventListener('DOMContentLoaded',()=>bindClick('programBtn',openProgram));
|
|
98837
|
+
window.addEventListener('DOMContentLoaded',()=>{const select=E('modelSelect');if(select)select.onchange=()=>applyModel();const popup=E('menuPopup');if(popup)popup.addEventListener('click',event=>event.stopPropagation())});
|
|
97465
98838
|
window.addEventListener('DOMContentLoaded',()=>{bindClick('refreshUserProcessesBtn',()=>refreshUserProcesses(true).catch(err=>showError(err.message)));USER_PROCESS_STATE.timer=setInterval(()=>{if(document.visibilityState!=='hidden')refreshUserProcesses(false).catch(()=>{})},5000)});
|
|
97466
98839
|
window.addEventListener('DOMContentLoaded',async()=>{for(const id of ['chat','sessionList','todos','tasks','activity','commands','diffs','fileExplorer','catalog']){bindPanelScrollState(id,E(id))}const drop=E('promptComposerShell');const fileInput=E('uploadInput');const promptPick=E('promptFilePick');const promptEl=E('prompt');if(promptPick&&fileInput){promptPick.onclick=(ev)=>{ev.preventDefault();fileInput.click()}}if(drop&&fileInput){let _dragC=0;drop.setAttribute('tabindex','0');drop.addEventListener('click',e=>{if(e.target===drop&&promptEl)promptEl.focus()});fileInput.onchange=()=>uploadFiles(fileInput.files).then(()=>{fileInput.value=''}).catch(err=>showError(err.message));for(const evt of ['dragenter','dragover']){drop.addEventListener(evt,e=>{e.preventDefault();if(evt==='dragenter')_dragC++;drop.classList.add('dragover')})}for(const evt of ['dragleave','dragend']){drop.addEventListener(evt,e=>{e.preventDefault();if(evt==='dragleave')_dragC--;if(_dragC<=0){_dragC=0;drop.classList.remove('dragover')}})}drop.addEventListener('drop',e=>{e.preventDefault();_dragC=0;drop.classList.remove('dragover');const files=e.dataTransfer?.files;if(files&&files.length)uploadFiles(files).catch(err=>showError(err.message))});drop.addEventListener('paste',e=>{const files=clipboardFilesFromEvent(e);if(!files.length)return;e.preventDefault();drop.classList.add('dragover');setTimeout(()=>drop.classList.remove('dragover'),220);uploadFiles(files).catch(err=>showError(err.message||String(err)))})}const configInput=E('configInput');if(configInput){configInput.onchange=()=>uploadLlmConfigFile(configInput.files&&configInput.files[0]).then(()=>{configInput.value=''}).catch(err=>showError(err.message||String(err)))}bindClick('newSessionBtn',createSession);bindClick('renameSessionBtn',renameSession);bindClick('deleteSessionBtn',deleteSession);bindClick('applyModelBtn',applyModel);bindClick('llmConfigBtn',openLlmConfigModal);bindClick('llmModalClose',()=>{E('llmConfigModal').style.display='none'});bindClick('llmConfigConfirm',submitLlmConfig);const llmProv=E('llmProvider');if(llmProv){llmProv.addEventListener('change',()=>renderLlmFields(llmProv.value))}const llmOverlay=E('llmConfigModal');if(llmOverlay){llmOverlay.addEventListener('click',e=>{if(e.target===llmOverlay)llmOverlay.style.display='none'})}bindClick('sendBtn',sendMessage);bindClick('interruptBtn',interruptRun);bindClick('clearStaleTodosBtn',clearStaleTodos);bindClick('planModeBtn',togglePlanMode);bindClick('refreshFilesBtn',()=>refreshFileExplorer(true));bindClick('previewReloadBtn',()=>renderActivePreview(true));bindClick('previewCopyBtn',()=>copyPreviewCode());bindPopupButton('toolsMenuBtn','toolsMenu');bindClick('compactAction',(e)=>{if(e)e.preventDefault();closePopups();compactNow()});bindClick('refreshAction',(e)=>{if(e)e.preventDefault();closePopups();refreshAll(true)});bindPopupButton('levelBtn','levelMenu',(menu)=>{for(const opt of menu.querySelectorAll('.level-option')){opt.addEventListener('click',e=>{e.preventDefault();const lvl=parseInt(opt.getAttribute('data-level')||'0',10);setTaskLevel(lvl);setPopupOpen('levelMenu',false)})}});bindPopupButton('exportMenuBtn','exportMenu',(menu)=>{for(const a of menu.querySelectorAll('.export-item')){a.addEventListener('click',()=>setPopupOpen('exportMenu',false))}});document.addEventListener('click',()=>closePopups());const langSel=E('langSelect');if(langSel){langSel.onchange=()=>setLanguage(langSel.value).then(()=>applyApplicationI18n()).catch(err=>showError(err.message||String(err)))}if(promptEl){promptEl.addEventListener('keydown',e=>{if((e.metaKey||e.ctrlKey)&&e.key==='Enter'){e.preventDefault();sendMessage()}})}bindApplicationStore();applyUiStyle();applyStaticUiClass();applyMainI18n();applyApplicationI18n();_bindPreviewCopyGuard();try{await refreshAll(false);if(!S.sessions.length){const bootCreate=()=>createSession({prompt:false}).catch(err=>showError(err.message||String(err)));if(typeof requestAnimationFrame==='function'){requestAnimationFrame(()=>setTimeout(bootCreate,0))}else{setTimeout(bootCreate,0)}}}catch(err){showError(err.message||String(err))}_deltaStartWatchdog();scheduleSessionPoll(false);document.addEventListener('visibilitychange',()=>{const next=document.visibilityState||'visible';if(next===S.lastVisibilityState)return;S.lastVisibilityState=next;if(next==='hidden'){if(S.deltaWatchdogTimer){clearTimeout(S.deltaWatchdogTimer);S.deltaWatchdogTimer=null}if(S.sessionPollTimer){clearTimeout(S.sessionPollTimer);S.sessionPollTimer=null}if(S.staticMode)freezeAutoUpdates();return}if(S.staticMode&&S.frozen)resumeAutoUpdates();_deltaStartWatchdog();scheduleSessionPoll(true);scheduleSnapshot({forceFull:false,delayMs:40,allowWhenFrozen:true})})})
|
|
97467
98840
|
function kernelNoticeDeviceId(){let value=localStorage.getItem('clouds_kernel_notice_device')||'';if(value.length<24){const bytes=new Uint8Array(18);crypto.getRandomValues(bytes);value='web_'+Array.from(bytes,x=>x.toString(16).padStart(2,'0')).join('');localStorage.setItem('clouds_kernel_notice_device',value)}return value}
|
|
@@ -97473,6 +98846,9 @@ window.addEventListener('DOMContentLoaded',()=>{const search=E('sessionSearch');
|
|
|
97473
98846
|
|
|
97474
98847
|
APP_CSS += r"""
|
|
97475
98848
|
:root{--web-scrollbar-size:8px;--web-scrollbar-thumb:rgba(98,116,142,.44);--web-scrollbar-thumb-hover:rgba(76,98,128,.7)}
|
|
98849
|
+
.model-picker{display:inline-flex;align-items:center;gap:6px;max-width:min(46vw,520px);height:44px}.model-picker select{box-sizing:border-box;min-width:180px;max-width:32vw;height:44px;min-height:44px;padding:0 34px 0 12px;line-height:1.2;appearance:none;-webkit-appearance:none;background:#fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4l3 3 3-3M3 8l3-3 3 3' fill='none' stroke='%235e6c84' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat right 11px center}.model-picker .hidden{display:none}.model-picker button{box-sizing:border-box;flex:0 0 auto;width:44px;height:44px;min-width:44px;min-height:44px;padding:0;display:inline-flex;align-items:center;justify-content:center;line-height:1}.model-picker #modelManageBtn{font-size:25px!important;font-weight:600;line-height:1}
|
|
98850
|
+
.model-setting-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px;font-size:.78rem;color:var(--fg,#0f1b2d)}.model-setting-row select{min-width:130px;padding:4px 6px;border:1px solid var(--line,#d9e1ec);border-radius:6px;background:#fff}.model-setting-check{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:.78rem}.model-setting-check input{accent-color:var(--brand,#1f6feb)}
|
|
98851
|
+
.menu-popup{position:fixed;z-index:10020}.menu-popup.is-hidden{display:none!important}.menu-popup:not(.is-hidden){display:block}.menu-popup.agent-model-menu{width:min(380px,calc(100vw - 8px));max-height:min(460px,72vh);overflow:auto;padding:4px;background:var(--panel,#fff);border:1px solid var(--line,#d9e1ec);border-radius:8px;box-shadow:0 10px 28px rgba(17,31,53,.22);color:var(--fg,#0f1b2d)}.agent-model-menu button{box-sizing:border-box;height:40px!important;min-height:40px!important;line-height:1.2!important;display:flex;align-items:center;color:var(--fg,#0f1b2d);border-radius:7px;padding:8px 10px!important}.agent-model-option{height:42px;padding:1px 0}.agent-model-option>button:first-child{justify-content:flex-start;text-align:left}.agent-model-option .agent-model-config{height:36px!important;min-height:36px!important;justify-content:center;padding:0!important}.agent-model-menu button:hover{background:var(--hover,#edf3fb)}.agent-model-menu button.is-active{color:var(--brand,#1f6feb);font-weight:600}.agent-model-summary{padding:8px 10px;border-bottom:1px solid var(--line,#d9e1ec);color:var(--muted,#667085);font-size:.75rem}.agent-model-summary strong,.agent-model-summary small{display:block}.agent-model-summary strong{color:var(--fg,#0f1b2d);font-size:.82rem}.agent-model-summary small{margin-top:2px}.agent-model-option{display:flex;align-items:center;gap:4px;min-width:0}.agent-model-option>button:first-child{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.agent-model-config{display:inline-grid!important;place-items:center!important;width:26px!important;min-width:26px!important;height:24px!important;min-height:24px!important;padding:0!important;border:1px solid var(--line,#d9e1ec)!important;background:transparent!important;color:var(--muted,#667085)!important}.agent-model-config:hover{background:var(--hover,#edf3fb)!important;color:var(--brand,#1f6feb)!important}.agent-model-actions{display:flex;align-items:center;justify-content:flex-end;gap:5px;padding:6px 4px}.agent-model-search{display:block;width:calc(100% - 16px);margin:7px 8px;padding:6px 8px;border:1px solid var(--line,#d9e1ec);border-radius:6px;background:var(--input,#fff);color:var(--fg,#0f1b2d);box-sizing:border-box}.agent-model-list{min-height:0}.agent-model-menu .codicon:before{display:block;font:600 14px/1 sans-serif}.agent-model-menu .codicon-refresh:before{content:"\21bb"}.agent-model-menu .codicon-cloud-upload:before{content:"\2191"}.agent-model-menu .codicon-settings-gear:before{content:"\2699"}.agent-model-menu .codicon-save:before{content:"\2713"}.agent-model-menu .codicon-check:before{content:"\2713"}.agent-model-menu .codicon-hubot:before{content:"\25c7"}
|
|
97476
98852
|
.session-history-badge{box-sizing:border-box;position:absolute;right:-3px;bottom:-3px;display:flex;align-items:center;justify-content:center;width:15px;height:15px;padding:0;overflow:hidden;border:1px solid #252526;border-radius:50%;background:#c586c0;color:#fff;pointer-events:none}.session-history-badge>.codicon{box-sizing:border-box;position:relative;display:block;flex:0 0 9px;width:9px;height:9px;margin:0;border:1px solid currentColor;border-radius:50%;font-size:0;line-height:0;text-align:center}.session-history-badge>.codicon::before{content:"";position:absolute;left:3px;top:1px;width:1px;height:3px;background:currentColor;transform-origin:50% 100%;transform:rotate(0deg)}.session-history-badge>.codicon::after{content:"";position:absolute;left:4px;top:4px;width:3px;height:1px;background:currentColor;transform-origin:left center;transform:rotate(35deg)}
|
|
97477
98853
|
html,body{scrollbar-gutter:stable}
|
|
97478
98854
|
*{scrollbar-width:thin;scrollbar-color:transparent transparent!important}
|
|
@@ -97486,6 +98862,7 @@ html,body{scrollbar-gutter:stable}
|
|
|
97486
98862
|
#chat,#sessionList,.chat-tabs,.preview-body,.preview-code-scroll,.msg-md .md-code,.msg-code-shell,.msg-diff-shell,#activity,#commands,#diffs,#fileExplorer,#catalog,.popup-menu,.application-list,.application-editor-body,.application-skill-catalog,.modal,.modal-body{scrollbar-gutter:stable}
|
|
97487
98863
|
@media(hover:none),(pointer:coarse){*:active{scrollbar-color:var(--web-scrollbar-thumb) transparent!important}*:active::-webkit-scrollbar-thumb{background:var(--web-scrollbar-thumb)!important;background-clip:padding-box!important}}
|
|
97488
98864
|
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}}
|
|
98865
|
+
.agent-model-menu button{height:34px!important;min-height:34px!important;padding:6px 9px!important;border-radius:7px!important}.agent-model-option{height:36px!important}.agent-model-option .agent-model-config{width:32px!important;min-width:32px!important;height:32px!important;min-height:32px!important}.agent-model-actions{gap:6px;padding:5px 4px}.agent-model-actions .agent-model-config{height:34px!important;min-height:34px!important}.agent-model-config-text{display:inline-flex!important;align-items:center!important;justify-content:center!important;place-items:unset!important;width:auto!important;min-width:104px!important;height:34px!important;min-height:34px!important;padding:0 8px!important;gap:5px!important;color:var(--brand,#1f6feb)!important;font-size:11px!important;line-height:1!important;white-space:nowrap!important}.agent-model-config-text .codicon{display:inline-block!important;flex:0 0 auto}
|
|
97489
98866
|
"""
|
|
97490
98867
|
|
|
97491
98868
|
APP_TS = """type SessionSummary={id:string;title:string;running:boolean;updated_at:number};
|
|
@@ -98079,14 +99456,21 @@ ADMIN_INDEX_HTML = """<!doctype html>
|
|
|
98079
99456
|
<label>History versions<input id="evolutionHistoryDepth" type="number" min="0" max="12" value="2"></label>
|
|
98080
99457
|
<label>Start date<input id="evolutionStartDate" type="date"></label>
|
|
98081
99458
|
<label>End date<input id="evolutionEndDate" type="date"></label>
|
|
98082
|
-
<label
|
|
98083
|
-
<label
|
|
99459
|
+
<label>生成模型<select id="evolutionGeneratorProfile" data-admin-no-i18n></select></label>
|
|
99460
|
+
<label>评审模型<select id="evolutionJudgeProfile" data-admin-no-i18n></select></label>
|
|
99461
|
+
<div class="evolution-model-info evolution-span">
|
|
99462
|
+
<div id="evolutionModelSources" class="evolution-model-sources" aria-live="polite"></div>
|
|
99463
|
+
<p id="evolutionModelResolved" class="evolution-model-resolved" data-admin-no-i18n></p>
|
|
99464
|
+
</div>
|
|
98084
99465
|
<label class="evolution-span">User scope<input id="evolutionUserScope" placeholder="* or comma-separated user ids"></label>
|
|
98085
99466
|
<label class="evolution-span">Session scope<input id="evolutionSessionScope" placeholder="* or comma-separated session ids"></label>
|
|
98086
99467
|
<label class="switch-row evolution-span"><input id="evolutionEventTriggers" type="checkbox">Enable metric/event triggers</label>
|
|
98087
99468
|
</div>
|
|
98088
|
-
<div
|
|
98089
|
-
<div
|
|
99469
|
+
<div id="evolutionPolicyNote" class="notice hidden" role="status" aria-live="polite"></div>
|
|
99470
|
+
<div class="evolution-toolbar">
|
|
99471
|
+
<div class="evolution-actions"><button id="saveEvolutionBtn" type="button">Save Policy</button><button id="runEvolutionBtn" class="secondary" type="button">Run Now</button><button id="killEvolutionBtn" class="danger" type="button">Emergency Off</button></div>
|
|
99472
|
+
<div class="evolution-secondary-actions"><button id="refreshEvolutionModelsBtn" class="ghost" type="button">刷新模型列表</button><button id="reloadEvolutionConfigBtn" class="ghost" type="button">重新载入配置</button></div>
|
|
99473
|
+
</div>
|
|
98090
99474
|
<div id="evolutionBootstrap" class="notice">Restart source is configured in Startup Parameters: <code>inherit</code> keeps existing kernel history; <code>inject</code> adds the embedded kernel without deleting old versions.</div>
|
|
98091
99475
|
</article>
|
|
98092
99476
|
<article class="card evolution-current">
|
|
@@ -98351,6 +99735,30 @@ th{color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.
|
|
|
98351
99735
|
.process-bulk-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 12px;margin-bottom:12px;background:#fff;border:1px solid var(--line);border-radius:10px}.process-select-all{display:flex;flex-direction:row;align-items:center;gap:7px}.process-select-all input,.process-row-check{width:16px;height:16px;margin:0}.process-bulk-bar span{color:var(--muted);font-size:.78rem;margin-right:auto}
|
|
98352
99736
|
.process-layout{display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,360px);gap:12px;align-items:start}.process-table{min-height:250px}.process-table table{min-width:980px}.process-table td{vertical-align:top}.process-command{display:block;max-width:360px;white-space:normal;overflow-wrap:anywhere;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.72rem}.process-cell-meta{display:block;color:var(--muted);font-size:.68rem;margin-top:3px}.process-row-actions{display:flex;gap:5px}.process-row-actions button{padding:5px 8px;font-size:.7rem}.process-detail{position:sticky;top:76px;padding:14px;min-height:250px}.process-detail h3{margin:0 0 8px}.process-detail-grid{display:grid;grid-template-columns:100px 1fr;gap:6px 9px;font-size:.76rem}.process-detail-grid dt{color:var(--muted)}.process-detail-grid dd{margin:0;overflow-wrap:anywhere}.process-output{margin:12px 0 0;max-height:360px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;background:#111827;color:#e5edf8;border-radius:8px;padding:10px;font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace}
|
|
98353
99737
|
.evolution-grid{display:grid;grid-template-columns:minmax(360px,1.15fr) minmax(320px,.85fr);gap:14px;margin-bottom:14px}.evolution-form-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}.evolution-span{grid-column:1/-1}.evolution-actions{margin-top:14px}.evolution-current-body{padding:12px;border:1px solid var(--line);border-radius:10px;background:#f8fafc;margin-bottom:14px;font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;overflow-wrap:anywhere}.evolution-current h4{margin:8px 0}.evolution-pipeline{display:flex;flex-wrap:wrap;gap:7px}.evolution-step{padding:7px 9px;border:1px solid var(--line);border-radius:999px;background:#fff;color:var(--muted);font-size:.72rem}.evolution-step.active{border-color:#8cb2f7;background:#eaf1ff;color:#1849a9}.evolution-step.done{border-color:#a6e0c6;background:#ecfdf3;color:#067647}.evolution-lineage{display:flex;gap:10px;overflow:auto;padding:8px 2px 14px}.evolution-node{min-width:210px;padding:12px;border:1px solid var(--line);border-top:4px solid #98a2b3;border-radius:11px;background:#fff;box-shadow:0 7px 18px rgba(27,39,71,.05)}.evolution-node.active{border-top-color:#12b76a}.evolution-node.canary{border-top-color:#f79009}.evolution-node.rolled_back{border-top-color:#f04438}.evolution-node strong,.evolution-node small{display:block;overflow-wrap:anywhere}.evolution-node small{margin-top:5px;color:var(--muted)}.evolution-detail pre{max-height:620px;overflow:auto;padding:12px;border-radius:9px;background:#101828;color:#e4e7ec;white-space:pre-wrap;overflow-wrap:anywhere}.evolution-score{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:10px 0}.evolution-score div{padding:9px;border:1px solid var(--line);border-radius:9px;background:#f8fafc}.evolution-score strong{display:block;font-size:1.05rem}.evolution-tables table{min-width:720px}
|
|
99738
|
+
.evolution-grid{align-items:start}
|
|
99739
|
+
.evolution-grid>.card,.evolution-tables>.card{min-width:0}
|
|
99740
|
+
.evolution-form-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
|
|
99741
|
+
.evolution-form-grid>label{min-width:0;line-height:1.5}
|
|
99742
|
+
.evolution-form-grid input:not([type="checkbox"]),.evolution-form-grid select{min-width:0;max-width:100%;min-height:42px}
|
|
99743
|
+
.evolution-form-grid select{text-overflow:ellipsis}
|
|
99744
|
+
.evolution-form-grid .switch-row{flex-direction:row;justify-content:flex-start;align-items:center;gap:9px;text-align:left}
|
|
99745
|
+
.evolution-form-grid .switch-row input{flex:0 0 18px}
|
|
99746
|
+
.evolution-model-info{min-width:0;padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface2)}
|
|
99747
|
+
.evolution-model-sources{display:flex;flex-wrap:wrap;align-items:center;gap:7px}
|
|
99748
|
+
.evolution-model-sources .badge{max-width:100%;white-space:normal;overflow-wrap:anywhere;font-size:.72rem;font-weight:600}
|
|
99749
|
+
.evolution-model-resolved{margin:9px 0 0;font-size:.75rem;line-height:1.6;color:var(--muted);white-space:pre-line;overflow-wrap:anywhere}
|
|
99750
|
+
.evolution-toolbar{display:grid;gap:10px;margin:14px 0}
|
|
99751
|
+
.evolution-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin:0;align-items:stretch}
|
|
99752
|
+
.evolution-secondary-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;align-items:stretch}
|
|
99753
|
+
.evolution-toolbar button{display:flex;align-items:center;justify-content:center;min-width:0;min-height:42px;margin:0;padding:9px 10px;font-size:.84rem;line-height:1.4;white-space:normal}
|
|
99754
|
+
.evolution-settings .card-head{flex-wrap:wrap}
|
|
99755
|
+
.evolution-settings .notice{font-size:.8rem;line-height:1.6;overflow-wrap:anywhere}
|
|
99756
|
+
#evolutionPolicyNote{margin:12px 0 0}
|
|
99757
|
+
#evolutionBootstrap{margin-bottom:0}
|
|
99758
|
+
.evolution-current-body{white-space:pre-line}
|
|
99759
|
+
.evolution-tables{grid-template-columns:repeat(2,minmax(0,1fr))}
|
|
99760
|
+
@media(max-width:720px){.evolution-tables{grid-template-columns:minmax(0,1fr)}}
|
|
99761
|
+
@media(max-width:480px){.evolution-actions{grid-template-columns:repeat(2,minmax(0,1fr))}.evolution-actions .danger{grid-column:1/-1}}
|
|
98354
99762
|
.login-overlay{position:fixed;inset:0;background:rgba(10,18,34,.66);display:flex;align-items:center;justify-content:center;z-index:100;backdrop-filter:blur(8px);padding:18px}
|
|
98355
99763
|
.login-overlay.hidden{display:none}
|
|
98356
99764
|
.login-card{width:min(440px,100%);background:#fff;border-radius:18px;padding:25px;box-shadow:0 28px 80px rgba(0,0,0,.28);display:flex;flex-direction:column;gap:14px}
|
|
@@ -98365,7 +99773,7 @@ th{color:var(--muted);font-size:.72rem;text-transform:uppercase;letter-spacing:.
|
|
|
98365
99773
|
.toast.error{background:#912018}
|
|
98366
99774
|
.collab-service-bar{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:12px 0;margin-bottom:8px;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}.collab-service-copy>div{display:flex;align-items:center;gap:9px}.collab-service-copy strong{font-size:.92rem}.collab-service-copy p{margin:5px 0 0;color:var(--muted);font-size:.78rem}.collab-service-actions{justify-content:flex-end}.collab-service-bar+.notice{margin:0 0 12px}.collab-admin-layout{display:grid;grid-template-columns:minmax(310px,390px) minmax(0,1fr);gap:14px;align-items:start}.collab-project-column{display:flex;flex-direction:column;gap:10px}.collab-create-form{display:flex;flex-direction:column;gap:9px}.collab-create-form label{margin:0}.collab-filter-row,.collab-member-filter{display:grid;grid-template-columns:minmax(0,1fr) 130px;gap:8px}.collab-project-list,.collab-member-list,.collab-conflict-list{display:flex;flex-direction:column;gap:8px}.collab-project-row,.collab-member-row,.collab-conflict-row{background:#fff;border:1px solid var(--line);border-radius:10px;padding:11px}.collab-project-row{cursor:pointer}.collab-project-row.active{border-color:#8cb2f7;background:#f1f6ff}.collab-project-row h3,.collab-member-row h4,.collab-conflict-row h4,.collab-detail-head h3{margin:0}.collab-project-row p,.collab-member-row p,.collab-conflict-row p{margin:5px 0;color:var(--muted);font-size:.76rem}.collab-detail-head{display:flex;justify-content:space-between;gap:14px;align-items:flex-start;background:#fff;border:1px solid var(--line);border-radius:10px;padding:14px;margin-bottom:10px}.collab-member-filter{margin-bottom:10px}.collab-member-row-head,.collab-conflict-head{display:flex;justify-content:space-between;gap:10px;align-items:flex-start}.collab-device-list{margin-top:8px;border-top:1px solid var(--line);padding-top:7px}.collab-device-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:.75rem}.collab-device-row span:first-child{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.collab-device-row .inline-actions{margin-left:auto}.collab-conflict-row{border-color:#f3d39d;background:#fffaf0}.collab-conflict-row .conflict-meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:7px}.collab-conflict-row .conflict-meta span{font-size:.69rem;background:#fff;border:1px solid #f3d39d;border-radius:999px;padding:3px 6px;color:#7a2e0e}.section-head.compact{margin-top:18px;padding-bottom:8px}.section-head.compact h3{margin:0}.section-head.compact p{font-size:.76rem}.collab-detail-column>.empty{margin:0}.collab-member-row .inline-actions button,.collab-conflict-row button,.collab-detail-head .inline-actions button{padding:5px 8px;font-size:.72rem}
|
|
98367
99775
|
@media(max-width:1100px){.metric-grid{grid-template-columns:repeat(3,1fr)}.config-grid{grid-template-columns:repeat(2,minmax(220px,1fr))}.apps-layout,.collab-admin-layout,.process-layout,.evolution-grid{grid-template-columns:1fr}.process-detail{position:static}.process-toolbar{grid-template-columns:repeat(2,minmax(0,1fr))}.app-builder{position:static}.focus-user-summary{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
|
98368
|
-
@media(max-width:720px){.admin-shell{padding:14px}.admin-header,.section-head,.collab-detail-head,.collab-service-bar{flex-direction:column;align-items:stretch}.header-actions{justify-content:flex-start}.metric-grid{grid-template-columns:repeat(2,1fr)}.two-column,.chart-grid{grid-template-columns:1fr}.config-grid,.process-toolbar,.evolution-form-grid{grid-template-columns:1fr}.evolution-span{grid-column:auto}.admin-nav{overflow:auto}.action-bar button{flex:1 1 145px}.user-tracking-head{align-items:stretch}.user-filter{align-items:stretch;flex-direction:column}.focus-user-summary{grid-template-columns:repeat(2,minmax(0,1fr))}.chart-host{min-height:215px}.collab-filter-row,.collab-member-filter{grid-template-columns:1fr}.collab-service-actions{justify-content:flex-start}.collab-service-actions>*{flex:1 1 180px;text-align:center}}
|
|
99776
|
+
@media(max-width:720px){.admin-shell{padding:14px}.admin-header,.section-head,.collab-detail-head,.collab-service-bar{flex-direction:column;align-items:stretch}.header-actions{justify-content:flex-start}.metric-grid{grid-template-columns:repeat(2,1fr)}.two-column,.chart-grid{grid-template-columns:1fr}.config-grid,.process-toolbar,.evolution-form-grid{grid-template-columns:1fr}.evolution-span{grid-column:auto}.admin-nav{overflow:auto}.admin-nav .nav-tab{flex:0 0 auto;white-space:nowrap}.action-bar button{flex:1 1 145px}.user-tracking-head{align-items:stretch}.user-filter{align-items:stretch;flex-direction:column}.focus-user-summary{grid-template-columns:repeat(2,minmax(0,1fr))}.chart-host{min-height:215px}.collab-filter-row,.collab-member-filter{grid-template-columns:1fr}.collab-service-actions{justify-content:flex-start}.collab-service-actions>*{flex:1 1 180px;text-align:center}}
|
|
98369
99777
|
"""
|
|
98370
99778
|
|
|
98371
99779
|
ADMIN_JS = r"""
|
|
@@ -98431,6 +99839,20 @@ const ADMIN_I18N_ROWS=[
|
|
|
98431
99839
|
['导出 JSON','导出 JSON','匯出 JSON','JSON をエクスポート','Export JSON'],
|
|
98432
99840
|
['Liquid Kernel Evolution','液态内核演进','液態核心演進','Liquid Kernel 進化','Liquid Kernel Evolution'],
|
|
98433
99841
|
['版本化 Agent 内核、自主评估、随机盲评、Canary 与可回滚升级链路。','版本化 Agent 内核、自主评估、随机盲评、Canary 与可回滚升级链路。','版本化 Agent 核心、自主評估、隨機盲評、Canary 與可回復升級鏈路。','バージョン管理された Agent カーネル、自律評価、ランダム盲検、Canary、ロールバック可能な更新フロー。','Versioned agent kernel with autonomous evaluation, randomized blind judging, canary rollout, and rollback.'],
|
|
99842
|
+
['Use global default','沿用全局默认','沿用全域預設','グローバル既定を使用','Use global default'],
|
|
99843
|
+
['Global models','全局模型','全域模型','グローバルモデル','Global models'],
|
|
99844
|
+
['Agent chat models','Agent 聊天模型','Agent 聊天模型','Agent チャットモデル','Agent chat models'],
|
|
99845
|
+
['IDE account models','IDE 账号模型','IDE 帳號模型','IDE アカウントモデル','IDE account models'],
|
|
99846
|
+
['available models','个可选模型','個可選模型','利用可能なモデル','available models'],
|
|
99847
|
+
['Sign in to IDE to use this source.','未登录 IDE,此来源暂不可用','未登入 IDE,此來源暫不可用','IDE にログインすると利用できます','Sign in to IDE to use this source.'],
|
|
99848
|
+
['private model configuration no longer exists','未找到该来源的私有模型配置','未找到此來源的私有模型設定','このソースの個人モデル設定がありません','private model configuration no longer exists'],
|
|
99849
|
+
['Saved generator','已保存的生成模型','已儲存的生成模型','保存済み生成モデル','Saved generator'],
|
|
99850
|
+
['Saved judge','已保存的评审模型','已儲存的評審模型','保存済み評価モデル','Saved judge'],
|
|
99851
|
+
['Model directory unavailable','模型目录暂不可用','模型目錄暫不可用','モデル一覧を利用できません','Model directory unavailable'],
|
|
99852
|
+
['Loading models','正在读取模型','正在讀取模型','モデルを読込中','Loading models'],
|
|
99853
|
+
['selected model configuration is disabled','模型配置已停用','模型設定已停用','モデル設定は無効です','selected model configuration is disabled'],
|
|
99854
|
+
['selected model has no service address','模型未配置服务地址','模型未設定服務位址','モデルのサービス URL がありません','selected model has no service address'],
|
|
99855
|
+
['No model configured.','尚未配置具体模型','尚未設定具體模型','モデルが未設定です','No model configured.'],
|
|
98434
99856
|
['Evolution Policy','演进策略','演進策略','進化ポリシー','Evolution Policy'],
|
|
98435
99857
|
['Schedule','计划周期','排程週期','スケジュール','Schedule'],
|
|
98436
99858
|
['Timezone','时区','時區','タイムゾーン','Timezone'],
|
|
@@ -98615,7 +100037,7 @@ const ADMIN_I18N_ALL=[...ADMIN_I18N_ROWS,...ADMIN_CONFIG_I18N_ROWS,...ADMIN_DYNA
|
|
|
98615
100037
|
const ADMIN_I18N_EXACT=new Map(ADMIN_I18N_ALL.map(row=>[row[0],row]));
|
|
98616
100038
|
const ADMIN_I18N_FRAGMENTS=[...ADMIN_I18N_ALL].filter(row=>String(row[0]||'').length>1).sort((a,b)=>b[0].length-a[0].length);
|
|
98617
100039
|
const ADMIN_TRADITIONAL_CHARS={'后':'後','发':'發','复':'復','务':'務','进':'進','启':'啟','关':'關','闭':'閉','应':'應','用':'用','网':'網','项':'項','选':'選','择':'擇','读':'讀','写':'寫','数':'數','据':'據','认':'認','证':'證','录':'錄','审':'審','计':'計','时':'時','间':'間','过':'過','滤':'濾','错':'錯','误':'誤','输':'輸','访':'訪','问':'問','标':'標','识':'識','获':'獲','显':'顯','详':'詳','页':'頁','签':'簽','态':'態','动':'動','实':'實','现':'現','历':'歷','辖':'轄','围':'圍','并':'並','与':'與','开':'開','创':'創','删':'刪','库':'庫','设':'設','置':'置','户':'戶','类':'類','总':'總','长':'長','径':'徑','轮':'輪','调':'調','整':'整','级':'級','备':'備','份':'份','从':'從','将':'將','为':'為','这':'這','个':'個','没':'沒','无':'無','对':'對','话':'話','体':'體','内':'內','链':'鏈','滚':'滾','线':'線','异':'異','步':'步','结':'結','构':'構','继':'繼','续':'續','档':'檔','归':'歸','离':'離','冻':'凍','检':'檢','测':'測','满':'滿','压':'壓','缩':'縮','较':'較','评':'評','毁':'毀','拥':'擁','护':'護','载':'載','节':'節','点':'點','弹':'彈','窗':'窗','语':'語','简':'簡','统':'統','协':'協','区':'區','称':'稱','码':'碼','险':'險','丢':'丟','仅':'僅','侧':'側','准':'準','击':'擊','叠':'疊','号':'號','听':'聽','员':'員','处':'處','尔':'爾','弃':'棄','当':'當','报':'報','换':'換','断':'斷','暂':'暫','机':'機','权':'權','来':'來','紧':'緊','约':'約','细':'細','终':'終','绑':'綁','绝':'絕','状':'狀','监':'監','盖':'蓋','确':'確','范':'範','许':'許','试':'試','说':'說','请':'請','账':'帳','跃':'躍','运':'運','远':'遠','采':'採','钟':'鐘','销':'銷','队':'隊','际':'際','验':'驗','黄':'黃','联':'聯','筛':'篩'};
|
|
98618
|
-
const A={token:sessionStorage.getItem('clouds_coder_admin_token')||'',config:null,metrics:null,metricUserHash:'',metricResizeTimer:0,apps:[],skills:[],reviewStatus:'pending',selectedSkills:[],toastTimer:0,serverErrors:[],bootId:'',restartNonce:'',collabProjects:[],collabProject:null,collabMembers:[],collabConflicts:[],collabAudit:[],processPayload:null,selectedProcesses:[],processDetailId:'',evolution:null,evolutionTimer:0,language:'zh-CN',languageSource:'webui_user_preference',languageObserver:null,languageSyncBusy:false};
|
|
100040
|
+
const A={token:sessionStorage.getItem('clouds_coder_admin_token')||'',config:null,metrics:null,metricUserHash:'',metricResizeTimer:0,apps:[],skills:[],reviewStatus:'pending',selectedSkills:[],toastTimer:0,serverErrors:[],bootId:'',restartNonce:'',collabProjects:[],collabProject:null,collabMembers:[],collabConflicts:[],collabAudit:[],processPayload:null,selectedProcesses:[],processDetailId:'',evolution:null,evolutionTimer:0,evolutionDraft:null,evolutionDirty:false,evolutionBusy:false,evolutionModels:null,evolutionError:'',evolutionLoading:null,language:'zh-CN',languageSource:'webui_user_preference',languageObserver:null,languageSyncBusy:false};
|
|
98619
100041
|
const E=id=>document.getElementById(id);
|
|
98620
100042
|
function normalizeAdminLanguage(value){const raw=String(value||'').trim();if(ADMIN_LANGUAGE_CODES.includes(raw))return raw;const low=raw.toLowerCase();if(['zh','zh-cn','cn'].includes(low))return'zh-CN';if(['zh-tw','zh-hk','tc'].includes(low))return'zh-TW';if(['ja-jp','jp'].includes(low))return'ja';if(['en-us','en-gb'].includes(low))return'en';return'zh-CN'}
|
|
98621
100043
|
function adminTraditional(value){return Array.from(String(value||''),ch=>ADMIN_TRADITIONAL_CHARS[ch]||ch).join('')}
|
|
@@ -98627,7 +100049,7 @@ function translateAdminAttributes(element,capture=false){if(!element||element.no
|
|
|
98627
100049
|
function applyAdminI18n(root=document){const target=root?.nodeType?root:document;if(target.nodeType===3){translateAdminTextNode(target);return}if(target.nodeType===1)translateAdminAttributes(target);const walker=document.createTreeWalker(target,NodeFilter.SHOW_TEXT|NodeFilter.SHOW_ELEMENT);let current;while((current=walker.nextNode())){if(current.nodeType===3)translateAdminTextNode(current);else translateAdminAttributes(current)}document.documentElement.lang=A.language;document.title=adminTranslate('Clouds Coder Admin');renderAdminLanguageControl()}
|
|
98628
100050
|
function observeAdminI18n(){if(A.languageObserver)A.languageObserver.disconnect();A.languageObserver=new MutationObserver(records=>{for(const record of records){if(record.type==='characterData'){if(ADMIN_INTERNAL_TEXT.has(record.target)){ADMIN_INTERNAL_TEXT.delete(record.target);continue}translateAdminTextNode(record.target,true);continue}for(const added of record.addedNodes||[])applyAdminI18n(added)}});A.languageObserver.observe(document.body,{subtree:true,childList:true,characterData:true})}
|
|
98629
100051
|
function renderAdminLanguageControl(){const selects=[E('adminLanguageSelect'),E('adminLoginLanguageSelect')].filter(Boolean),labels=[E('adminLanguageLabel'),E('adminLoginLanguageLabel')].filter(Boolean),source=E('adminLanguageSource'),labelText={'zh-CN':'界面语言','zh-TW':'介面語言','ja':'表示言語','en':'Language'}[A.language],sourceText={'zh-CN':'与 WebUI 用户语言同步','zh-TW':'與 WebUI 使用者語言同步','ja':'WebUI のユーザー言語と同期','en':'Synced with WebUI language'}[A.language];for(const select of selects){if(select.value!==A.language)select.value=A.language;select.setAttribute('aria-label',labelText)}for(const label of labels)label.textContent=labelText;if(source)source.textContent=sourceText}
|
|
98630
|
-
function applyAdminLanguage(language){A.language=normalizeAdminLanguage(language);try{localStorage.setItem('clouds_coder_admin_language',A.language)}catch(_){}applyAdminI18n(document)}
|
|
100052
|
+
function applyAdminLanguage(language){A.language=normalizeAdminLanguage(language);try{localStorage.setItem('clouds_coder_admin_language',A.language)}catch(_){}applyAdminI18n(document);if(A.evolutionModels&&A.evolutionDraft){A.evolutionDraft=collectEvolutionConfig();renderEvolutionModels()}}
|
|
98631
100053
|
async function loadAdminLanguage(quiet=false){if(A.languageSyncBusy)return;A.languageSyncBusy=true;try{const out=await request('/api/admin/language');A.languageSource=String(out.source||'webui_user_preference');applyAdminLanguage(out.language)}catch(err){if(!quiet)console.warn('admin language sync failed',err)}finally{A.languageSyncBusy=false}}
|
|
98632
100054
|
async function saveAdminLanguage(language){const previous=A.language;applyAdminLanguage(language);try{const out=await request('/api/admin/language',{method:'POST',body:JSON.stringify({language:A.language})});A.languageSource=String(out.source||'webui_user_preference');applyAdminLanguage(out.language)}catch(err){applyAdminLanguage(previous);toast(err.message,true)}}
|
|
98633
100055
|
const ADMIN_NATIVE_CONFIRM=window.confirm.bind(window),ADMIN_NATIVE_PROMPT=window.prompt.bind(window);window.confirm=message=>ADMIN_NATIVE_CONFIRM(adminTranslate(message));window.prompt=(message,defaultValue)=>ADMIN_NATIVE_PROMPT(adminTranslate(message),defaultValue);
|
|
@@ -98636,7 +100058,7 @@ function toast(message,error=false){const el=E('toast');el.textContent=adminTran
|
|
|
98636
100058
|
function setAuthenticated(ok){E('loginOverlay').classList.toggle('hidden',!!ok);E('connectionBadge').textContent=ok?'已认证':'未认证';E('connectionBadge').className='badge '+(ok?'good':'muted')}
|
|
98637
100059
|
async function request(path,opt={}){const headers={...(opt.headers||{})};if(opt.body!==undefined&&!headers['Content-Type'])headers['Content-Type']='application/json';const r=await fetch(path,{...opt,headers});const raw=await r.text();let body={};try{body=raw?JSON.parse(raw):{}}catch(_){body={error:raw||'请求失败'}}if(!r.ok){const details=Array.isArray(body.errors)?body.errors:[];const msg=[body.error||'请求失败',...details.map(x=>String(x.key||'参数')+': '+String(x.error||'无效'))].filter(Boolean).join('\n');const err=new Error(msg);err.status=r.status;err.code=String(body.code||'');err.details=details;err.body=body;throw err}return body}
|
|
98638
100060
|
async function api(path,opt={}){const headers={...(opt.headers||{})};if(A.token)headers.Authorization='Bearer '+A.token;try{return await request(path,{...opt,headers})}catch(err){if(err.status===401){clearSession();setAuthenticated(false);E('loginError').textContent='登录会话已过期,请重新登录';loadAuthStatus().catch(()=>{});}throw err}}
|
|
98639
|
-
function clearSession(){sessionStorage.removeItem('clouds_coder_admin_token');A.token='';A.config=null;A.metrics=null;A.apps=[];A.skills=[];A.collabProjects=[];A.collabProject=null;A.collabMembers=[];A.collabConflicts=[];A.collabAudit=[];A.processPayload=null;A.selectedProcesses=[];A.processDetailId=''}
|
|
100061
|
+
function clearSession(){A.evolutionDraft=null;A.evolutionModels=null;A.evolutionDirty=false;sessionStorage.removeItem('clouds_coder_admin_token');A.token='';A.config=null;A.metrics=null;A.apps=[];A.skills=[];A.collabProjects=[];A.collabProject=null;A.collabMembers=[];A.collabConflicts=[];A.collabAudit=[];A.processPayload=null;A.selectedProcesses=[];A.processDetailId=''}
|
|
98640
100062
|
function setAuthBusy(form,busy){const btn=form?.querySelector('button[type="submit"]');if(btn)btn.disabled=!!busy}
|
|
98641
100063
|
function authError(err){const retry=Number(err?.body?.retry_after||0);E('loginError').textContent=retry?String(err.message)+'(约 '+retry+' 秒后重试)':String(err?.message||err||'认证失败')}
|
|
98642
100064
|
function renderAuthState(status){const setup=!!status?.setup_required;E('retryAuthBtn').classList.add('hidden');E('setupForm').classList.toggle('hidden',!setup);E('passwordLoginForm').classList.toggle('hidden',setup);E('tokenLoginDetails').classList.toggle('hidden',!status?.token_login_enabled);E('tokenLoginBtn').textContent=setup?'用此 Token 创建管理员':'使用 Token 进入';E('tokenLoginHelp').textContent=setup?'远程首次初始化:输入现有 Admin Token,然后在上方填写账号密码并点击“创建管理员”。本机首次运行无需填写 Token。':'验证后只保存换取的短期会话,不保存原始 Admin Token。';E('authTitle').textContent=setup?'创建管理员账号':'管理员登录';if(setup){E('authDescription').textContent=status?.local_setup_allowed?'这是首次运行。请创建唯一管理员账号,密码只以强哈希形式保存在本机。':'首次创建仅允许在本机完成;远程初始化请展开高级入口并使用 Admin Token。';setTimeout(()=>E('setupUsername').focus(),0)}else{E('authDescription').textContent='使用管理员账号和密码登录。登录成功后本标签页只保存短期会话。';setTimeout(()=>E('loginUsername').focus(),0)}}
|
|
@@ -98647,7 +100069,7 @@ async function loginWithPassword(){const form=E('passwordLoginForm');setAuthBusy
|
|
|
98647
100069
|
async function loginWithToken(){if(!E('setupForm').classList.contains('hidden')){await registerAdmin();return}const form=E('tokenLoginForm'),candidate=E('tokenInput').value.trim();if(!candidate){E('loginError').textContent='请输入 Admin Token';return}setAuthBusy(form,true);E('loginError').textContent='';try{const out=await request('/api/admin/auth/token-login',{method:'POST',headers:{Authorization:'Bearer '+candidate},body:'{}'});await acceptSession(out.access_token)}catch(err){authError(err);E('tokenInput').value='';E('tokenInput').focus()}finally{setAuthBusy(form,false)}}
|
|
98648
100070
|
async function logoutAdmin(){const token=A.token;try{if(token)await request('/api/admin/auth/logout',{method:'POST',headers:{Authorization:'Bearer '+token},body:'{}'})}catch(_){}finally{clearSession();setAuthenticated(false);await loadAuthStatus().catch(err=>authError(err))}}
|
|
98649
100071
|
async function bootstrapAuth(){setAuthenticated(false);if(A.token){try{const state=await request('/api/admin/auth/session',{headers:{Authorization:'Bearer '+A.token}});if(state.auth_kind==='token'){const out=await request('/api/admin/auth/token-login',{method:'POST',headers:{Authorization:'Bearer '+A.token},body:'{}'});await acceptSession(out.access_token)}else await acceptSession(A.token);return}catch(err){if(err.status===401)clearSession();else{E('authTitle').textContent='认证服务不可用';E('authDescription').textContent='暂时无法验证已保存的会话,请重试。';authError(err);E('retryAuthBtn').classList.remove('hidden');return}}}await loadAuthStatus()}
|
|
98650
|
-
function node(tag,attrs={},text=''){const el=document.createElement(tag);for(const [k,v] of Object.entries(attrs||{})){if(k==='class')el.className=v;else if(k==='dataset')Object.assign(el.dataset,v);else if(k==='type')el.type=v;else el.setAttribute(k,String(v))}if(text!==undefined&&text!==null)el.textContent=String(text);return el}
|
|
100072
|
+
function node(tag,attrs={},text=''){const el=document.createElement(tag);for(const [k,v] of Object.entries(attrs||{})){if(k==='class')el.className=v;else if(k==='dataset')Object.assign(el.dataset,v);else if(k==='type')el.type=v;else if(typeof v==='boolean'&&typeof el[k]==='boolean')el[k]=v;else el.setAttribute(k,String(v))}if(text!==undefined&&text!==null)el.textContent=String(text);return el}
|
|
98651
100073
|
function switchView(name){document.querySelectorAll('.nav-tab').forEach(x=>x.classList.toggle('active',x.dataset.view===name));document.querySelectorAll('.admin-view').forEach(x=>x.classList.toggle('active',x.id===name+'View'));if(name==='metrics')loadMetrics().catch(err=>toast(err.message,true));if(name==='processes')loadProcesses().catch(err=>toast(err.message,true));if(name==='config'&&!A.config)loadConfig().catch(err=>toast(err.message,true));if(name==='evolution')loadEvolution().catch(err=>toast(err.message,true));if(name==='apps')loadApps().catch(err=>toast(err.message,true));if(name==='collaboration')loadCollaboration().catch(err=>toast(err.message,true))}
|
|
98652
100074
|
function table(headers,rows){if(!rows.length)return node('div',{class:'empty'},'暂无数据');const t=node('table');const thead=node('thead'),tr=node('tr');headers.forEach(h=>tr.appendChild(node('th',{},h[0])));thead.appendChild(tr);t.appendChild(thead);const tb=node('tbody');rows.forEach(row=>{const r=node('tr');headers.forEach(h=>r.appendChild(node('td',{},h[1](row))));tb.appendChild(r)});t.appendChild(tb);return t}
|
|
98653
100075
|
const SVG_NS='http://www.w3.org/2000/svg',METRIC_COLORS=['series-1','series-2','series-3','series-4','series-danger','series-muted'];
|
|
@@ -98714,23 +100136,47 @@ async function stopProcessIds(ids,label){const unique=[...new Set((ids||[]).filt
|
|
|
98714
100136
|
async function stopSelectedProcesses(){await stopProcessIds(A.selectedProcesses,'所选')}
|
|
98715
100137
|
async function stopFilteredProcesses(){const rows=Array.isArray(A.processPayload?.processes)?A.processPayload.processes:[];await stopProcessIds(rows.filter(row=>row.can_stop).map(row=>row.id),'当前筛选结果中')}
|
|
98716
100138
|
function bindProcesses(){E('refreshProcessesBtn').onclick=()=>loadProcesses().catch(err=>toast(err.message,true));E('filterProcessesBtn').onclick=()=>loadProcesses().catch(err=>toast(err.message,true));E('processSearch').onkeydown=ev=>{if(ev.key==='Enter')loadProcesses().catch(err=>toast(err.message,true))};E('processStatus').onchange=()=>loadProcesses().catch(err=>toast(err.message,true));E('selectAllProcesses').onchange=()=>{const rows=Array.isArray(A.processPayload?.processes)?A.processPayload.processes:[];A.selectedProcesses=E('selectAllProcesses').checked?rows.filter(row=>row.can_stop).map(row=>String(row.id)):[];renderProcesses()};E('stopSelectedProcessesBtn').onclick=()=>stopSelectedProcesses().catch(err=>toast(err.message,true));E('stopFilteredProcessesBtn').onclick=()=>stopFilteredProcesses().catch(err=>toast(err.message,true))}
|
|
98717
|
-
const EVOLUTION_PIPELINE=['queued','collecting','assessing','proposal_ready','validating_patch','benchmarking','awaiting_approval','canary','promoted'];
|
|
100139
|
+
const EVOLUTION_PIPELINE=['queued','collecting','assessing','generating','proposal_ready','validating_patch','benchmarking','judging','awaiting_approval','canary','promoted'];
|
|
98718
100140
|
function evolutionTime(value){const ts=Number(value||0);return ts?new Date(ts*1000).toLocaleString(A.language):'-'}
|
|
98719
100141
|
function evolutionScopes(value){return String(value||'').split(',').map(x=>x.trim()).filter(Boolean).slice(0,500)}
|
|
98720
|
-
function renderEvolution(){const data=A.evolution||{},cfg=data.config||{},active=data.active||{},bootstrap=data.bootstrap||{},runs=Array.isArray(data.runs?.runs)?data.runs.runs:[],versions=Array.isArray(data.versions?.versions)?data.versions.versions:[];
|
|
100142
|
+
function renderEvolution(){const data=A.evolution||{},cfg=data.config||{},active=data.active||{},bootstrap=data.bootstrap||{},runs=Array.isArray(data.runs?.runs)?data.runs.runs:[],versions=Array.isArray(data.versions?.versions)?data.versions.versions:[];evolutionControls();E('evolutionActiveBadge').textContent=cfg.mode||'Off';E('evolutionActiveBadge').className='badge '+(cfg.mode==='Off'?'muted':cfg.mode==='Aggressive'?'bad':'good');const canary=active.canary||null;E('evolutionCanaryBadge').textContent=canary?`canary ${Number(canary.percent||0)}%`:'stable';E('evolutionCanaryBadge').className='badge '+(canary?'warn':'good');E('evolutionBootstrap').textContent=`startup policy=${data.startup_policy||'inherit'} · request=${bootstrap.requested_policy||'inherit'} · ${bootstrap.history_action||'history status unavailable'}${bootstrap.injected_version?' · injected='+bootstrap.injected_version:''}`;E('evolutionCurrent').textContent=`active=${active.version||'-'}\ncanary=${canary?canary.version+' @ '+canary.percent+'%':'none'}\nmutable_surface=${cfg.mutable_surface||'none'}\nweights=hard 30% / LLM blind judge 70%\nminimum_gain=${Number(cfg.minimum_gain||0).toFixed(1)}\nhistory=current + ${Number(cfg.history_version_depth||0)} previous versions\nimmutable=${(data.immutable_control_components||[]).join(', ')}`;
|
|
98721
100143
|
const currentRun=runs.find(row=>!['no_change','rejected','failed','cancelled','promoted'].includes(String(row.status||'')))||runs[0]||{};const pipeline=E('evolutionPipeline');pipeline.innerHTML='';const currentIndex=EVOLUTION_PIPELINE.indexOf(String(currentRun.status||''));for(const [index,status] of EVOLUTION_PIPELINE.entries()){pipeline.appendChild(node('span',{class:'evolution-step '+(index<currentIndex?'done':index===currentIndex?'active':'')},status))}
|
|
98722
100144
|
const lineage=E('evolutionLineage');lineage.innerHTML='';for(const row of [...versions].reverse()){const card=node('button',{type:'button',class:'evolution-node '+String(row.status||'')});card.append(node('strong',{},row.version||'-'),node('small',{},'parent '+(row.parent_version||'baseline')),node('small',{},String(row.status||'')+' · score '+Number(row.mixed_score||0).toFixed(1)),node('small',{},evolutionTime(row.promoted_at||row.created_at)));card.onclick=()=>showEvolutionVersion(row.version);lineage.appendChild(card)}if(!versions.length)lineage.appendChild(node('div',{class:'empty'},'No kernel versions'));
|
|
98723
100145
|
const runHost=E('evolutionRuns');runHost.innerHTML='';if(!runs.length)runHost.appendChild(node('div',{class:'empty'},'No evolution runs'));else{const table=node('table'),head=node('tr');for(const label of ['Run','Mode','Status','Candidate','Created','Actions'])head.appendChild(node('th',{},label));table.appendChild(head);for(const row of runs){const tr=node('tr'),actions=node('td');const view=node('button',{type:'button',class:'ghost'},'View');view.onclick=()=>showEvolutionRun(row.run_id);actions.appendChild(view);if(row.status==='awaiting_approval'){const approve=node('button',{type:'button'},'Approve'),reject=node('button',{type:'button',class:'danger'},'Reject');approve.onclick=()=>evolutionRunAction(row.run_id,'approve');reject.onclick=()=>evolutionRunAction(row.run_id,'reject');actions.append(approve,reject)}else if(!['no_change','rejected','failed','cancelled','promoted','rolled_back'].includes(row.status)){const cancel=node('button',{type:'button',class:'danger'},'Cancel');cancel.onclick=()=>evolutionRunAction(row.run_id,'cancel');actions.appendChild(cancel)}for(const value of [row.run_id,row.mode,row.status,row.candidate_version||'-',evolutionTime(row.created_at)])tr.appendChild(node('td',{},value));tr.appendChild(actions);table.appendChild(tr)}runHost.appendChild(table)}
|
|
98724
100146
|
const versionHost=E('evolutionVersions');versionHost.innerHTML='';if(!versions.length)versionHost.appendChild(node('div',{class:'empty'},'No versions'));else{const table=node('table'),head=node('tr');for(const label of ['Version','Status','Hard','LLM','Mixed','Actions'])head.appendChild(node('th',{},label));table.appendChild(head);for(const row of versions){const tr=node('tr'),actions=node('td'),view=node('button',{type:'button',class:'ghost'},'Diff');view.onclick=()=>showEvolutionVersion(row.version,true);actions.appendChild(view);if(row.status==='canary'&&String(active.canary?.version||'')===String(row.version||'')&&Number(active.canary?.percent||0)>=100){const promote=node('button',{type:'button'},'Promote');promote.onclick=()=>evolutionVersionAction(row.version,'promote');actions.appendChild(promote)}if(row.parent_version){const rollback=node('button',{type:'button',class:'danger'},'Rollback');rollback.onclick=()=>evolutionVersionAction(row.parent_version,'rollback');actions.appendChild(rollback)}for(const value of [row.version,row.status,Number(row.hard_score||0).toFixed(1),Number(row.soft_score||0).toFixed(1),Number(row.mixed_score||0).toFixed(1)])tr.appendChild(node('td',{},value));tr.appendChild(actions);table.appendChild(tr)}versionHost.appendChild(table)}
|
|
98725
100147
|
}
|
|
98726
|
-
|
|
98727
|
-
function
|
|
98728
|
-
|
|
98729
|
-
|
|
100148
|
+
const EVOLUTION_FIELDS=['Mode','Schedule','Timezone','HistoryDepth','StartDate','EndDate','GeneratorProfile','JudgeProfile','UserScope','SessionScope','EventTriggers'];
|
|
100149
|
+
function evolutionRefKey(ref){return ref?JSON.stringify([ref.source,ref.owner,ref.profile_id,ref.model,ref.fingerprint||'']):''}
|
|
100150
|
+
function renderEvolutionModels(){const catalog=A.evolutionModels||{},cfg=A.evolutionDraft||{};
|
|
100151
|
+
for(const role of ['generator','judge']){const el=E(role==='generator'?'evolutionGeneratorProfile':'evolutionJudgeProfile'),ref=cfg[role+'_model_ref'],legacy=cfg[role+'_profile']||'';el.innerHTML='';el.appendChild(node('option',{value:''},adminTranslate('Use global default')));
|
|
100152
|
+
let selected='';for(const [index,row] of (catalog.options||[]).entries()){const value=String(index);el.appendChild(node('option',{value,disabled:!row.available},row.label+(row.reason?' · '+adminTranslate(row.reason):'')));if(ref&&(row.aliases||[row.ref]).some(alias=>evolutionRefKey(alias)===evolutionRefKey(ref)))selected=value}
|
|
100153
|
+
if(ref&&selected===''){selected='saved';el.appendChild(node('option',{value:selected},`[${ref.source}] ${ref.model} · ${ref.profile_id} (已保存)`))}
|
|
100154
|
+
else if(!ref&&legacy){selected='legacy';el.appendChild(node('option',{value:selected},'旧配置 · '+legacy))}
|
|
100155
|
+
el.value=selected;el.title=el.selectedOptions[0]?.textContent||'';
|
|
100156
|
+
}
|
|
100157
|
+
const sourceNames={global:'Global models',agent:'Agent chat models',ide:'IDE account models'},sourceHost=E('evolutionModelSources');sourceHost.replaceChildren();
|
|
100158
|
+
for(const row of catalog.sources||[]){const count=(catalog.options||[]).filter(option=>option.available&&(option.sources||[]).includes(row.source)).length;const label=adminTranslate(sourceNames[row.source]||row.source)+' · '+(row.available?count+' '+adminTranslate('available models'):adminTranslate(row.reason));sourceHost.appendChild(node('span',{class:'badge '+(row.available&&count?'good':'muted'),'data-admin-no-i18n':''},label))}
|
|
100159
|
+
E('evolutionModelResolved').textContent=['generator','judge'].map(role=>{const row=catalog.resolved?.[role];return adminTranslate(role==='generator'?'Saved generator':'Saved judge')+': '+(row?.available?`[${row.ref.source}] ${row.ref.model} · ${row.ref.profile_id}`:adminTranslate(row?.reason||'Loading models'))}).join('\n');
|
|
100160
|
+
}
|
|
100161
|
+
function fillEvolutionForm(cfg){A.evolutionDraft=structuredClone(cfg);A.evolutionDirty=false;A.evolutionError='';E('evolutionMode').value=cfg.mode||'Off';E('evolutionSchedule').value=cfg.schedule||'off';E('evolutionTimezone').value=cfg.timezone||'Asia/Shanghai';E('evolutionHistoryDepth').value=Number(cfg.history_version_depth??2);E('evolutionStartDate').value=cfg.history_start_date||'';E('evolutionEndDate').value=cfg.history_end_date||'';E('evolutionUserScope').value=(cfg.user_scope||['*']).join(', ');E('evolutionSessionScope').value=(cfg.session_scope||['*']).join(', ');E('evolutionEventTriggers').checked=!!cfg.event_triggers;renderEvolutionModels()}
|
|
100162
|
+
function evolutionControls(){const cfg=A.evolutionDraft||{},server=A.evolution?.config||{},conflict=!!cfg.revision&&cfg.revision!==server.revision;
|
|
100163
|
+
E('evolutionRevision').textContent='rev '+(cfg.revision||'-')+(A.evolutionDirty?' · 未保存':'')+(conflict?' · 服务端已更新':'');
|
|
100164
|
+
E('runEvolutionBtn').disabled=!cfg.revision||A.evolutionDirty||A.evolutionBusy||conflict||server.mode==='Off'||!!A.evolution?.active_run_id;
|
|
100165
|
+
E('saveEvolutionBtn').disabled=!cfg.revision||!!A.evolutionBusy||conflict;
|
|
100166
|
+
for(const field of EVOLUTION_FIELDS)E('evolution'+field).disabled=!!A.evolutionBusy;
|
|
100167
|
+
const note=A.evolutionError||(conflict?'配置已被其他页面修改。点击“重新载入配置”后再保存。':A.evolutionDirty?'有未保存的更改,请先保存策略,再启动。':A.evolution?.last_trigger_error?.error|| (server.mode==='Off'?'当前为 Off,请选择运行模式并保存。':'策略已保存,可启动。'));
|
|
100168
|
+
E('evolutionPolicyNote').textContent=note;E('evolutionPolicyNote').classList.remove('hidden');
|
|
100169
|
+
}
|
|
100170
|
+
async function loadEvolutionModels(){const catalog=await api('/api/admin/evolution/models'),draft=A.evolutionDraft?collectEvolutionConfig():null;A.evolutionModels=catalog;if(draft)A.evolutionDraft=draft;renderEvolutionModels()}
|
|
100171
|
+
async function loadEvolution(){clearTimeout(A.evolutionTimer);if(A.evolutionLoading)return A.evolutionLoading;A.evolutionLoading=(async()=>{try{const data=await api('/api/admin/evolution');if(data.config.revision<(A.evolution?.config?.revision||0))return A.evolution;A.evolution=data;if(!A.evolutionDraft||(!A.evolutionDirty&&!A.evolutionBusy&&A.evolutionDraft.revision!==data.config.revision))fillEvolutionForm(data.config);renderEvolution();if(!A.evolutionModels)await loadEvolutionModels();return data}finally{A.evolutionLoading=null;if(E('evolutionView').classList.contains('active')){const live=!!A.evolution?.active_run_id||!!A.evolution?.active?.canary;A.evolutionTimer=setTimeout(()=>loadEvolution().catch(err=>toast(err.message,true)),live?2500:15000)}}})();return A.evolutionLoading}
|
|
100172
|
+
function collectEvolutionConfig(){const current=A.evolutionDraft||{},out={...current,mode:E('evolutionMode').value,schedule:E('evolutionSchedule').value,timezone:E('evolutionTimezone').value.trim(),history_version_depth:Number(E('evolutionHistoryDepth').value),history_start_date:E('evolutionStartDate').value,history_end_date:E('evolutionEndDate').value,user_scope:evolutionScopes(E('evolutionUserScope').value),session_scope:evolutionScopes(E('evolutionSessionScope').value),event_triggers:E('evolutionEventTriggers').checked};for(const role of ['generator','judge']){const value=E(role==='generator'?'evolutionGeneratorProfile':'evolutionJudgeProfile').value;if(value==='saved'||value==='legacy')continue;out[role+'_model_ref']=value===''?null:A.evolutionModels.options[Number(value)].ref;out[role+'_profile']=''}return out}
|
|
100173
|
+
async function saveEvolution(){if(A.evolutionBusy)return;const cfg=collectEvolutionConfig();A.evolutionBusy=true;A.evolutionError='';evolutionControls();try{const out=await api('/api/admin/evolution/config',{method:'POST',body:JSON.stringify({revision:cfg.revision,values:cfg})});A.evolution.config=out.config;fillEvolutionForm(out.config);toast('Evolution policy saved');await loadEvolutionModels()}catch(err){A.evolutionError=err.message;if(err.status===409)await loadEvolution();throw err}finally{A.evolutionBusy=false;evolutionControls()}}
|
|
100174
|
+
async function runEvolution(){if(A.evolutionBusy||E('runEvolutionBtn').disabled)return;A.evolutionBusy=true;A.evolutionError='';evolutionControls();try{const out=await api('/api/admin/evolution/runs',{method:'POST',body:JSON.stringify({revision:A.evolutionDraft.revision})});toast('任务已启动: '+out.run_id+' · '+(out.models?.generator?.model||'')+' / '+(out.models?.judge?.model||''));await loadEvolution()}catch(err){A.evolutionError=err.message;await loadEvolution();throw err}finally{A.evolutionBusy=false;evolutionControls()}}
|
|
100175
|
+
function bindEvolutionForm(){for(const field of EVOLUTION_FIELDS)E('evolution'+field).addEventListener('input',()=>{A.evolutionDirty=true;A.evolutionError='';for(const id of ['evolutionGeneratorProfile','evolutionJudgeProfile'])E(id).title=E(id).selectedOptions[0]?.textContent||'';evolutionControls()});E('reloadEvolutionConfigBtn').onclick=async()=>{if(A.evolutionDirty&&!confirm('放弃未保存的 Evolution 更改并重新载入?'))return;try{await loadEvolution();fillEvolutionForm(A.evolution.config);await loadEvolutionModels();evolutionControls()}catch(err){toast(err.message,true)}};E('refreshEvolutionModelsBtn').onclick=()=>loadEvolutionModels().catch(err=>toast(err.message,true))}
|
|
98730
100176
|
async function emergencyEvolutionOff(){if(!confirm('Set evolution to Off, cancel the active run, and abort any Canary?'))return;await api('/api/admin/evolution/emergency-off',{method:'POST',body:'{}'});await loadEvolution();toast('Liquid kernel evolution is Off')}
|
|
98731
100177
|
async function evolutionRunAction(runId,action){if(!confirm(action+' evolution run '+runId+'?'))return;await api('/api/admin/evolution/runs/'+encodeURIComponent(runId)+'/'+action,{method:'POST',body:JSON.stringify({reason:'Admin '+action})});await loadEvolution();toast('Run '+action+' accepted')}
|
|
98732
100178
|
async function evolutionVersionAction(version,action){if(!confirm(action+' kernel '+version+'?'))return;await api('/api/admin/evolution/versions/'+encodeURIComponent(version)+'/'+action,{method:'POST',body:JSON.stringify({reason:'Admin '+action})});await loadEvolution();toast('Kernel '+action+' accepted')}
|
|
98733
|
-
async function showEvolutionRun(runId){const detail=await api('/api/admin/evolution/runs/'+encodeURIComponent(runId));E('evolutionDetailCard').classList.remove('hidden');E('evolutionDetailTitle').textContent='Run '+runId;E('evolutionDetail').innerHTML='';const result=detail.result||{},scores=node('div',{class:'evolution-score'});for(const [label,value] of [['Hard',result.candidate?.hard],['LLM',result.candidate?.soft],['Mixed',result.candidate?.mixed]])scores.appendChild(node('div',{},`${label}\n${Number(value||0).toFixed(1)}`));E('evolutionDetail').append(scores,node('pre',{},JSON.stringify(detail,null,2)));E('evolutionDetailCard').scrollIntoView({behavior:'smooth'})}
|
|
100179
|
+
async function showEvolutionRun(runId){const detail=await api('/api/admin/evolution/runs/'+encodeURIComponent(runId));E('evolutionDetailCard').classList.remove('hidden');E('evolutionDetailTitle').textContent='Run '+runId;E('evolutionDetail').innerHTML='';const result=detail.result||{},scores=node('div',{class:'evolution-score'});for(const [label,value] of [['Hard',result.candidate?.hard],['LLM',result.candidate?.soft],['Mixed',result.candidate?.mixed]])scores.appendChild(node('div',{},`${label}\n${Number(value||0).toFixed(1)}`));const refs=detail.config||{};E('evolutionDetail').append(node('p',{},'状态: '+detail.status+' · 生成: '+(refs.generator_model_ref?.model||refs.generator_profile||'全局默认')+' · 评审: '+(refs.judge_model_ref?.model||refs.judge_profile||'全局默认')+(detail.error?' · '+detail.error:'')),scores,node('pre',{},JSON.stringify(detail,null,2)));E('evolutionDetailCard').scrollIntoView({behavior:'smooth'})}
|
|
98734
100180
|
async function showEvolutionVersion(version,diffOnly=false){const path='/api/admin/evolution/versions/'+encodeURIComponent(version)+(diffOnly?'/diff':'');const detail=await api(path);E('evolutionDetailCard').classList.remove('hidden');E('evolutionDetailTitle').textContent=(diffOnly?'Diff ':'Version ')+version;E('evolutionDetail').innerHTML='';E('evolutionDetail').appendChild(node('pre',{},diffOnly?detail.diff||'No diff':JSON.stringify(detail,null,2)));E('evolutionDetailCard').scrollIntoView({behavior:'smooth'})}
|
|
98735
100181
|
async function loadApps(){const [apps,skills]=await Promise.all([api('/api/admin/apps'),api('/api/apps/skills')]);A.apps=Array.isArray(apps)?apps:[];A.skills=Array.isArray(skills)?skills:[];renderSkillCatalog();renderAdminApps()}
|
|
98736
100182
|
function renderSkillCatalog(){const q=String(E('adminSkillSearch').value||'').trim().toLowerCase(),host=E('adminSkillCatalog');host.innerHTML='';const selected=new Set(A.selectedSkills);const rows=A.skills.filter(s=>!q||[s.id,s.name,s.description].join(' ').toLowerCase().includes(q));if(!rows.length){host.appendChild(node('div',{class:'empty'},'没有匹配的 Skill'));return}rows.forEach(s=>{const label=node('label',{class:'skill-option'+(selected.has(s.id)?' selected':'')}),check=node('input',{type:'checkbox'});check.checked=selected.has(s.id);check.addEventListener('change',()=>toggleAdminSkill(s.id));const text=node('div');text.append(node('strong',{},s.name||s.id),node('span',{},s.id),node('span',{},s.description||''));label.append(check,text);host.appendChild(label)});renderSelectedSkills()}
|
|
@@ -98740,7 +100186,7 @@ async function createSharedApp(){const payload={name:E('adminAppName').value.tri
|
|
|
98740
100186
|
function renderAdminApps(){const host=E('adminAppList');host.innerHTML='';document.querySelectorAll('.review-tab').forEach(x=>x.classList.toggle('active',x.dataset.status===A.reviewStatus));const rows=A.apps.filter(x=>x.status===A.reviewStatus);if(!rows.length){host.appendChild(node('div',{class:'card empty'},'此分类暂无应用'));return}rows.sort((a,b)=>(b.updated_at||0)-(a.updated_at||0)).forEach(app=>{const card=node('article',{class:'admin-app-card'}),title=node('div',{class:'admin-app-title'}),left=node('div'),h=node('h3',{},(app.icon?app.icon+' ':'')+(app.name||'未命名应用'));left.appendChild(h);title.append(left,node('span',{class:'badge '+(app.status==='published'?'good':app.status==='rejected'?'muted':'warn')},app.status));card.append(title,node('p',{},app.description||'暂无说明'));const meta=node('div',{class:'app-meta'});meta.append(node('span',{},'revision '+(app.submitted_revision||app.revision||1)),node('span',{},'owner '+(app.owner_hash||'admin')));card.appendChild(meta);const skills=node('div',{class:'app-skills'});(app.skills||[]).sort((a,b)=>(a.order||0)-(b.order||0)).forEach(s=>skills.appendChild(node('span',{},s.name||s.id)));card.appendChild(skills);if(app.review?.note)card.appendChild(node('p',{},'审核备注:'+app.review.note));const history=Array.isArray(app.lifecycle_history)?app.lifecycle_history:[];if(history.length){const lifecycle=node('div',{class:'app-lifecycle'});lifecycle.appendChild(node('strong',{},'治理历史'));[...history].reverse().forEach(item=>{const at=item.at?new Date(Number(item.at)*1000).toLocaleString('zh-CN'):'-';const text=at+' · '+String(item.action||'')+' · '+String(item.from||'')+' -> '+String(item.to||'')+' · revision '+String(item.revision||'')+(item.note?' · '+String(item.note):'');lifecycle.appendChild(node('div',{},text))});card.appendChild(lifecycle)}if(app.status==='pending'){const actions=node('div',{class:'review-actions'}),note=node('input',{placeholder:'审核备注(可选)',maxlength:'1000'}),approve=node('button',{type:'button'},'通过并发布'),reject=node('button',{type:'button',class:'danger'},'拒绝');approve.onclick=()=>reviewApp(app,true,note.value).catch(err=>toast(err.message,true));reject.onclick=()=>reviewApp(app,false,note.value).catch(err=>toast(err.message,true));actions.append(note,approve,reject);card.appendChild(actions)}else if(app.status==='published'||app.status==='unpublished'){const actions=node('div',{class:'review-actions'}),note=node('input',{placeholder:'治理备注(可选)',maxlength:'1000'}),publish=app.status==='unpublished',action=node('button',{type:'button',class:publish?'':'danger'},publish?'重新上架':'下架');action.onclick=()=>changePublication(app,publish,note.value).catch(err=>toast(err.message,true));actions.append(note,action);card.appendChild(actions)}host.appendChild(card)})}
|
|
98741
100187
|
async function reviewApp(app,approve,note){await api('/api/admin/apps/'+encodeURIComponent(app.id)+'/'+(approve?'approve':'reject'),{method:'POST',body:JSON.stringify({note,revision:app.submitted_revision||app.revision})});await loadApps();toast(approve?'应用已发布':'应用已拒绝')}
|
|
98742
100188
|
async function changePublication(app,publish,note){const label=publish?'重新上架':'下架';if(!confirm('确定'+label+'此共享应用吗?'))return;await api('/api/admin/apps/'+encodeURIComponent(app.id)+'/'+(publish?'republish':'unpublish'),{method:'POST',body:JSON.stringify({note,revision:app.submitted_revision||app.revision,lifecycle_revision:app.lifecycle_revision||0})});await loadApps();toast('应用已'+label)}
|
|
98743
|
-
function bind(){document.querySelectorAll('.nav-tab').forEach(x=>x.onclick=()=>switchView(x.dataset.view));document.querySelectorAll('.review-tab').forEach(x=>x.onclick=()=>{A.reviewStatus=x.dataset.status;renderAdminApps()});E('adminLanguageSelect').onchange=()=>saveAdminLanguage(E('adminLanguageSelect').value);E('evolutionMode').onchange=()=>{const schedules={Off:'off',Tuning:'weekly',Thinking:'every_3_days',Aggressive:'daily'};E('evolutionSchedule').value=schedules[E('evolutionMode').value]||'off'};E('refreshMetricsBtn').onclick=()=>loadMetrics().catch(err=>toast(err.message,true));E('metricsHours').onchange=()=>{A.metricUserHash='';loadMetrics('').catch(err=>toast(err.message,true))};E('metricUserSelect').onchange=()=>{A.metricUserHash=E('metricUserSelect').value;loadMetrics(A.metricUserHash).catch(err=>toast(err.message,true))};E('saveConfigBtn').onclick=()=>saveConfig(false).catch(err=>{notice(err.message,true);toast(err.message,true)});E('syncActiveConfigBtn').onclick=()=>syncActiveConfig().catch(err=>toast(err.message,true));E('setDefaultBtn').onclick=()=>saveConfig(true).catch(err=>toast(err.message,true));E('restoreDefaultBtn').onclick=()=>resetConfig('default').catch(err=>toast(err.message,true));E('resetInitialBtn').onclick=()=>{if(confirm('确定重置为程序初始参数吗?'))resetConfig('initial').catch(err=>toast(err.message,true))};E('saveRestartBtn').onclick=()=>restartWithDraft().catch(err=>toast(err.message,true));E('exportConfigBtn').onclick=()=>downloadJson('clouds-coder-startup-config.json',{version:1,values:collectConfig()});E('importConfigBtn').onclick=()=>E('configFileInput').click();E('configFileInput').onchange=()=>{const f=E('configFileInput').files?.[0];if(f)importConfigFile(f).catch(err=>toast(err.message,true));E('configFileInput').value=''};E('refreshEvolutionBtn').onclick=()=>loadEvolution().catch(err=>toast(err.message,true));E('saveEvolutionBtn').onclick=()=>saveEvolution().catch(err=>toast(err.message,true));E('runEvolutionBtn').onclick=()=>runEvolution().catch(err=>toast(err.message,true));E('killEvolutionBtn').onclick=()=>emergencyEvolutionOff().catch(err=>toast(err.message,true));E('closeEvolutionDetailBtn').onclick=()=>E('evolutionDetailCard').classList.add('hidden');E('refreshAppsBtn').onclick=()=>loadApps().catch(err=>toast(err.message,true));E('adminSkillSearch').oninput=renderSkillCatalog;E('createSharedAppBtn').onclick=()=>createSharedApp().catch(err=>toast(err.message,true));E('refreshCollaborationBtn').onclick=()=>loadCollaboration().catch(err=>toast(err.message,true));E('enableLanCollaborationBtn').onclick=()=>configureLanCollaboration(true).catch(err=>toast(err.message,true));E('disableLanCollaborationBtn').onclick=()=>configureLanCollaboration(false).catch(err=>toast(err.message,true));E('createCollabProjectForm').onsubmit=ev=>{ev.preventDefault();createCollabProject().catch(err=>toast(err.message,true))};E('collabProjectSearch').oninput=()=>loadCollaboration().catch(err=>toast(err.message,true));E('collabProjectStatus').onchange=()=>loadCollaboration().catch(err=>toast(err.message,true));E('collabMemberSearch').oninput=()=>{if(A.collabProject)loadCollabProjectDetail().catch(err=>toast(err.message,true))};E('collabMemberStatus').onchange=()=>{if(A.collabProject)loadCollabProjectDetail().catch(err=>toast(err.message,true))};E('logoutBtn').onclick=()=>logoutAdmin();E('setupForm').onsubmit=ev=>{ev.preventDefault();registerAdmin()};E('passwordLoginForm').onsubmit=ev=>{ev.preventDefault();loginWithPassword()};E('tokenLoginForm').onsubmit=ev=>{ev.preventDefault();loginWithToken()};E('retryAuthBtn').onclick=()=>bootstrapAuth();window.addEventListener('focus',()=>loadAdminLanguage(true));document.addEventListener('visibilitychange',()=>{if(document.visibilityState==='visible')loadAdminLanguage(true)});window.addEventListener('resize',()=>{clearTimeout(A.metricResizeTimer);A.metricResizeTimer=setTimeout(()=>{if(A.metrics&&E('metricsView').classList.contains('active'))renderMetricCharts(A.metrics)},160)})}
|
|
100189
|
+
function bind(){document.querySelectorAll('.nav-tab').forEach(x=>x.onclick=()=>switchView(x.dataset.view));document.querySelectorAll('.review-tab').forEach(x=>x.onclick=()=>{A.reviewStatus=x.dataset.status;renderAdminApps()});E('adminLanguageSelect').onchange=()=>saveAdminLanguage(E('adminLanguageSelect').value);E('evolutionMode').onchange=()=>{const schedules={Off:'off',Tuning:'weekly',Thinking:'every_3_days',Aggressive:'daily'};E('evolutionSchedule').value=schedules[E('evolutionMode').value]||'off'};E('refreshMetricsBtn').onclick=()=>loadMetrics().catch(err=>toast(err.message,true));E('metricsHours').onchange=()=>{A.metricUserHash='';loadMetrics('').catch(err=>toast(err.message,true))};E('metricUserSelect').onchange=()=>{A.metricUserHash=E('metricUserSelect').value;loadMetrics(A.metricUserHash).catch(err=>toast(err.message,true))};E('saveConfigBtn').onclick=()=>saveConfig(false).catch(err=>{notice(err.message,true);toast(err.message,true)});E('syncActiveConfigBtn').onclick=()=>syncActiveConfig().catch(err=>toast(err.message,true));E('setDefaultBtn').onclick=()=>saveConfig(true).catch(err=>toast(err.message,true));E('restoreDefaultBtn').onclick=()=>resetConfig('default').catch(err=>toast(err.message,true));E('resetInitialBtn').onclick=()=>{if(confirm('确定重置为程序初始参数吗?'))resetConfig('initial').catch(err=>toast(err.message,true))};E('saveRestartBtn').onclick=()=>restartWithDraft().catch(err=>toast(err.message,true));E('exportConfigBtn').onclick=()=>downloadJson('clouds-coder-startup-config.json',{version:1,values:collectConfig()});E('importConfigBtn').onclick=()=>E('configFileInput').click();E('configFileInput').onchange=()=>{const f=E('configFileInput').files?.[0];if(f)importConfigFile(f).catch(err=>toast(err.message,true));E('configFileInput').value=''};bindEvolutionForm();E('refreshEvolutionBtn').onclick=()=>loadEvolution().catch(err=>toast(err.message,true));E('saveEvolutionBtn').onclick=()=>saveEvolution().catch(err=>toast(err.message,true));E('runEvolutionBtn').onclick=()=>runEvolution().catch(err=>toast(err.message,true));E('killEvolutionBtn').onclick=()=>emergencyEvolutionOff().catch(err=>toast(err.message,true));E('closeEvolutionDetailBtn').onclick=()=>E('evolutionDetailCard').classList.add('hidden');E('refreshAppsBtn').onclick=()=>loadApps().catch(err=>toast(err.message,true));E('adminSkillSearch').oninput=renderSkillCatalog;E('createSharedAppBtn').onclick=()=>createSharedApp().catch(err=>toast(err.message,true));E('refreshCollaborationBtn').onclick=()=>loadCollaboration().catch(err=>toast(err.message,true));E('enableLanCollaborationBtn').onclick=()=>configureLanCollaboration(true).catch(err=>toast(err.message,true));E('disableLanCollaborationBtn').onclick=()=>configureLanCollaboration(false).catch(err=>toast(err.message,true));E('createCollabProjectForm').onsubmit=ev=>{ev.preventDefault();createCollabProject().catch(err=>toast(err.message,true))};E('collabProjectSearch').oninput=()=>loadCollaboration().catch(err=>toast(err.message,true));E('collabProjectStatus').onchange=()=>loadCollaboration().catch(err=>toast(err.message,true));E('collabMemberSearch').oninput=()=>{if(A.collabProject)loadCollabProjectDetail().catch(err=>toast(err.message,true))};E('collabMemberStatus').onchange=()=>{if(A.collabProject)loadCollabProjectDetail().catch(err=>toast(err.message,true))};E('logoutBtn').onclick=()=>logoutAdmin();E('setupForm').onsubmit=ev=>{ev.preventDefault();registerAdmin()};E('passwordLoginForm').onsubmit=ev=>{ev.preventDefault();loginWithPassword()};E('tokenLoginForm').onsubmit=ev=>{ev.preventDefault();loginWithToken()};E('retryAuthBtn').onclick=()=>bootstrapAuth();window.addEventListener('focus',()=>loadAdminLanguage(true));document.addEventListener('visibilitychange',()=>{if(document.visibilityState==='visible')loadAdminLanguage(true)});window.addEventListener('resize',()=>{clearTimeout(A.metricResizeTimer);A.metricResizeTimer=setTimeout(()=>{if(A.metrics&&E('metricsView').classList.contains('active'))renderMetricCharts(A.metrics)},160)})}
|
|
98744
100190
|
window.addEventListener('DOMContentLoaded',async()=>{let cached='zh-CN';try{cached=localStorage.getItem('clouds_coder_admin_language')||cached}catch(_){}applyAdminLanguage(cached);observeAdminI18n();bind();bindProcesses();const loginLanguage=E('adminLoginLanguageSelect');if(loginLanguage)loginLanguage.onchange=()=>saveAdminLanguage(loginLanguage.value);await loadAdminLanguage(true);await bootstrapAuth()});
|
|
98745
100191
|
"""
|
|
98746
100192
|
|
|
@@ -114768,6 +116214,7 @@ html,body{scrollbar-gutter:stable}
|
|
|
114768
116214
|
.open-editors,.side-list,.toolchains,.collaboration-list,.editor-tabs,.artifact-stage,.artifact-markdown,.panel-content,.agent-todo-body,.agent-messages,.agent-composer,.agent-attachments,.agent-tool-output,.agent-diff,.agent-model-menu,.palette-results,.modal,.modal-body,.conflict-preview,.prompt-enhance-body,.application-list,.ide-application-fields,.ide-application-selected,.ide-application-catalog,.xterm-viewport{scrollbar-gutter:stable}
|
|
114769
116215
|
@media(hover:none),(pointer:coarse){*:active{scrollbar-color:var(--ide-scrollbar-thumb) transparent!important}*:active::-webkit-scrollbar-thumb{background:var(--ide-scrollbar-thumb)!important;background-clip:padding-box!important}}
|
|
114770
116216
|
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important}}
|
|
116217
|
+
.agent-model-menu button{color:#ddd}.agent-model-menu button:hover{color:#fff}.agent-model-config .codicon,.agent-model-config .codicon:before{font-size:18px!important}.agent-model-config{color:#d4d4d4!important}
|
|
114771
116218
|
"""
|
|
114772
116219
|
|
|
114773
116220
|
IDE_JS = r"""
|
|
@@ -117484,25 +118931,47 @@ class AppContext:
|
|
|
117484
118931
|
if base:
|
|
117485
118932
|
self.base_url = base
|
|
117486
118933
|
|
|
117487
|
-
def
|
|
117488
|
-
|
|
117489
|
-
|
|
117490
|
-
|
|
117491
|
-
|
|
117492
|
-
if
|
|
117493
|
-
|
|
117494
|
-
|
|
117495
|
-
|
|
117496
|
-
|
|
117497
|
-
|
|
117498
|
-
"
|
|
117499
|
-
|
|
118934
|
+
def _evolution_profile_source(self, source: str, owner: str) -> tuple[dict, str]:
|
|
118935
|
+
if source == "global":
|
|
118936
|
+
return copy.deepcopy(self.global_profiles), self.global_active_profile_id
|
|
118937
|
+
if not owner or owner in {".", ".."} or "/" in owner or "\\" in owner:
|
|
118938
|
+
raise LiquidKernelError("invalid_model_owner", "model owner is invalid", 403)
|
|
118939
|
+
if source == "ide":
|
|
118940
|
+
try:
|
|
118941
|
+
with self.ide_auth._connect() as conn:
|
|
118942
|
+
account = conn.execute("SELECT disabled FROM ide_accounts WHERE user_id=?", (owner,)).fetchone()
|
|
118943
|
+
except sqlite3.Error as exc:
|
|
118944
|
+
raise LiquidKernelError("model_owner_unavailable", "IDE model ownership cannot be verified", 503) from exc
|
|
118945
|
+
if account is None or bool(account["disabled"]):
|
|
118946
|
+
raise LiquidKernelError("model_owner_disabled", "IDE model owner was deleted or disabled", 409)
|
|
118947
|
+
path = self.codes_root / owner / "user_prefs.json"
|
|
118948
|
+
if not path.is_file():
|
|
118949
|
+
raise LiquidKernelError("model_profile_missing", "private model configuration no longer exists", 409)
|
|
118950
|
+
raw = self.crypto.read_json(path, {})
|
|
118951
|
+
profiles = raw.get("model_profiles", {}) if isinstance(raw, dict) else {}
|
|
118952
|
+
if not isinstance(profiles, dict):
|
|
118953
|
+
raise LiquidKernelError("model_profile_missing", "private model configuration cannot be read", 409)
|
|
118954
|
+
return profiles, str(raw.get("active_profile_id", ""))
|
|
118955
|
+
|
|
118956
|
+
def _evolution_models(self):
|
|
118957
|
+
from liquid_kernel.models import EvolutionModels
|
|
118958
|
+
return EvolutionModels(
|
|
118959
|
+
self._evolution_profile_source,
|
|
118960
|
+
lambda profile: probe_provider_models(profile, cached_only=True),
|
|
118961
|
+
model_runtime_settings_for,
|
|
118962
|
+
self.liquid_kernel.registry.signing_key,
|
|
118963
|
+
)
|
|
118964
|
+
|
|
118965
|
+
def _liquid_kernel_profile(self, profile_id="") -> dict:
|
|
118966
|
+
reference = profile_id if isinstance(profile_id, dict) else None
|
|
118967
|
+
_, profile = self._evolution_models().resolve(reference, "" if reference else profile_id)
|
|
118968
|
+
return profile
|
|
117500
118969
|
|
|
117501
|
-
def _liquid_kernel_model_call(self, system: str, prompt: str, profile_id
|
|
118970
|
+
def _liquid_kernel_model_call(self, system: str, prompt: str, profile_id, max_tokens: int) -> dict:
|
|
117502
118971
|
profile = self._liquid_kernel_profile(profile_id)
|
|
117503
118972
|
client = OllamaClient(
|
|
117504
|
-
base_url=str(profile.get("base_url",
|
|
117505
|
-
model=str(profile
|
|
118973
|
+
base_url=str(profile.get("base_url", "") or ""),
|
|
118974
|
+
model=str(profile["model"]),
|
|
117506
118975
|
timeout=max(DEFAULT_REQUEST_TIMEOUT, min(MAX_TIMEOUT_SECONDS, 900)),
|
|
117507
118976
|
provider=str(profile.get("provider", "ollama") or "ollama"),
|
|
117508
118977
|
endpoint=str(profile.get("endpoint", "") or ""),
|
|
@@ -117513,27 +118982,43 @@ class AppContext:
|
|
|
117513
118982
|
response_stream=False,
|
|
117514
118983
|
)
|
|
117515
118984
|
client.apply_profile(profile)
|
|
117516
|
-
|
|
117517
|
-
|
|
117518
|
-
|
|
117519
|
-
|
|
117520
|
-
|
|
117521
|
-
|
|
117522
|
-
|
|
117523
|
-
|
|
117524
|
-
|
|
118985
|
+
# Custom templates may contain a literal model. Bind it to the selected
|
|
118986
|
+
# model too, without changing the template in its encrypted source.
|
|
118987
|
+
if client.provider == "custom_http" and client.payload_template:
|
|
118988
|
+
template = client.payload_template.strip()
|
|
118989
|
+
if not template.startswith("{"):
|
|
118990
|
+
raise LiquidKernelError("invalid_model_template", "custom model template must be a JSON object")
|
|
118991
|
+
client.payload_template = template[:-1] + ',"model":' + json.dumps(client.model) + ',"max_tokens":' + str(max(512, min(int(max_tokens or 4096), 32_000))) + '}'
|
|
118992
|
+
active_run = self.liquid_kernel.active_run_id
|
|
118993
|
+
try:
|
|
118994
|
+
response = client.chat(
|
|
118995
|
+
[{"role": "user", "content": str(prompt or "")}],
|
|
118996
|
+
system=str(system or ""),
|
|
118997
|
+
max_tokens=max(512, min(int(max_tokens or 4096), 32_000)),
|
|
118998
|
+
temperature=max(0.0, min(0.3, float(profile.get("temperature", 0.1)))),
|
|
118999
|
+
think=False,
|
|
119000
|
+
response_stream=False,
|
|
119001
|
+
cancel_check=lambda: bool(active_run and active_run in self.liquid_kernel.cancelled),
|
|
119002
|
+
)
|
|
119003
|
+
except Exception as exc:
|
|
119004
|
+
if active_run in self.liquid_kernel.cancelled:
|
|
119005
|
+
raise LiquidKernelError("run_cancelled", "evolution run was cancelled", 409) from exc
|
|
119006
|
+
# Provider exceptions may contain credentials, URLs or request data.
|
|
119007
|
+
raise LiquidKernelError("model_call_failed", "Selected model request failed (" + type(exc).__name__ + "); check its service, credentials and model availability", 502) from exc
|
|
117525
119008
|
text = str(response.get("content", "") or "")
|
|
119009
|
+
secrets = [profile.get("api_key", ""), *list((profile.get("headers") or {}).values())]
|
|
119010
|
+
for secret in secrets:
|
|
119011
|
+
if isinstance(secret, str) and len(secret) >= 4:
|
|
119012
|
+
text = text.replace(secret, "[secret redacted]")
|
|
119013
|
+
text = self._liquid_kernel_redact_text(text)
|
|
117526
119014
|
parsed = parse_json_object(text, {})
|
|
117527
119015
|
if not isinstance(parsed, dict) or not parsed:
|
|
117528
|
-
raise LiquidKernelError(
|
|
117529
|
-
"invalid_model_output",
|
|
117530
|
-
"evolution model must return a JSON object",
|
|
117531
|
-
details={"output": trim(text, 1200)},
|
|
117532
|
-
)
|
|
119016
|
+
raise LiquidKernelError("invalid_model_output", "evolution model must return a JSON object")
|
|
117533
119017
|
return parsed
|
|
117534
119018
|
|
|
117535
119019
|
def _liquid_kernel_judge_call(self, payload: dict, profile_id: str, max_tokens: int) -> dict:
|
|
117536
|
-
|
|
119020
|
+
from liquid_kernel.control import valid_judge_score
|
|
119021
|
+
profile = profile_id
|
|
117537
119022
|
order = ["incumbent", "candidate"]
|
|
117538
119023
|
random.SystemRandom().shuffle(order)
|
|
117539
119024
|
source_a = str(payload.get(f"{order[0]}_source", "") or "")
|
|
@@ -117562,8 +119047,8 @@ class AppContext:
|
|
|
117562
119047
|
profile,
|
|
117563
119048
|
max_tokens,
|
|
117564
119049
|
)
|
|
117565
|
-
score_a =
|
|
117566
|
-
score_b =
|
|
119050
|
+
score_a = valid_judge_score(result.get("score_a"))
|
|
119051
|
+
score_b = valid_judge_score(result.get("score_b"))
|
|
117567
119052
|
mapped = {order[0]: score_a, order[1]: score_b}
|
|
117568
119053
|
return {
|
|
117569
119054
|
"incumbent": mapped["incumbent"],
|
|
@@ -117989,6 +119474,8 @@ class AppContext:
|
|
|
117989
119474
|
experience_provider=self._liquid_kernel_experience,
|
|
117990
119475
|
model_callback=self._liquid_kernel_model_call,
|
|
117991
119476
|
judge_callback=self._liquid_kernel_judge_call,
|
|
119477
|
+
model_resolver=lambda config: self._evolution_models().bind_run(config),
|
|
119478
|
+
config_validator=lambda config, **kwargs: self._evolution_models().validate_config(config, **kwargs),
|
|
117992
119479
|
)
|
|
117993
119480
|
if self.liquid_kernel_startup_policy == "inject":
|
|
117994
119481
|
injection = self.liquid_kernel.inject_embedded_kernel()
|
|
@@ -123744,6 +125231,7 @@ document.addEventListener('DOMContentLoaded', function(){{
|
|
|
123744
125231
|
selection: str,
|
|
123745
125232
|
*,
|
|
123746
125233
|
client_ip: str = "",
|
|
125234
|
+
settings: dict | None = None,
|
|
123747
125235
|
) -> dict:
|
|
123748
125236
|
collaboration = str(user_id or "").startswith("collab:")
|
|
123749
125237
|
source_manager = None
|
|
@@ -123762,20 +125250,20 @@ document.addEventListener('DOMContentLoaded', function(){{
|
|
|
123762
125250
|
"Stop active Agents in the main Web UI before changing its model."
|
|
123763
125251
|
)
|
|
123764
125252
|
if bool(getattr(sess, "running", False)):
|
|
123765
|
-
sess._queue_deferred_runtime_update("model_selection", {"selection": picked, "model_override": ""})
|
|
125253
|
+
sess._queue_deferred_runtime_update("model_selection", {"selection": picked, "model_override": "", "settings": normalize_model_runtime_settings(settings)})
|
|
123766
125254
|
if source_manager is not None:
|
|
123767
|
-
source_manager.set_runtime_model(picked, None)
|
|
125255
|
+
source_manager.set_runtime_model(picked, None, settings)
|
|
123768
125256
|
queued = sess.model_catalog()
|
|
123769
125257
|
queued["queued"] = True
|
|
123770
125258
|
queued["note"] = "Model switch queued until the current run finishes."
|
|
123771
125259
|
return queued
|
|
123772
125260
|
if source_manager is not None:
|
|
123773
|
-
out = source_manager.set_runtime_model(picked, None)
|
|
125261
|
+
out = source_manager.set_runtime_model(picked, None, settings)
|
|
123774
125262
|
if target_manager is not source_manager:
|
|
123775
125263
|
self._sync_ordinary_ide_llm_source(user_id, client_ip, force=True)
|
|
123776
125264
|
out = sess.model_catalog()
|
|
123777
125265
|
return out
|
|
123778
|
-
out = sess.set_runtime_selection(picked)
|
|
125266
|
+
out = sess.set_runtime_selection(picked, settings=settings)
|
|
123779
125267
|
self.manager_for_user(user_id)._sync_from_session(sess, apply_to_all=False)
|
|
123780
125268
|
return out
|
|
123781
125269
|
|
|
@@ -126449,7 +127937,10 @@ document.addEventListener('DOMContentLoaded', function(){{
|
|
|
126449
127937
|
mgr = self.manager_for_user(user_id)
|
|
126450
127938
|
sess = mgr.get(session_id)
|
|
126451
127939
|
if sess and bool(getattr(sess, "_deferred_runtime_sync_requested", False)):
|
|
126452
|
-
|
|
127940
|
+
# Model selection and its per-model runtime controls are user
|
|
127941
|
+
# preferences. Propagate them to every existing session so the
|
|
127942
|
+
# WebUI and IDE observe the same state immediately.
|
|
127943
|
+
mgr._sync_from_session(sess, apply_to_all=True)
|
|
126453
127944
|
sess._deferred_runtime_sync_requested = False
|
|
126454
127945
|
except Exception:
|
|
126455
127946
|
pass
|
|
@@ -127261,6 +128752,7 @@ document.addEventListener('DOMContentLoaded', function(){{
|
|
|
127261
128752
|
cfg = dict(config or {})
|
|
127262
128753
|
OllamaClient.clear_global_probe_cache()
|
|
127263
128754
|
parsed = parse_llm_config_profiles(cfg, self.base_url, self.model)
|
|
128755
|
+
probe_and_merge_model_profiles(parsed.get("profiles", []), force_refresh=True)
|
|
127264
128756
|
self.default_llm_config = cfg
|
|
127265
128757
|
revision = self._llm_config_revision(cfg)
|
|
127266
128758
|
self.global_llm_config_revision = revision
|
|
@@ -128031,6 +129523,7 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128031
129523
|
|
|
128032
129524
|
def model_catalog(self) -> dict:
|
|
128033
129525
|
opts = []
|
|
129526
|
+
profiles_changed = False
|
|
128034
129527
|
for pid, profile in self.global_profiles.items():
|
|
128035
129528
|
model = str(profile.get("model", ""))
|
|
128036
129529
|
caps = merge_multimodal_capabilities(
|
|
@@ -128048,9 +129541,80 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128048
129541
|
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
128049
129542
|
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
128050
129543
|
"response_stream": bool(profile.get("response_stream", False)),
|
|
129544
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
129545
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
129546
|
+
"reasoning_supported": (
|
|
129547
|
+
profile.get("reasoning_supported")
|
|
129548
|
+
if profile.get("reasoning_supported") is not None
|
|
129549
|
+
else model_reasoning_style(
|
|
129550
|
+
str(profile.get("provider", "")), model, caps
|
|
129551
|
+
)
|
|
129552
|
+
!= "none"
|
|
129553
|
+
),
|
|
129554
|
+
"reasoning_style": str(
|
|
129555
|
+
profile.get("reasoning_style")
|
|
129556
|
+
or model_reasoning_style(str(profile.get("provider", "")), model, caps)
|
|
129557
|
+
or ""
|
|
129558
|
+
),
|
|
129559
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
129560
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
128051
129561
|
"capabilities": caps,
|
|
128052
129562
|
}
|
|
128053
129563
|
)
|
|
129564
|
+
seen = {str(x.get("selection", "")) for x in opts}
|
|
129565
|
+
for pid, profile in self.global_profiles.items():
|
|
129566
|
+
configured = [str(x).strip() for x in profile.get("models", []) if str(x).strip()]
|
|
129567
|
+
records = probe_provider_models(profile, background=True)
|
|
129568
|
+
profiles_changed = merge_probed_models_into_profile(profile, records) or profiles_changed
|
|
129569
|
+
for rec in records:
|
|
129570
|
+
model_id = str(rec.get("id", "") or "").strip()
|
|
129571
|
+
if model_id and model_id not in configured:
|
|
129572
|
+
configured.append(model_id)
|
|
129573
|
+
for model_id in configured:
|
|
129574
|
+
selection = f"{pid}::{model_id}"
|
|
129575
|
+
if not model_id or selection in seen:
|
|
129576
|
+
continue
|
|
129577
|
+
seen.add(selection)
|
|
129578
|
+
rec = next((r for r in records if str(r.get("id", "")) == model_id), {})
|
|
129579
|
+
rcaps = rec.get("capabilities", {}) if isinstance(rec, dict) else {}
|
|
129580
|
+
opts.append({
|
|
129581
|
+
"selection": selection, "profile_id": pid,
|
|
129582
|
+
"provider": profile.get("provider", ""), "model": model_id,
|
|
129583
|
+
"label": f"{profile.get('label', pid)} | {model_id}",
|
|
129584
|
+
"source": "provider-probe" if rec else profile.get("source", ""),
|
|
129585
|
+
"thinking_hint": bool(profile.get("thinking_hint", False)),
|
|
129586
|
+
"thinking_stream": bool(profile.get("thinking_stream", False)),
|
|
129587
|
+
"response_stream": bool(profile.get("response_stream", False)),
|
|
129588
|
+
"effort": str(profile.get("effort", "") or ""),
|
|
129589
|
+
"max_effort": str(profile.get("max_effort", "") or ""),
|
|
129590
|
+
"reasoning_supported": (
|
|
129591
|
+
rcaps.get("reasoning_supported")
|
|
129592
|
+
if isinstance(rcaps, dict) and rcaps.get("reasoning_supported") is not None
|
|
129593
|
+
else profile.get("reasoning_supported")
|
|
129594
|
+
if profile.get("reasoning_supported") is not None
|
|
129595
|
+
else model_reasoning_style(
|
|
129596
|
+
str(profile.get("provider", "")), model_id,
|
|
129597
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
129598
|
+
)
|
|
129599
|
+
!= "none"
|
|
129600
|
+
),
|
|
129601
|
+
"reasoning_style": str(
|
|
129602
|
+
(rcaps.get("reasoning_style") if isinstance(rcaps, dict) else "")
|
|
129603
|
+
or profile.get("reasoning_style", "")
|
|
129604
|
+
or model_reasoning_style(
|
|
129605
|
+
str(profile.get("provider", "")), model_id,
|
|
129606
|
+
rcaps if isinstance(rcaps, dict) else None,
|
|
129607
|
+
)
|
|
129608
|
+
or ""
|
|
129609
|
+
),
|
|
129610
|
+
"display_name": str(profile.get("display_name", profile.get("label", pid)) or ""),
|
|
129611
|
+
"title": str(profile.get("title", profile.get("label", pid)) or ""),
|
|
129612
|
+
"capabilities": merge_multimodal_capabilities(infer_model_multimodal_capabilities(str(profile.get("provider", "")), model_id), parse_capability_overrides(profile.get("capabilities", {}))),
|
|
129613
|
+
})
|
|
129614
|
+
for _option in opts:
|
|
129615
|
+
_pid = str(_option.get("profile_id", "") or "")
|
|
129616
|
+
_profile = self.global_profiles.get(_pid, {})
|
|
129617
|
+
apply_model_option_runtime_fields(_option, _profile, str(_option.get("model", "") or ""))
|
|
128054
129618
|
selected_profile = self.global_profiles.get(self.global_active_profile_id, {})
|
|
128055
129619
|
selected = f"{self.global_active_profile_id}::{selected_profile.get('model', self.model)}"
|
|
128056
129620
|
option_map = {str(x.get("selection", "")) for x in opts}
|
|
@@ -128067,6 +129631,25 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128067
129631
|
"thinking_hint": bool(selected_profile.get("thinking_hint", False)),
|
|
128068
129632
|
"thinking_stream": bool(selected_profile.get("thinking_stream", False)),
|
|
128069
129633
|
"response_stream": bool(selected_profile.get("response_stream", False)),
|
|
129634
|
+
"reasoning_supported": (
|
|
129635
|
+
selected_profile.get("reasoning_supported")
|
|
129636
|
+
if selected_profile.get("reasoning_supported") is not None
|
|
129637
|
+
else model_reasoning_style(
|
|
129638
|
+
str(selected_profile.get("provider", "")),
|
|
129639
|
+
str(selected_profile.get("model", self.model)),
|
|
129640
|
+
selected_profile.get("capabilities", {}),
|
|
129641
|
+
)
|
|
129642
|
+
!= "none"
|
|
129643
|
+
),
|
|
129644
|
+
"reasoning_style": str(
|
|
129645
|
+
selected_profile.get("reasoning_style")
|
|
129646
|
+
or model_reasoning_style(
|
|
129647
|
+
str(selected_profile.get("provider", "")),
|
|
129648
|
+
str(selected_profile.get("model", self.model)),
|
|
129649
|
+
selected_profile.get("capabilities", {}),
|
|
129650
|
+
)
|
|
129651
|
+
or ""
|
|
129652
|
+
),
|
|
128070
129653
|
"capabilities": merge_multimodal_capabilities(
|
|
128071
129654
|
infer_model_multimodal_capabilities(
|
|
128072
129655
|
str(selected_profile.get("provider", "")),
|
|
@@ -128076,6 +129659,13 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128076
129659
|
),
|
|
128077
129660
|
},
|
|
128078
129661
|
)
|
|
129662
|
+
selected_option = next((item for item in opts if str(item.get("selection", "")) == selected), None)
|
|
129663
|
+
if selected_option is not None:
|
|
129664
|
+
apply_model_option_runtime_fields(
|
|
129665
|
+
selected_option,
|
|
129666
|
+
selected_profile,
|
|
129667
|
+
str(selected_option.get("model", "") or ""),
|
|
129668
|
+
)
|
|
128079
129669
|
active_caps = merge_multimodal_capabilities(
|
|
128080
129670
|
infer_model_multimodal_capabilities(
|
|
128081
129671
|
str(selected_profile.get("provider", "")),
|
|
@@ -128083,6 +129673,21 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128083
129673
|
),
|
|
128084
129674
|
parse_capability_overrides(selected_profile.get("capabilities", {})),
|
|
128085
129675
|
)
|
|
129676
|
+
if profiles_changed:
|
|
129677
|
+
self.global_profiles_payload = {
|
|
129678
|
+
**(self.global_profiles_payload if isinstance(self.global_profiles_payload, dict) else {}),
|
|
129679
|
+
"profiles": [dict(item) for item in self.global_profiles.values()],
|
|
129680
|
+
}
|
|
129681
|
+
# Keep the discovered directory in the system config so a restart
|
|
129682
|
+
# can render the same provider/model choices before the next probe.
|
|
129683
|
+
persisted = dict(self.default_llm_config or {})
|
|
129684
|
+
persisted["profiles"] = [dict(item) for item in self.global_profiles.values()]
|
|
129685
|
+
persisted["default_profile_id"] = self.global_active_profile_id
|
|
129686
|
+
self.default_llm_config = persisted
|
|
129687
|
+
try:
|
|
129688
|
+
LLM_CONFIG_PATH.write_text(json_dumps(persisted, indent=2), encoding="utf-8")
|
|
129689
|
+
except Exception:
|
|
129690
|
+
pass
|
|
128086
129691
|
return {
|
|
128087
129692
|
"provider": selected_profile.get("provider", "ollama"),
|
|
128088
129693
|
"models": [x["selection"] for x in opts] or [self.model],
|
|
@@ -128094,7 +129699,7 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128094
129699
|
"active_capabilities": active_caps,
|
|
128095
129700
|
}
|
|
128096
129701
|
|
|
128097
|
-
def set_runtime_model(self, model: str, thinking: bool | None = None) -> dict:
|
|
129702
|
+
def set_runtime_model(self, model: str, thinking: bool | None = None, settings: dict | None = None) -> dict:
|
|
128098
129703
|
raw = str(model or "").strip()
|
|
128099
129704
|
if not raw:
|
|
128100
129705
|
raise ValueError("model required")
|
|
@@ -128122,6 +129727,11 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128122
129727
|
self.global_profiles[pid]["capabilities"] = infer_model_multimodal_capabilities(
|
|
128123
129728
|
"ollama", selected_model.strip()
|
|
128124
129729
|
)
|
|
129730
|
+
self.global_profiles[pid] = apply_model_runtime_settings(
|
|
129731
|
+
self.global_profiles.get(pid, {}),
|
|
129732
|
+
str(self.global_profiles[pid].get("model", "") or ""),
|
|
129733
|
+
settings,
|
|
129734
|
+
)
|
|
128125
129735
|
self.global_active_profile_id = pid
|
|
128126
129736
|
selected_profile = dict(self.global_profiles.get(pid, {}))
|
|
128127
129737
|
self._sync_global_ollama_defaults(selected_profile)
|
|
@@ -128130,7 +129740,7 @@ Use this skill when tasks match this flow pattern and reusable execution is need
|
|
|
128130
129740
|
with self._lock:
|
|
128131
129741
|
for mgr in self._session_mgrs.values():
|
|
128132
129742
|
try:
|
|
128133
|
-
mgr.set_runtime_model(selection, self.thinking)
|
|
129743
|
+
mgr.set_runtime_model(selection, self.thinking, settings)
|
|
128134
129744
|
except Exception:
|
|
128135
129745
|
pass
|
|
128136
129746
|
return self.model_catalog()
|
|
@@ -129393,6 +131003,28 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
129393
131003
|
def _user_id(self) -> str:
|
|
129394
131004
|
return user_id_from_ip(self._client_ip())
|
|
129395
131005
|
|
|
131006
|
+
def _evolution_model_context(self) -> dict:
|
|
131007
|
+
# Admin Authorization is distinct from IDE identity. Verify the IDE
|
|
131008
|
+
# cookie (or explicit IDE session header) using the existing auth store.
|
|
131009
|
+
token = str(self.headers.get("X-Evolution-IDE-Token", "") or "").strip()
|
|
131010
|
+
if not token:
|
|
131011
|
+
for item in str(self.headers.get("Cookie", "") or "").split(";"):
|
|
131012
|
+
key, sep, value = item.strip().partition("=")
|
|
131013
|
+
if sep and key == "clouds_ide_session":
|
|
131014
|
+
token = unquote(value.strip())
|
|
131015
|
+
break
|
|
131016
|
+
account = self.app.ide_auth.verify_session(token, self._client_ip()) if token else None
|
|
131017
|
+
if account and (bool(account.get("must_change_password")) or (not bool(getattr(self.app, "ide_password_login_enabled", True)) and account.get("session_kind") != "local_auto")):
|
|
131018
|
+
account = None
|
|
131019
|
+
return {"agent": self._user_id(), "ide": str(account.get("user_id", "")) if account else ""}
|
|
131020
|
+
|
|
131021
|
+
@staticmethod
|
|
131022
|
+
def _evolution_revision(payload: dict) -> int:
|
|
131023
|
+
revision = payload.get("revision")
|
|
131024
|
+
if isinstance(revision, bool) or not isinstance(revision, int) or revision < 1:
|
|
131025
|
+
raise LiquidKernelError("config_revision_required", "load and save the evolution policy before starting; revision is required", 409)
|
|
131026
|
+
return revision
|
|
131027
|
+
|
|
129396
131028
|
def _session_mgr(self) -> SessionManager:
|
|
129397
131029
|
return self.app.manager_for_user(self._user_id())
|
|
129398
131030
|
|
|
@@ -129685,6 +131317,14 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
129685
131317
|
if not self._require_admin(query):
|
|
129686
131318
|
return
|
|
129687
131319
|
return self._send_json(self.app.admin_config_payload())
|
|
131320
|
+
if path == "/api/admin/evolution/models":
|
|
131321
|
+
if not self._require_admin():
|
|
131322
|
+
return
|
|
131323
|
+
try:
|
|
131324
|
+
return self._send_json(self.app._evolution_models().catalog(
|
|
131325
|
+
self._evolution_model_context(), self.app.liquid_kernel.config()))
|
|
131326
|
+
except (LiquidKernelError, IDEAuthError) as exc:
|
|
131327
|
+
return self._send_json({"error": str(exc), "code": exc.code}, status=exc.status)
|
|
129688
131328
|
if path == "/api/admin/evolution":
|
|
129689
131329
|
if not self._require_admin(query):
|
|
129690
131330
|
return
|
|
@@ -129990,10 +131630,33 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
129990
131630
|
if path == "/api/ollama/models":
|
|
129991
131631
|
ollama_url = str((query.get("base_url", [""]) or [""])[0]).strip() or "http://127.0.0.1:11434"
|
|
129992
131632
|
try:
|
|
129993
|
-
|
|
129994
|
-
|
|
131633
|
+
model_records = probe_ollama_model_records(
|
|
131634
|
+
ollama_url,
|
|
131635
|
+
timeout=1.5,
|
|
131636
|
+
)
|
|
131637
|
+
models = [
|
|
131638
|
+
str(record.get("id", "")).strip()
|
|
131639
|
+
for record in model_records
|
|
131640
|
+
if isinstance(record, dict) and str(record.get("id", "")).strip()
|
|
131641
|
+
]
|
|
131642
|
+
return self._send_json(
|
|
131643
|
+
{
|
|
131644
|
+
"ok": True,
|
|
131645
|
+
"models": models,
|
|
131646
|
+
"model_records": model_records,
|
|
131647
|
+
"base_url": ollama_url,
|
|
131648
|
+
}
|
|
131649
|
+
)
|
|
129995
131650
|
except Exception as exc:
|
|
129996
|
-
return self._send_json(
|
|
131651
|
+
return self._send_json(
|
|
131652
|
+
{
|
|
131653
|
+
"ok": False,
|
|
131654
|
+
"models": [],
|
|
131655
|
+
"model_records": [],
|
|
131656
|
+
"error": str(exc)[:300],
|
|
131657
|
+
"base_url": ollama_url,
|
|
131658
|
+
}
|
|
131659
|
+
)
|
|
129997
131660
|
if path == "/api/openai_compat/models":
|
|
129998
131661
|
base_url = str((query.get("base_url", [""]) or [""])[0]).strip()
|
|
129999
131662
|
api_key = str((query.get("api_key", [""]) or [""])[0]).strip()
|
|
@@ -130010,7 +131673,60 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130010
131673
|
last_error = ""
|
|
130011
131674
|
notes: list[str] = []
|
|
130012
131675
|
probe_headers = openai_compat_probe_headers(provider, api_key)
|
|
130013
|
-
|
|
131676
|
+
model_urls = (
|
|
131677
|
+
[anthropic_model_list_url(normalized_base)]
|
|
131678
|
+
if provider == "anthropic"
|
|
131679
|
+
else openai_compat_model_list_urls(normalized_base, provider)
|
|
131680
|
+
)
|
|
131681
|
+
# Anthropic's Models API is cursor paginated. Fetch the complete
|
|
131682
|
+
# catalog so importing a provider gets every available model.
|
|
131683
|
+
if provider == "anthropic" and model_urls and model_urls[0]:
|
|
131684
|
+
models_url = model_urls[0]
|
|
131685
|
+
all_records: list[dict] = []
|
|
131686
|
+
seen_ids: set[str] = set()
|
|
131687
|
+
after_id = ""
|
|
131688
|
+
for _page in range(20):
|
|
131689
|
+
page_url = f"{models_url}{'&' if '?' in models_url else '?'}limit=1000"
|
|
131690
|
+
if after_id:
|
|
131691
|
+
page_url += "&after_id=" + quote(after_id, safe="")
|
|
131692
|
+
try:
|
|
131693
|
+
req = urllib.request.Request(page_url, method="GET")
|
|
131694
|
+
for hk, hv in probe_headers.items():
|
|
131695
|
+
if str(hk or "").strip() and str(hv or "").strip():
|
|
131696
|
+
req.add_header(str(hk), str(hv))
|
|
131697
|
+
with urlopen(req, timeout=8) as resp:
|
|
131698
|
+
payload = json.loads(resp.read().decode("utf-8", errors="replace"))
|
|
131699
|
+
reachable = True
|
|
131700
|
+
page_records = extract_openai_compat_model_records(payload)
|
|
131701
|
+
for record in page_records:
|
|
131702
|
+
mid = str(record.get("id", "") or "").strip()
|
|
131703
|
+
if mid and mid not in seen_ids:
|
|
131704
|
+
seen_ids.add(mid)
|
|
131705
|
+
all_records.append(record)
|
|
131706
|
+
if not isinstance(payload, dict) or not payload.get("has_more"):
|
|
131707
|
+
break
|
|
131708
|
+
next_after = str(payload.get("last_id", "") or "").strip()
|
|
131709
|
+
if not next_after or next_after == after_id:
|
|
131710
|
+
break
|
|
131711
|
+
after_id = next_after
|
|
131712
|
+
except urllib.error.HTTPError as exc:
|
|
131713
|
+
reachable = int(getattr(exc, "code", 0) or 0) in {200, 401, 403, 404, 405}
|
|
131714
|
+
last_error = f"HTTP {int(getattr(exc, 'code', 0) or 0)}"
|
|
131715
|
+
break
|
|
131716
|
+
except Exception as exc:
|
|
131717
|
+
last_error = trim(str(exc), 300)
|
|
131718
|
+
break
|
|
131719
|
+
if all_records:
|
|
131720
|
+
return self._send_json({
|
|
131721
|
+
"ok": True, "reachable": True, "provider": provider,
|
|
131722
|
+
"models": [str(r.get("id", "")).strip() for r in all_records],
|
|
131723
|
+
"model_records": [
|
|
131724
|
+
{"id": str(r.get("id", "")).strip(), "capabilities": dict(r.get("capabilities", {}))}
|
|
131725
|
+
for r in all_records
|
|
131726
|
+
],
|
|
131727
|
+
"base_url": normalized_base, "scanned_url": models_url,
|
|
131728
|
+
})
|
|
131729
|
+
for models_url in model_urls:
|
|
130014
131730
|
try:
|
|
130015
131731
|
req = urllib.request.Request(models_url, method="GET")
|
|
130016
131732
|
for hk, hv in probe_headers.items():
|
|
@@ -130023,7 +131739,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130023
131739
|
payload = json.loads(body_text)
|
|
130024
131740
|
except Exception:
|
|
130025
131741
|
payload = {}
|
|
130026
|
-
|
|
131742
|
+
model_records = extract_openai_compat_model_records(payload)
|
|
131743
|
+
model_ids = [
|
|
131744
|
+
str(record.get("id", "")).strip()
|
|
131745
|
+
for record in model_records
|
|
131746
|
+
if isinstance(record, dict) and str(record.get("id", "")).strip()
|
|
131747
|
+
]
|
|
130027
131748
|
if model_ids:
|
|
130028
131749
|
return self._send_json(
|
|
130029
131750
|
{
|
|
@@ -130031,6 +131752,17 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130031
131752
|
"reachable": True,
|
|
130032
131753
|
"provider": provider,
|
|
130033
131754
|
"models": model_ids,
|
|
131755
|
+
"model_records": [
|
|
131756
|
+
{
|
|
131757
|
+
"id": model_id,
|
|
131758
|
+
"capabilities": dict(
|
|
131759
|
+
model_records[index].get("capabilities", {})
|
|
131760
|
+
)
|
|
131761
|
+
if isinstance(model_records[index], dict)
|
|
131762
|
+
else {},
|
|
131763
|
+
}
|
|
131764
|
+
for index, model_id in enumerate(model_ids)
|
|
131765
|
+
],
|
|
130034
131766
|
"base_url": normalized_base,
|
|
130035
131767
|
"scanned_url": models_url,
|
|
130036
131768
|
}
|
|
@@ -130049,7 +131781,12 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130049
131781
|
payload = json.loads(body_text) if body_text else {}
|
|
130050
131782
|
except Exception:
|
|
130051
131783
|
payload = {}
|
|
130052
|
-
|
|
131784
|
+
model_records = extract_openai_compat_model_records(payload)
|
|
131785
|
+
model_ids = [
|
|
131786
|
+
str(record.get("id", "")).strip()
|
|
131787
|
+
for record in model_records
|
|
131788
|
+
if isinstance(record, dict) and str(record.get("id", "")).strip()
|
|
131789
|
+
]
|
|
130053
131790
|
if model_ids:
|
|
130054
131791
|
return self._send_json(
|
|
130055
131792
|
{
|
|
@@ -130057,6 +131794,17 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130057
131794
|
"reachable": True,
|
|
130058
131795
|
"provider": provider,
|
|
130059
131796
|
"models": model_ids,
|
|
131797
|
+
"model_records": [
|
|
131798
|
+
{
|
|
131799
|
+
"id": model_id,
|
|
131800
|
+
"capabilities": dict(
|
|
131801
|
+
model_records[index].get("capabilities", {})
|
|
131802
|
+
)
|
|
131803
|
+
if isinstance(model_records[index], dict)
|
|
131804
|
+
else {},
|
|
131805
|
+
}
|
|
131806
|
+
for index, model_id in enumerate(model_ids)
|
|
131807
|
+
],
|
|
130060
131808
|
"base_url": normalized_base,
|
|
130061
131809
|
"scanned_url": models_url,
|
|
130062
131810
|
}
|
|
@@ -130089,6 +131837,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130089
131837
|
"reachable": True,
|
|
130090
131838
|
"provider": provider,
|
|
130091
131839
|
"models": [],
|
|
131840
|
+
"model_records": [],
|
|
130092
131841
|
"base_url": normalized_base,
|
|
130093
131842
|
"note": "endpoint reachable; no standard model list returned",
|
|
130094
131843
|
"error": last_error,
|
|
@@ -130101,6 +131850,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130101
131850
|
"reachable": False,
|
|
130102
131851
|
"provider": provider,
|
|
130103
131852
|
"models": [],
|
|
131853
|
+
"model_records": [],
|
|
130104
131854
|
"error": last_error or "unable to reach endpoint",
|
|
130105
131855
|
"base_url": normalized_base,
|
|
130106
131856
|
"attempts": notes[-6:],
|
|
@@ -130113,6 +131863,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130113
131863
|
"reachable": False,
|
|
130114
131864
|
"provider": provider,
|
|
130115
131865
|
"models": [],
|
|
131866
|
+
"model_records": [],
|
|
130116
131867
|
"error": str(exc)[:300],
|
|
130117
131868
|
"base_url": extract_base_url(base_url),
|
|
130118
131869
|
}
|
|
@@ -130362,6 +132113,68 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130362
132113
|
return self._auth_error(exc)
|
|
130363
132114
|
except Exception:
|
|
130364
132115
|
return self._send_json({"error": "invalid authentication request", "code": "invalid_request"}, status=400)
|
|
132116
|
+
if path == "/api/admin/evolution/config":
|
|
132117
|
+
if not self._require_admin():
|
|
132118
|
+
return
|
|
132119
|
+
payload = self._read_json()
|
|
132120
|
+
try:
|
|
132121
|
+
return self._send_json(self.app.liquid_kernel.save_config(
|
|
132122
|
+
payload.get("values", payload), expected_revision=self._evolution_revision(payload),
|
|
132123
|
+
context=self._evolution_model_context(),
|
|
132124
|
+
))
|
|
132125
|
+
except (LiquidKernelError, IDEAuthError) as exc:
|
|
132126
|
+
return self._send_json({"error": str(exc), "code": exc.code, "details": getattr(exc, "details", {})}, status=exc.status)
|
|
132127
|
+
except (ValueError, TypeError):
|
|
132128
|
+
return self._send_json({"error": "invalid evolution policy values", "code": "invalid_config"}, status=400)
|
|
132129
|
+
except (OSError, sqlite3.Error):
|
|
132130
|
+
return self._send_json({"error": "evolution configuration storage is unavailable", "code": "config_storage_unavailable"}, status=503)
|
|
132131
|
+
if path == "/api/admin/evolution/emergency-off":
|
|
132132
|
+
if not self._require_admin():
|
|
132133
|
+
return
|
|
132134
|
+
try:
|
|
132135
|
+
self._read_json()
|
|
132136
|
+
return self._send_json(self.app.liquid_kernel.emergency_off())
|
|
132137
|
+
except LiquidKernelError as exc:
|
|
132138
|
+
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
132139
|
+
if path == "/api/admin/evolution/runs":
|
|
132140
|
+
if not self._require_admin():
|
|
132141
|
+
return
|
|
132142
|
+
payload = self._read_json()
|
|
132143
|
+
try:
|
|
132144
|
+
return self._send_json(self.app.liquid_kernel.trigger("manual", expected_revision=self._evolution_revision(payload)), status=202)
|
|
132145
|
+
except LiquidKernelError as exc:
|
|
132146
|
+
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
132147
|
+
m_evolution_run_action = re.match(r"^/api/admin/evolution/runs/([^/]+)/(approve|reject|cancel)$", path)
|
|
132148
|
+
if m_evolution_run_action:
|
|
132149
|
+
if not self._require_admin():
|
|
132150
|
+
return
|
|
132151
|
+
payload = self._read_json()
|
|
132152
|
+
try:
|
|
132153
|
+
action = m_evolution_run_action.group(2)
|
|
132154
|
+
if action == "approve":
|
|
132155
|
+
out = self.app.liquid_kernel.approve(m_evolution_run_action.group(1))
|
|
132156
|
+
elif action == "reject":
|
|
132157
|
+
out = self.app.liquid_kernel.reject(m_evolution_run_action.group(1), str(payload.get("reason", "") or ""))
|
|
132158
|
+
else:
|
|
132159
|
+
out = self.app.liquid_kernel.cancel(m_evolution_run_action.group(1))
|
|
132160
|
+
return self._send_json(out)
|
|
132161
|
+
except LiquidKernelError as exc:
|
|
132162
|
+
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
132163
|
+
m_evolution_version_action = re.match(r"^/api/admin/evolution/versions/([^/]+)/(promote|rollback)$", path)
|
|
132164
|
+
if m_evolution_version_action:
|
|
132165
|
+
if not self._require_admin():
|
|
132166
|
+
return
|
|
132167
|
+
payload = self._read_json()
|
|
132168
|
+
try:
|
|
132169
|
+
if m_evolution_version_action.group(2) == "promote":
|
|
132170
|
+
out = self.app.liquid_kernel.registry.promote(m_evolution_version_action.group(1))
|
|
132171
|
+
else:
|
|
132172
|
+
out = self.app.liquid_kernel.registry.rollback(
|
|
132173
|
+
m_evolution_version_action.group(1), reason=str(payload.get("reason", "manual rollback") or "manual rollback")
|
|
132174
|
+
)
|
|
132175
|
+
return self._send_json(out)
|
|
132176
|
+
except LiquidKernelError as exc:
|
|
132177
|
+
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130365
132178
|
mgr = self._session_mgr()
|
|
130366
132179
|
m_process_stop = re.match(r"^/api/processes/([^/]+)/stop$", path)
|
|
130367
132180
|
if m_process_stop:
|
|
@@ -130452,63 +132265,6 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130452
132265
|
expected_revision=str(payload.get("revision", "") or ""),
|
|
130453
132266
|
)
|
|
130454
132267
|
return self._send_json(out, status=200 if out.get("ok") else (409 if out.get("conflict") else 400))
|
|
130455
|
-
if path == "/api/admin/evolution/config":
|
|
130456
|
-
if not self._require_admin():
|
|
130457
|
-
return
|
|
130458
|
-
payload = self._read_json()
|
|
130459
|
-
try:
|
|
130460
|
-
return self._send_json(self.app.liquid_kernel.save_config(
|
|
130461
|
-
payload.get("values", payload), expected_revision=int(payload.get("revision", 0) or 0)
|
|
130462
|
-
))
|
|
130463
|
-
except LiquidKernelError as exc:
|
|
130464
|
-
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130465
|
-
if path == "/api/admin/evolution/emergency-off":
|
|
130466
|
-
if not self._require_admin():
|
|
130467
|
-
return
|
|
130468
|
-
try:
|
|
130469
|
-
self._read_json()
|
|
130470
|
-
return self._send_json(self.app.liquid_kernel.emergency_off())
|
|
130471
|
-
except LiquidKernelError as exc:
|
|
130472
|
-
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130473
|
-
if path == "/api/admin/evolution/runs":
|
|
130474
|
-
if not self._require_admin():
|
|
130475
|
-
return
|
|
130476
|
-
payload = self._read_json()
|
|
130477
|
-
try:
|
|
130478
|
-
return self._send_json(self.app.liquid_kernel.trigger(str(payload.get("trigger", "manual") or "manual")), status=202)
|
|
130479
|
-
except LiquidKernelError as exc:
|
|
130480
|
-
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130481
|
-
m_evolution_run_action = re.match(r"^/api/admin/evolution/runs/([^/]+)/(approve|reject|cancel)$", path)
|
|
130482
|
-
if m_evolution_run_action:
|
|
130483
|
-
if not self._require_admin():
|
|
130484
|
-
return
|
|
130485
|
-
payload = self._read_json()
|
|
130486
|
-
try:
|
|
130487
|
-
action = m_evolution_run_action.group(2)
|
|
130488
|
-
if action == "approve":
|
|
130489
|
-
out = self.app.liquid_kernel.approve(m_evolution_run_action.group(1))
|
|
130490
|
-
elif action == "reject":
|
|
130491
|
-
out = self.app.liquid_kernel.reject(m_evolution_run_action.group(1), str(payload.get("reason", "") or ""))
|
|
130492
|
-
else:
|
|
130493
|
-
out = self.app.liquid_kernel.cancel(m_evolution_run_action.group(1))
|
|
130494
|
-
return self._send_json(out)
|
|
130495
|
-
except LiquidKernelError as exc:
|
|
130496
|
-
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130497
|
-
m_evolution_version_action = re.match(r"^/api/admin/evolution/versions/([^/]+)/(promote|rollback)$", path)
|
|
130498
|
-
if m_evolution_version_action:
|
|
130499
|
-
if not self._require_admin():
|
|
130500
|
-
return
|
|
130501
|
-
payload = self._read_json()
|
|
130502
|
-
try:
|
|
130503
|
-
if m_evolution_version_action.group(2) == "promote":
|
|
130504
|
-
out = self.app.liquid_kernel.registry.promote(m_evolution_version_action.group(1))
|
|
130505
|
-
else:
|
|
130506
|
-
out = self.app.liquid_kernel.registry.rollback(
|
|
130507
|
-
m_evolution_version_action.group(1), reason=str(payload.get("reason", "manual rollback") or "manual rollback")
|
|
130508
|
-
)
|
|
130509
|
-
return self._send_json(out)
|
|
130510
|
-
except LiquidKernelError as exc:
|
|
130511
|
-
return self._send_json({"error": str(exc), "code": exc.code, "details": exc.details}, status=exc.status)
|
|
130512
132268
|
if path == "/api/admin/config/sync-active":
|
|
130513
132269
|
if not self._require_admin():
|
|
130514
132270
|
return
|
|
@@ -130731,7 +132487,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130731
132487
|
if not model:
|
|
130732
132488
|
return self._send_json({"error": "model required"}, status=400)
|
|
130733
132489
|
try:
|
|
130734
|
-
return self._send_json(mgr.set_runtime_model(model, None))
|
|
132490
|
+
return self._send_json(mgr.set_runtime_model(model, None, normalize_model_runtime_settings(payload)))
|
|
130735
132491
|
except Exception as exc:
|
|
130736
132492
|
return self._send_json({"error": str(exc)}, status=400)
|
|
130737
132493
|
if path == "/api/config/language":
|
|
@@ -130788,6 +132544,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130788
132544
|
if not selection:
|
|
130789
132545
|
return self._send_json({"error": "selection required"}, status=400)
|
|
130790
132546
|
model_override = payload.get("model_override")
|
|
132547
|
+
runtime_settings = normalize_model_runtime_settings(payload)
|
|
130791
132548
|
if bool(getattr(sess, "running", False)):
|
|
130792
132549
|
try:
|
|
130793
132550
|
sess._queue_deferred_runtime_update(
|
|
@@ -130795,6 +132552,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130795
132552
|
{
|
|
130796
132553
|
"selection": selection,
|
|
130797
132554
|
"model_override": model_override if isinstance(model_override, str) else "",
|
|
132555
|
+
"settings": runtime_settings,
|
|
130798
132556
|
},
|
|
130799
132557
|
)
|
|
130800
132558
|
except Exception as exc:
|
|
@@ -130806,8 +132564,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130806
132564
|
)
|
|
130807
132565
|
return self._send_json(queued)
|
|
130808
132566
|
try:
|
|
130809
|
-
out = sess.set_runtime_selection(selection, model_override if isinstance(model_override, str) else None)
|
|
130810
|
-
mgr._sync_from_session(sess, apply_to_all=
|
|
132567
|
+
out = sess.set_runtime_selection(selection, model_override if isinstance(model_override, str) else None, settings=runtime_settings)
|
|
132568
|
+
mgr._sync_from_session(sess, apply_to_all=True)
|
|
130811
132569
|
except Exception as exc:
|
|
130812
132570
|
return self._send_json({"error": str(exc)}, status=400)
|
|
130813
132571
|
return self._send_json(out)
|
|
@@ -130914,7 +132672,7 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
130914
132672
|
meta = sess.add_upload(filename, raw, mime)
|
|
130915
132673
|
if isinstance(meta.get("model_catalog"), dict) and not bool(meta.get("model_catalog", {}).get("queued")):
|
|
130916
132674
|
try:
|
|
130917
|
-
mgr._sync_from_session(sess, apply_to_all=
|
|
132675
|
+
mgr._sync_from_session(sess, apply_to_all=True)
|
|
130918
132676
|
except Exception:
|
|
130919
132677
|
pass
|
|
130920
132678
|
return self._send_json(meta, status=201)
|
|
@@ -131708,7 +133466,7 @@ class SkillsHandler(BaseHTTPRequestHandler):
|
|
|
131708
133466
|
if not selection:
|
|
131709
133467
|
return self._send_json({"error": "selection required"}, status=400)
|
|
131710
133468
|
try:
|
|
131711
|
-
return self._send_json(mgr.set_runtime_model(selection, None))
|
|
133469
|
+
return self._send_json(mgr.set_runtime_model(selection, None, normalize_model_runtime_settings(payload)))
|
|
131712
133470
|
except Exception as exc:
|
|
131713
133471
|
return self._send_json({"error": str(exc)}, status=400)
|
|
131714
133472
|
if path == "/api/skillslab/language":
|
|
@@ -133859,6 +135617,7 @@ class IdeHandler(BaseHTTPRequestHandler):
|
|
|
133859
135617
|
m.group(1),
|
|
133860
135618
|
str(payload.get("selection", payload.get("model", "")) or ""),
|
|
133861
135619
|
client_ip=self._client_ip(),
|
|
135620
|
+
settings=normalize_model_runtime_settings(payload),
|
|
133862
135621
|
)
|
|
133863
135622
|
)
|
|
133864
135623
|
except Exception as exc:
|
|
@@ -134796,6 +136555,85 @@ class McpServiceHandler(BaseHTTPRequestHandler):
|
|
|
134796
136555
|
|
|
134797
136556
|
# Bootstrap sequence: load configuration, initialize shared application state,
|
|
134798
136557
|
# and expose the HTTP service plus background runtime workers.
|
|
136558
|
+
def sqlite_failure_diagnostics(exc: BaseException, database=None) -> dict:
|
|
136559
|
+
"""Read-only process/storage probes for server logs, never public responses."""
|
|
136560
|
+
details = {
|
|
136561
|
+
"pid": os.getpid(),
|
|
136562
|
+
"python": sys.version.split()[0],
|
|
136563
|
+
"sqlite": sqlite3.sqlite_version,
|
|
136564
|
+
"script": os.path.abspath(__file__),
|
|
136565
|
+
"sqlite_errorcode": getattr(exc, "sqlite_errorcode", None),
|
|
136566
|
+
"sqlite_errorname": getattr(exc, "sqlite_errorname", ""),
|
|
136567
|
+
"operation": getattr(exc, "sqlite_operation", "query"),
|
|
136568
|
+
}
|
|
136569
|
+
try:
|
|
136570
|
+
import resource
|
|
136571
|
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
136572
|
+
details["fd_limit_soft"], details["fd_limit_hard"] = soft, hard
|
|
136573
|
+
except (ImportError, OSError, ValueError):
|
|
136574
|
+
pass
|
|
136575
|
+
try:
|
|
136576
|
+
fd = os.open(os.devnull, os.O_RDONLY)
|
|
136577
|
+
except OSError as probe:
|
|
136578
|
+
details["fd_probe_errno"] = probe.errno
|
|
136579
|
+
else:
|
|
136580
|
+
os.close(fd)
|
|
136581
|
+
details["fd_probe_errno"] = 0
|
|
136582
|
+
fd_directory = "/dev/fd" if sys.platform == "darwin" else "/proc/self/fd"
|
|
136583
|
+
if os.name == "posix":
|
|
136584
|
+
try:
|
|
136585
|
+
names = os.listdir(fd_directory)
|
|
136586
|
+
count = 0
|
|
136587
|
+
for name in names:
|
|
136588
|
+
try:
|
|
136589
|
+
os.fstat(int(name))
|
|
136590
|
+
count += 1
|
|
136591
|
+
except (OSError, ValueError):
|
|
136592
|
+
continue
|
|
136593
|
+
details["open_fds"] = count
|
|
136594
|
+
except OSError as probe:
|
|
136595
|
+
details["fd_count_errno"] = probe.errno
|
|
136596
|
+
path = getattr(exc, "sqlite_database_path", None) or database
|
|
136597
|
+
if path:
|
|
136598
|
+
path = os.path.abspath(os.fspath(path))
|
|
136599
|
+
details["database"] = path
|
|
136600
|
+
states = {}
|
|
136601
|
+
for label, target in (
|
|
136602
|
+
("parent", os.path.dirname(path)),
|
|
136603
|
+
("database", path),
|
|
136604
|
+
("wal", path + "-wal"),
|
|
136605
|
+
("shm", path + "-shm"),
|
|
136606
|
+
):
|
|
136607
|
+
try:
|
|
136608
|
+
info = os.stat(target)
|
|
136609
|
+
states[label] = {
|
|
136610
|
+
"kind": "directory" if stat.S_ISDIR(info.st_mode) else "file",
|
|
136611
|
+
"mode": oct(stat.S_IMODE(info.st_mode)),
|
|
136612
|
+
"bytes": info.st_size,
|
|
136613
|
+
}
|
|
136614
|
+
except OSError as probe:
|
|
136615
|
+
states[label] = {"errno": probe.errno}
|
|
136616
|
+
details["paths"] = states
|
|
136617
|
+
# No O_CREAT: a missing authentication/collaboration DB must stay missing.
|
|
136618
|
+
try:
|
|
136619
|
+
fd = os.open(path, os.O_RDWR)
|
|
136620
|
+
except OSError as probe:
|
|
136621
|
+
details["database_open_errno"] = probe.errno
|
|
136622
|
+
else:
|
|
136623
|
+
os.close(fd)
|
|
136624
|
+
details["database_open_errno"] = 0
|
|
136625
|
+
errors = {details.get(key) for key in ("fd_probe_errno", "fd_count_errno", "database_open_errno")}
|
|
136626
|
+
if errors.intersection({errno.EMFILE, errno.ENFILE}):
|
|
136627
|
+
details["cause"] = "file_descriptor_exhaustion"
|
|
136628
|
+
elif details.get("database_open_errno") in {errno.EACCES, errno.EPERM, errno.EROFS}:
|
|
136629
|
+
details["cause"] = "storage_access_denied"
|
|
136630
|
+
elif details.get("database_open_errno") in {errno.ENOENT, errno.ENOTDIR}:
|
|
136631
|
+
details["cause"] = "storage_path_unavailable"
|
|
136632
|
+
else:
|
|
136633
|
+
details["cause"] = "undetermined"
|
|
136634
|
+
return details
|
|
136635
|
+
|
|
136636
|
+
|
|
134799
136637
|
def collaboration_file_watcher_loop(
|
|
134800
136638
|
app,
|
|
134801
136639
|
stop_event,
|
|
@@ -134861,6 +136699,14 @@ def collaboration_file_watcher_loop(
|
|
|
134861
136699
|
"[collaboration] file watcher degraded: "
|
|
134862
136700
|
f"{trim(str(exc), 240)}; failures={failures}; retry_in={retry_delay:.1f}s"
|
|
134863
136701
|
)
|
|
136702
|
+
if isinstance(exc, sqlite3.DatabaseError):
|
|
136703
|
+
try:
|
|
136704
|
+
diagnostics = sqlite_failure_diagnostics(
|
|
136705
|
+
exc, getattr(app.collaboration, "db_path", None)
|
|
136706
|
+
)
|
|
136707
|
+
log("[collaboration] storage diagnostics: " + json.dumps(diagnostics, ensure_ascii=True))
|
|
136708
|
+
except Exception as diagnostic_error:
|
|
136709
|
+
log(f"[collaboration] storage diagnostics unavailable: {type(diagnostic_error).__name__}")
|
|
134864
136710
|
|
|
134865
136711
|
|
|
134866
136712
|
def collaboration_watcher_health(app) -> dict:
|