meltygui 0.1.0__py3-none-any.whl

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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,592 @@
1
+ """Codex implements the chat mapping. All protocol interpretation lives here."""
2
+ import time
3
+ from pathlib import Path
4
+
5
+ from meltygui.chat.codex_settings import ThreadSettingsReader
6
+ from meltygui.chat.codex_settings import effective_settings
7
+ from meltygui.chat.codex_settings import fast_service_tier
8
+ from meltygui.chat.codex_settings import model_service_tiers
9
+
10
+ from meltygui.chat.chat_proxy import Chat
11
+ from meltygui.chat.chat_proxy import ChatProxy
12
+ from meltygui.chat.chat_proxy import epoch_seconds
13
+ from meltygui.chat.messages import from_codex
14
+ from meltygui.chat.messages import upsert
15
+ from meltygui.chat.messages import set_text
16
+ from meltygui.chat.messages import AssistantMessage
17
+ from meltygui.chat.messages import PlanMessage
18
+ from meltygui.chat.messages import CommandExecution
19
+ from meltygui.chat.messages import ToolOutput
20
+ from meltygui.chat.messages import ReasoningMessage
21
+ from meltygui.chat.messages import FileChange
22
+ from meltygui.chat.messages import McpToolCall
23
+ from meltygui.chat.messages import FileTags
24
+ from meltygui.chat.messages import diff_counts
25
+ from meltygui.chat.messages import match_file
26
+ from meltygui.chat.codex_transport import CodexTransport
27
+ from meltygui.chat.writer_locks import codex_writer_locks
28
+ from meltygui.chat.writer_locks import lock_message
29
+ from meltygui.completion.providers.codex_accounts import account_home
30
+
31
+
32
+ class CodexChats(ChatProxy):
33
+ inherits_defaults = True
34
+
35
+ def __init__(self, account_id, metadata=None, wake=None, transport_factory=None):
36
+ from meltygui.chat.activity import UserMessageTimes
37
+ self.user_times = UserMessageTimes("codex")
38
+ self.settings_reader = ThreadSettingsReader()
39
+ self.source_home = account_home(account_id)
40
+ self.transport = None
41
+ self.writers = {}
42
+ self.refresh_pending = False
43
+ self.config_defaults = {}
44
+ self.config_pending = set()
45
+ self.transport_factory = transport_factory or CodexTransport
46
+ super().__init__(account_id, metadata, wake)
47
+
48
+ def connect(self):
49
+ self.transport = self._open_transport()
50
+ account = self.transport.request("account/read", {"refreshToken": False}).get("account")
51
+ if not account:
52
+ raise RuntimeError("Sign in to this account in Internet Accounts first")
53
+ cursor = None
54
+ while not self.closed:
55
+ result = self.transport.request("thread/list", {
56
+ "limit": 100, "cursor": cursor, "sortKey": "updated_at",
57
+ "sourceKinds": ["appServer", "cli", "vscode", "exec"]})
58
+ self.publish("listing", self._listing_sizes(result.get("data", [])))
59
+ cursor = result.get("nextCursor")
60
+ if not cursor:
61
+ break
62
+ models = []
63
+ cursor = None
64
+ try:
65
+ while True:
66
+ result = self.transport.request("model/list", {"limit": 100, "cursor": cursor})
67
+ models.extend(result.get("data", []))
68
+ cursor = result.get("nextCursor")
69
+ if not cursor:
70
+ break
71
+ except Exception:
72
+ pass # Default model remains usable with old servers.
73
+ self.publish("models", models)
74
+ self._read_defaults(self.transport, "")
75
+ self.publish("writer_locks", codex_writer_locks(self.source_home))
76
+ self.publish("ready", None)
77
+
78
+ def defaults_for(self, project):
79
+ """Queue project-aware config reads without blocking the render thread."""
80
+ # Existing backend instances survive source hotswaps.
81
+ self.__dict__.setdefault("config_defaults", {})
82
+ self.__dict__.setdefault("config_pending", set())
83
+ if project not in self.config_defaults and project not in self.config_pending:
84
+ self.config_pending.add(project)
85
+ self.submit("defaults", project)
86
+ return self.config_defaults.get(project, self.config_defaults.get("", {}))
87
+
88
+ def _read_defaults(self, server, project):
89
+ config = server.request("config/read", {
90
+ "includeLayers": False, **({"cwd": project} if project else {})}).get("config", {})
91
+ self.publish("defaults", (project, config))
92
+ return config
93
+
94
+ def _thread_options(self, server, key, project):
95
+ config = self._read_defaults(server, project)
96
+ settings = self.known[key].metadata
97
+ effective = effective_settings(self.known[key], config, self.default_model)
98
+ options = {"cwd": project}
99
+ # Older Melty versions stamped catalog defaults into settings without
100
+ # an explicit-selection marker. Those should not override config.toml.
101
+ model = effective.get("model")
102
+ if model:
103
+ options["model"] = model
104
+ permissions = settings.get("permissions")
105
+ if permissions in ("ask", "full"):
106
+ options.update(approvalPolicy="never" if permissions == "full" else "on-request",
107
+ sandbox="danger-full-access" if permissions == "full" else "workspace-write")
108
+ else:
109
+ if config.get("approval_policy") is not None:
110
+ options["approvalPolicy"] = config["approval_policy"]
111
+ if config.get("sandbox_mode") is not None:
112
+ options["sandbox"] = config["sandbox_mode"]
113
+ effort = effective.get("effort")
114
+ supported = getattr(self, "model_efforts", {}).get(model)
115
+ if supported is not None and effort not in supported:
116
+ effort = None
117
+ if effort in (None, "", "default"):
118
+ effort = config.get("model_reasoning_effort")
119
+ if effort:
120
+ options["config"] = {"model_reasoning_effort": effort}
121
+ if "service_tier" in effective:
122
+ tier = effective["service_tier"]
123
+ tiers = getattr(self, "model_service_tiers", {}).get(model)
124
+ if tier in ("fast", "priority"):
125
+ tier = fast_service_tier(self, model) or tier
126
+ if tier in ("default", "") or tiers is not None and tier not in {entry["id"] for entry in tiers}:
127
+ tier = None
128
+ options["serviceTier"] = tier
129
+ return options
130
+
131
+ def _listing_sizes(self, threads):
132
+ # Filesystem data is read from the backend worker, never while drawing.
133
+ for thread in threads:
134
+ thread["last_user_at"] = self.user_times.read(thread.get("path"))
135
+ if not hasattr(self, "settings_reader"):
136
+ self.settings_reader = ThreadSettingsReader()
137
+ thread.update(self.settings_reader.read(thread.get("path")))
138
+ try:
139
+ thread["size_bytes"] = Path(thread["path"]).stat().st_size if thread.get("path") else None
140
+ except OSError:
141
+ thread["size_bytes"] = None
142
+ return threads
143
+
144
+ def _open_transport(self, remote_id=None):
145
+ from meltygui.core.runtime.toggles import Toggles
146
+ holder = {}
147
+ def event(value):
148
+ if not holder.get("closing"):
149
+ if value.get("method") == "transport/error" and remote_id:
150
+ value = {**value, "params": {**value.get("params", {}), "threadId": remote_id}}
151
+ if value.get("method") == "thread/settings/updated":
152
+ value = {**value, "settings_observed_at": time.time()}
153
+ self.publish("event", value)
154
+ server = self.transport_factory(self.source_home, executable=Toggles.InternetAccounts.codex_bin,
155
+ timeout=Toggles.InternetAccounts.codex_request_timeout_s, on_event=event)
156
+ server._melty_holder = holder
157
+ return server
158
+
159
+ def _close_transport(self, server):
160
+ server._melty_holder["closing"] = True
161
+ server.close()
162
+
163
+ def disconnect(self):
164
+ for server in list(self.writers.values()):
165
+ self._close_transport(server)
166
+ self.writers.clear()
167
+ if self.transport:
168
+ self._close_transport(self.transport)
169
+
170
+ def refresh(self):
171
+ if not self.refresh_pending:
172
+ self.refresh_pending = True
173
+ self.submit("refresh")
174
+
175
+ def execute(self, operation, *args):
176
+ server = self.transport
177
+ if operation == "defaults":
178
+ self._read_defaults(server, args[0])
179
+ elif operation == "fork":
180
+ key, remote_id, project, title = args
181
+ writer = self._open_transport()
182
+ try:
183
+ result = writer.request("thread/fork", {"threadId": remote_id, "cwd": project})
184
+ fork_id = result["thread"]["id"]
185
+ writer.request("thread/name/set", {"threadId": fork_id, "name": title})
186
+ self.publish("forked", (key, fork_id))
187
+ finally:
188
+ self._close_transport(writer)
189
+ elif operation == "refresh":
190
+ cursor = None
191
+ while not self.closed:
192
+ result = server.request("thread/list", {"limit": 100, "cursor": cursor,
193
+ "sortKey": "updated_at", "sourceKinds": ["appServer", "cli", "vscode", "exec"]})
194
+ self.publish("listing", self._listing_sizes(result.get("data", [])))
195
+ cursor = result.get("nextCursor")
196
+ if not cursor:
197
+ break
198
+ locks = codex_writer_locks(self.source_home)
199
+ self.publish("writer_locks", {key: owner for key, owner in locks.items() if key not in self.writers} if locks is not None else None)
200
+ self.publish("refreshed", None)
201
+ elif operation == "release":
202
+ remote_id, = args
203
+ writer = self.writers.pop(remote_id, None)
204
+ if writer:
205
+ self._close_transport(writer)
206
+ elif operation == "hydrate":
207
+ key, = args
208
+ chat = self.known.get(key) or dict.get(self, key)
209
+ if chat:
210
+ result = server.request("thread/read", {"threadId": chat.remote_id, "includeTurns": True})
211
+ self.publish("hydrated", (key, self._history(result["thread"], chat["project"])))
212
+ elif operation == "create":
213
+ key, project, title = args
214
+ writer = self._open_transport()
215
+ try:
216
+ options = self._thread_options(writer, key, project)
217
+ observed_at = time.time()
218
+ result = writer.request("thread/start", options)
219
+ writer.request("thread/name/set", {"threadId": result["thread"]["id"], "name": title})
220
+ self.writers[result["thread"]["id"]] = writer
221
+ self.publish("settings", (key, result, observed_at))
222
+ self.publish("created", (key, result["thread"]))
223
+ except Exception:
224
+ self._close_transport(writer)
225
+ raise
226
+ elif operation == "archive":
227
+ key, remote_id = args
228
+ try:
229
+ self.writers.get(remote_id, server).request("thread/archive", {"threadId": remote_id})
230
+ except Exception as error:
231
+ # Unsent threads have no rollout file to archive yet.
232
+ chat = self.known.get(key)
233
+ if (chat is None or not chat.loaded or chat["messages"] or chat["running"]
234
+ or "no rollout found for thread id" not in str(error).lower()):
235
+ raise
236
+ writer = self.writers.pop(remote_id, None)
237
+ if writer:
238
+ self._close_transport(writer)
239
+ self.publish("archived", key)
240
+ elif operation == "rename":
241
+ key, remote_id, title = args
242
+ server.request("thread/name/set", {"threadId": remote_id, "name": title})
243
+ self.publish("renamed", (key, title))
244
+ elif operation == "send":
245
+ key, remote_id, message_id, text, resumed = args
246
+ writer = self.writers.get(remote_id) or self._open_transport(remote_id)
247
+ self.writers[remote_id] = writer
248
+ try:
249
+ options = self._thread_options(writer, key, self.known[key]["project"])
250
+ model = options.get("model")
251
+ observed_at = time.time()
252
+ resumed_thread = writer.request("thread/resume", {"threadId": remote_id, **options})
253
+ self.publish("settings", (key, resumed_thread, observed_at))
254
+ result = writer.request("turn/start", {"threadId": remote_id,
255
+ **({"model": model} if model else {}),
256
+ **({"effort": options["config"]["model_reasoning_effort"]} if "config" in options else {}),
257
+ **({"serviceTier": options["serviceTier"]} if "serviceTier" in options else {}),
258
+ "input": [{"type": "text", "text": text}]})
259
+ self.publish("sent", (key, message_id, result["turn"]["id"]))
260
+ except Exception:
261
+ self.writers.pop(remote_id, None)
262
+ self._close_transport(writer)
263
+ raise
264
+ elif operation == "interrupt":
265
+ key, remote_id, turn_id = args
266
+ self.writers.get(remote_id, server).request("turn/interrupt", {"threadId": remote_id, "turnId": turn_id})
267
+ elif operation == "unsupported":
268
+ request_id, message, remote_id = args
269
+ self.writers.get(remote_id, server).answer(request_id, error={"code": -32601, "message": message})
270
+ elif operation == "answer":
271
+ key, request_id, answer = args
272
+ chat = self.known[key]
273
+ self.writers.get(chat.remote_id, server).answer(request_id, answer)
274
+ self.publish("answered", (key, request_id))
275
+
276
+ def receive(self, kind, value):
277
+ if kind == "defaults":
278
+ project, config = value
279
+ self.__dict__.setdefault("config_defaults", {})[project] = config
280
+ self.__dict__.setdefault("config_pending", set()).discard(project)
281
+ if not project and config.get("model"):
282
+ self.default_model = config["model"]
283
+ elif kind == "forked":
284
+ self.finish_fork(*value)
285
+ elif kind == "settings":
286
+ key, settings = value[:2]
287
+ chat = self.known.get(key)
288
+ if chat is not None:
289
+ self._apply_settings(chat, settings, value[2] if len(value) > 2 else None)
290
+ elif kind == "models":
291
+ self.model_service_tiers = {row["model"]: model_service_tiers(row)
292
+ for row in value if row.get("model")}
293
+ self.model_default_service_tiers = {row["model"]: row.get("defaultServiceTier")
294
+ for row in value if row.get("model")}
295
+ self.model_default_efforts = {row["model"]: row.get("defaultReasoningEffort")
296
+ for row in value if row.get("model")}
297
+ self.model_efforts = {row["model"]: tuple(option["reasoningEffort"]
298
+ for option in row.get("supportedReasoningEfforts", []) if option.get("reasoningEffort"))
299
+ for row in value if row.get("model")}
300
+ self.default_model = next((row["model"] for row in value
301
+ if row.get("isDefault") and row.get("model")), None)
302
+ self.models = {row.get("displayName") or row["model"]: row["model"]
303
+ for row in value if row.get("model") and not row.get("hidden")}
304
+ elif kind == "writer_locks":
305
+ if value is not None:
306
+ for chat in self.values():
307
+ locked = chat.remote_id in value
308
+ if (chat.get("locked") and not locked and chat.get("retryable_error")
309
+ and str(chat.error or "").startswith("This conversation is open for writing")):
310
+ chat.error = None
311
+ chat["lock_owner"] = value.get(chat.remote_id) if isinstance(value, dict) else None
312
+ chat["locked"] = locked
313
+ if locked and chat.get("retryable_error"):
314
+ chat.error = lock_message(chat["lock_owner"])
315
+ elif kind == "refreshed":
316
+ self.refresh_pending = False
317
+ elif kind == "listing":
318
+ for thread in value:
319
+ key = self.metadata.local_key(self.account_id, thread["id"])
320
+ if key not in self:
321
+ chat = Chat({"title": thread.get("name") or thread.get("preview") or "Conversation",
322
+ "project": thread.get("cwd") or "",
323
+ "created_at": epoch_seconds(thread.get("createdAt", thread.get("created_at"))),
324
+ "updated": epoch_seconds(thread.get("updatedAt", thread.get("updated_at")))},
325
+ remote_id=thread["id"])
326
+ chat["size_bytes"] = thread.get("size_bytes")
327
+ dict.__setitem__(self, key, chat)
328
+ self.known[key] = chat
329
+ else:
330
+ chat = dict.get(self, key)
331
+ chat["size_bytes"] = thread.get("size_bytes")
332
+ updated = epoch_seconds(thread.get("updatedAt", thread.get("updated_at")))
333
+ if thread.get("name") and chat["title"] == chat.saved_title and "rename" not in chat.inflight:
334
+ chat["title"] = chat.saved_title = thread["name"]
335
+ if updated > chat["updated"] and not chat["running"]:
336
+ chat["updated"] = updated
337
+ self.refresh_history(key)
338
+ chat = dict.get(self, key)
339
+ if thread.get("codex_settings_at", 0) >= chat.get("codex_settings_at", 0):
340
+ chat.update({field: thread[field] for field in ("codex_settings", "codex_settings_at") if field in thread})
341
+ # Session-naming jobs contain real model/assistant messages, so
342
+ # emptiness and a rounded 0.0 MB size cannot identify them.
343
+ preview = str(thread.get("preview") or "").lstrip()
344
+ chat["title_helper"] = preview.startswith("Name a chat session for the request below.")
345
+ chat["last_user_at"] = max(chat.get("last_user_at", 0), thread.get("last_user_at", 0))
346
+ self.metadata.apply(self.account_id, self)
347
+ elif kind == "ready":
348
+ self.loading = False
349
+ elif kind == "created":
350
+ key, thread = value
351
+ chat = self.known.get(key)
352
+ if chat:
353
+ chat.remote_id = thread["id"]
354
+ chat.inflight.discard("create")
355
+ # Persist remote identity even if the row was removed while creation ran.
356
+ self.metadata.conversation(self.account_id, chat["project"], key)["remote_id"] = chat.remote_id
357
+ chat.inflight.discard("archive")
358
+ elif kind == "hydrated":
359
+ key, history = value
360
+ chat = self.known.get(key)
361
+ if chat:
362
+ # A pre-hotswap worker may already have queued an old raw payload.
363
+ if "messages" not in history:
364
+ history = self._history(history, chat["project"])
365
+ # Disk history is authoritative; keep only the unsent local drafts.
366
+ pending = {identifier: message for identifier, message in chat["messages"].items()
367
+ if identifier not in chat.sent and message.get("role") == "user"}
368
+ chat["messages"].clear()
369
+ chat["messages"].update(history["messages"])
370
+ chat.update({field: history[field] for field in ("codex_settings", "codex_settings_at") if field in history
371
+ and history.get("codex_settings_at", 0) >= chat.get("codex_settings_at", 0)})
372
+ chat.command_scripts = getattr(history, "command_scripts", {})
373
+ chat.sent = set(chat["messages"])
374
+ chat["messages"].update(pending)
375
+ chat.loaded, chat.loading, chat.refreshing = True, False, False
376
+ elif kind == "archived":
377
+ self.known.pop(value, None)
378
+ elif kind == "renamed":
379
+ key, title = value
380
+ chat = self.known.get(key)
381
+ if chat:
382
+ chat.saved_title = title
383
+ chat.inflight.discard("rename")
384
+ elif kind == "sent":
385
+ key, message_id, turn_id = value
386
+ chat = self.known.get(key)
387
+ if chat:
388
+ chat["locked"] = False
389
+ chat.sent.add(message_id)
390
+ chat.resumed = False
391
+ chat.inflight.discard("send")
392
+ if chat["running"]:
393
+ chat.turn_id = turn_id
394
+ elif kind == "answered":
395
+ key, request_id = value
396
+ if key in self.known:
397
+ self.known[key]["requests"].pop(request_id, None)
398
+ elif kind == "failure":
399
+ operation, args, error = value
400
+ if operation == "defaults":
401
+ self.config_pending.discard(args[0])
402
+ self.models_error = "Could not read Codex defaults: " + error
403
+ return
404
+ if operation == "refresh":
405
+ self.refresh_pending = False
406
+ return
407
+ chat = self.known.get(args[0]) if args else None
408
+ if operation == "send" and chat is not None and "active writer" in error.lower():
409
+ _, _, message_id, text, _ = args
410
+ chat.error = lock_message(chat.get("lock_owner"))
411
+ chat["recovered_draft"] = text
412
+ chat["retryable_error"] = True
413
+ chat["locked"] = True
414
+ chat["messages"].pop(message_id, None)
415
+ chat.scanned = -1
416
+ chat.inflight.difference_update({"send", "interrupt", "interrupt_requested"})
417
+ chat.turn_id = None
418
+ chat["running"] = False
419
+ chat["updated"] = 0.0
420
+ chat.resumed = False
421
+ return
422
+ if operation != "hydrate":
423
+ self.error = error
424
+ self.loading = False
425
+ if chat:
426
+ chat.error = error
427
+ chat.loading = chat.refreshing = False
428
+ chat["running"] = False
429
+ if operation == "archive":
430
+ dict.__setitem__(self, args[0], chat)
431
+ elif kind == "event":
432
+ self._event(value)
433
+
434
+ def _apply_settings(self, chat, settings, observed_at=None):
435
+ observed_at = time.time() if observed_at is None else observed_at
436
+ if observed_at < chat.get("codex_settings_at", 0):
437
+ return
438
+ values = {target: settings[source] for source, target in
439
+ (("model", "model"), ("reasoningEffort", "effort"), ("effort", "effort"), ("serviceTier", "service_tier"))
440
+ if source in settings}
441
+ if values:
442
+ chat["codex_settings"] = {**chat.get("codex_settings", {}), **values}
443
+ chat["codex_settings_at"] = observed_at
444
+
445
+ def _history(self, thread, project):
446
+ history = Chat({"project": project})
447
+ if not hasattr(self, "settings_reader"):
448
+ self.settings_reader = ThreadSettingsReader()
449
+ history.update(self.settings_reader.read(thread.get("path")))
450
+ for turn in thread.get("turns", []):
451
+ for item in turn.get("items", []):
452
+ self._item(history, item, history=True)
453
+ return history
454
+
455
+ def _item(self, chat, item, history=False):
456
+ identifier = item.get("id")
457
+ if not identifier:
458
+ return
459
+ if item.get("type") == "userMessage" and not history:
460
+ return # the local submitted message already occupies a stable row
461
+ if not hasattr(chat, "command_scripts"):
462
+ chat.command_scripts = {}
463
+ incoming = from_codex(item, scripts=chat.command_scripts)
464
+ incoming["details"].setdefault("cwd", chat["project"])
465
+ old = chat["messages"].get(identifier)
466
+ if isinstance(incoming, CommandExecution) and isinstance(old, CommandExecution):
467
+ if item.get("aggregatedOutput") is None:
468
+ incoming["content"]["output"] = old["content"].get("output", ToolOutput(""))
469
+ # Line counts come from the turn diff, not the item: keep what the
470
+ # started item was stamped with when its completion re-parses the tags.
471
+ for path, entry in incoming.get("summary", {}).items():
472
+ previous = old.get("summary", {}).get(path)
473
+ if previous is not None and entry.get("added") is None and previous.get("added") is not None:
474
+ entry["added"], entry["removed"] = previous["added"], previous["removed"]
475
+ upsert(chat["messages"], identifier, incoming)
476
+
477
+ def _turn_diff(self, chat, diff):
478
+ """Attribute the turn's cumulative diff growth to the latest command writing each file.
479
+
480
+ Codex reports no per-command diff; `turn/diff/updated` carries the whole
481
+ turn's unified diff after each item. The growth since the previous update
482
+ is what the newest item did, so it lands on the most recent command whose
483
+ parsed tags name that file (a fileChange item already carries its own).
484
+ """
485
+ counts = diff_counts(diff)
486
+ previous = getattr(chat, "turn_counts", {})
487
+ chat.turn_counts = counts
488
+ for diff_path, (added, removed) in counts.items():
489
+ old_added, old_removed = previous.get(diff_path, (0, 0))
490
+ delta = (max(0, added - old_added), max(0, removed - old_removed))
491
+ for message in reversed(list(chat["messages"].values())):
492
+ if not isinstance(message, CommandExecution) or not isinstance(message.get("summary"), FileTags):
493
+ continue
494
+ entry = next((entry for path, entry in message["summary"].items()
495
+ if entry.get("access", "write") == "write" and match_file(path, diff_path)), None)
496
+ if entry is None:
497
+ continue
498
+ if entry.get("added") is None:
499
+ if delta == (0, 0):
500
+ break # nothing new to count; an unstamped tag stays count-less
501
+ entry["added"], entry["removed"] = delta
502
+ else:
503
+ entry["added"] += delta[0]
504
+ entry["removed"] += delta[1]
505
+ break
506
+
507
+ def _event(self, event):
508
+ method, params = event.get("method", ""), event.get("params") or {}
509
+ if method == "transport/error":
510
+ error = params.get("message")
511
+ remote_id = params.get("threadId")
512
+ if not remote_id:
513
+ self.error = error
514
+ for chat in self.values():
515
+ if not remote_id or chat.remote_id == remote_id:
516
+ chat["running"] = False
517
+ chat.error = error
518
+ return
519
+ remote_id = params.get("threadId") or (params.get("thread") or {}).get("id")
520
+ chat = next((chat for chat in self.known.values() if chat.remote_id == remote_id), None)
521
+ if chat is not None:
522
+ chat["updated"] = time.time() # any event for a thread is activity on it
523
+ if "id" in event:
524
+ if chat is None:
525
+ self.submit("unsupported", event["id"], "Unknown conversation", remote_id)
526
+ return
527
+ if method in ("item/commandExecution/requestApproval", "item/fileChange/requestApproval"):
528
+ chat["requests"][event["id"]] = {"kind": "approval", "data": params,
529
+ "text": params.get("command") or params.get("reason") or "Approve file changes?"}
530
+ elif method == "item/tool/requestUserInput":
531
+ chat["requests"][event["id"]] = {"kind": "input", "data": params}
532
+ else:
533
+ # Unsupported requests fail explicitly; never silently accept permissions.
534
+ self.submit("unsupported", event["id"], "Not supported by Melty chat yet", remote_id)
535
+ chat.error = f"Unsupported request: {method}"
536
+ return
537
+ if chat is None:
538
+ return
539
+ if method == "thread/settings/updated":
540
+ self._apply_settings(chat, params.get("threadSettings") or {}, event.get("settings_observed_at"))
541
+ elif method in ("item/started", "item/completed"):
542
+ self._item(chat, params.get("item") or {})
543
+ elif method in ("item/agentMessage/delta", "item/commandExecution/outputDelta", "item/plan/delta"):
544
+ identifier = params.get("itemId")
545
+ if not identifier:
546
+ return
547
+ cls, kind = ((CommandExecution, "commandExecution") if "commandExecution" in method else
548
+ (PlanMessage, "plan") if "plan" in method else (AssistantMessage, "agentMessage"))
549
+ message = chat["messages"].setdefault(identifier, cls(kind))
550
+ delta = params.get("delta", "")
551
+ if isinstance(message, CommandExecution):
552
+ message["content"]["output"] = ToolOutput(message["content"].get("output", "") + delta)
553
+ else:
554
+ set_text(message, message.source_text + delta)
555
+ elif method in ("item/reasoning/summaryTextDelta", "item/reasoning/textDelta"):
556
+ identifier = params.get("itemId")
557
+ if identifier:
558
+ message = chat["messages"].setdefault(identifier, ReasoningMessage("reasoning"))
559
+ field = "summary" if "summary" in method else "content"
560
+ index = str(params.get("summaryIndex", params.get("contentIndex", 0)))
561
+ parts = message["content"].setdefault(field, {})
562
+ parts[index] = ToolOutput(str(parts.get(index, "")) + params.get("delta", ""))
563
+ elif method in ("item/fileChange/outputDelta", "item/mcpToolCall/progress"):
564
+ identifier = params.get("itemId")
565
+ if identifier:
566
+ is_file = "fileChange" in method
567
+ cls = FileChange if is_file else McpToolCall
568
+ message = chat["messages"].setdefault(identifier, cls("fileChange" if is_file else "mcpToolCall"))
569
+ field = "output" if is_file else "progress"
570
+ delta = params.get("delta", "") if is_file else params.get("message", "") + "\n"
571
+ message["content"][field] = ToolOutput(message["content"].get(field, "") + delta)
572
+ elif method == "turn/diff/updated":
573
+ self._turn_diff(chat, params.get("diff") or params.get("unifiedDiff") or "")
574
+ elif method == "turn/started":
575
+ chat.turn_counts = {}
576
+ chat.turn_id = params["turn"]["id"]
577
+ chat["running"] = "interrupt_requested" not in chat.inflight
578
+ elif method == "turn/completed":
579
+ self.submit("release", chat.remote_id)
580
+ chat.resumed = False
581
+ chat.turn_id = None
582
+ chat["running"] = False
583
+ chat.inflight.discard("interrupt")
584
+ chat.inflight.discard("interrupt_requested")
585
+ chat["requests"].clear()
586
+ error = params.get("turn", {}).get("error")
587
+ if error:
588
+ chat.error = error.get("message", str(error))
589
+ elif method == "error":
590
+ chat.error = params.get("error", {}).get("message", "Codex error")
591
+ elif method == "serverRequest/resolved":
592
+ chat["requests"].pop(params.get("requestId"), None)