@pheem49/mint 1.17.0 → 1.18.0

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 (421) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +2 -2
  3. package/README.md +8 -2
  4. package/Release_Note.md +222 -71
  5. package/crates/mint-cli/src/agent/diff_render.rs +66 -44
  6. package/crates/mint-cli/src/agent/live_status.rs +21 -4
  7. package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
  8. package/crates/mint-cli/src/agent.rs +25 -10
  9. package/crates/mint-cli/src/commands/agent.rs +4 -92
  10. package/crates/mint-cli/src/commands/browser.rs +2 -15
  11. package/crates/mint-cli/src/commands/integrations.rs +3 -3
  12. package/crates/mint-cli/src/commands/mod.rs +0 -3
  13. package/crates/mint-cli/src/commands/tailnet_web.rs +1 -0
  14. package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
  15. package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
  16. package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
  17. package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
  18. package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
  19. package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
  20. package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
  21. package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
  22. package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
  23. package/crates/mint-cli/src/interactive/command_ui.rs +53 -5
  24. package/crates/mint-cli/src/interactive/composer.rs +186 -0
  25. package/crates/mint-cli/src/interactive/format.rs +78 -3
  26. package/crates/mint-cli/src/interactive/input_box.rs +61 -1
  27. package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
  28. package/crates/mint-cli/src/interactive/slash_commands.rs +648 -371
  29. package/crates/mint-cli/src/interactive/tool_activity.rs +93 -20
  30. package/crates/mint-cli/src/interactive.rs +37 -0
  31. package/crates/mint-cli/src/main.rs +6 -3
  32. package/crates/mint-cli/src/markdown.rs +2 -37
  33. package/crates/mint-cli/src/onboard.rs +700 -282
  34. package/crates/mint-cli/src/setup.rs +1342 -624
  35. package/crates/mint-cli/src/terminal_theme.rs +159 -35
  36. package/crates/mint-cli/tests/library_intake.rs +1 -0
  37. package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
  38. package/crates/mint-core/src/agent/chat/custom.rs +149 -0
  39. package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
  40. package/crates/mint-core/src/agent/chat/mod.rs +857 -0
  41. package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
  42. package/crates/mint-core/src/agent/chat/openai.rs +582 -0
  43. package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
  44. package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
  45. package/crates/mint-core/src/agent/memory.rs +56 -2
  46. package/crates/mint-core/src/agent/tasks.rs +2 -2
  47. package/crates/mint-core/src/api_server/routes/chat.rs +24 -2
  48. package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
  49. package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
  50. package/crates/mint-core/src/api_server/routes/sessions.rs +21 -0
  51. package/crates/mint-core/src/avatar_bridge.rs +0 -22
  52. package/crates/mint-core/src/browser/cdp.rs +10 -4
  53. package/crates/mint-core/src/browser/headless.rs +582 -0
  54. package/crates/mint-core/src/browser/input.rs +7 -62
  55. package/crates/mint-core/src/browser/interact.rs +0 -14
  56. package/crates/mint-core/src/browser/mod.rs +8 -29
  57. package/crates/mint-core/src/browser/navigate.rs +2 -36
  58. package/crates/mint-core/src/browser/runtime.rs +2 -4
  59. package/crates/mint-core/src/browser/session.rs +50 -466
  60. package/crates/mint-core/src/browser/workflow.rs +142 -32
  61. package/crates/mint-core/src/cron/store.rs +2 -2
  62. package/crates/mint-core/src/integrations/mcp.rs +6 -8
  63. package/crates/mint-core/src/lib.rs +0 -5
  64. package/crates/mint-core/src/media/gemini_live.rs +636 -86
  65. package/crates/mint-core/src/media/library/migration.rs +26 -0
  66. package/crates/mint-core/src/media/library/tests.rs +1 -1
  67. package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
  68. package/crates/mint-core/src/media/pictures.rs +1 -1
  69. package/crates/mint-core/src/media/tool_images.rs +92 -1
  70. package/crates/mint-core/src/mods/manager.rs +101 -35
  71. package/crates/mint-core/src/orchestration/context_render.rs +441 -24
  72. package/crates/mint-core/src/orchestration/decision_parsing.rs +531 -16
  73. package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
  74. package/crates/mint-core/src/orchestration/loop_guard.rs +145 -0
  75. package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
  76. package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
  77. package/crates/mint-core/src/orchestration/mod.rs +320 -4249
  78. package/crates/mint-core/src/orchestration/reliability.rs +67 -27
  79. package/crates/mint-core/src/orchestration/review_regressions.rs +5 -0
  80. package/crates/mint-core/src/orchestration/tests.rs +1665 -0
  81. package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
  82. package/crates/mint-core/src/orchestration/tools/files.rs +1 -0
  83. package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
  84. package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
  85. package/crates/mint-core/src/orchestration/tools/shell.rs +129 -58
  86. package/crates/mint-core/src/orchestration/truncation.rs +170 -13
  87. package/crates/mint-core/src/orchestration/types.rs +648 -0
  88. package/crates/mint-core/src/orchestration/verification.rs +0 -3
  89. package/crates/mint-core/src/orchestration/workspace_helpers.rs +24 -6
  90. package/crates/mint-core/src/prompts/agent.rs +52 -76
  91. package/crates/mint-core/src/prompts/tool_catalog.rs +8 -151
  92. package/crates/mint-core/src/search/files.rs +20 -0
  93. package/crates/mint-core/src/slash/mod.rs +228 -265
  94. package/crates/mint-core/src/slash/models.rs +6 -1
  95. package/crates/mint-core/src/system/bg_shell.rs +1 -0
  96. package/crates/mint-core/src/system/config.rs +176 -34
  97. package/crates/mint-mod-runtime/src/lib.rs +37 -23
  98. package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
  99. package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
  100. package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
  101. package/out/web/assets/{CliSessionsView-MI8kXMAb.js → CliSessionsView-z5i6qGyb.js} +1 -1
  102. package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
  103. package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
  104. package/out/web/assets/{CommandPalette-BD8SlTE5.js → CommandPalette-DaKlzbKs.js} +1 -1
  105. package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
  106. package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
  107. package/out/web/assets/{ImageStudioPanel-BP7N4rLr.js → ImageStudioPanel-B5N7TjeB.js} +1 -1
  108. package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
  109. package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
  110. package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
  111. package/out/web/assets/{McpRegistryPicker-CoS83vtc.js → McpRegistryPicker-DnXPlzI7.js} +1 -1
  112. package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
  113. package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
  114. package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
  115. package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
  116. package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
  117. package/out/web/assets/{PluginsView-jBYmcKh4.js → PluginsView-DoTg9MVa.js} +1 -1
  118. package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
  119. package/out/web/assets/{ScheduledTasksView-5uw9-V9H.js → ScheduledTasksView-BD5DVPkZ.js} +1 -1
  120. package/out/web/assets/{SelectField-DXBQtrUK.js → SelectField-Cy6CghrU.js} +1 -1
  121. package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
  122. package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
  123. package/out/web/assets/{SkillsView-C-gp9J7m.js → SkillsView-CIeWwq5z.js} +1 -1
  124. package/out/web/assets/StockCard-DxUciKyp.js +1 -0
  125. package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
  126. package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
  127. package/out/web/assets/{UiFeatureCard-6BwxTOCe.js → UiFeatureCard-CdY0hGWy.js} +1 -1
  128. package/out/web/assets/{UiGridCard-DvZcLNxb.js → UiGridCard-BBK3I5hC.js} +1 -1
  129. package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
  130. package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
  131. package/out/web/assets/{VeoStudioPanel-DfsOJKv0.js → VeoStudioPanel-Co6-wEWM.js} +2 -2
  132. package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
  133. package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
  134. package/out/web/assets/{abnfDiagram-VCTEODGH-BOo3Ckng.js → abnfDiagram-VCTEODGH-CMfR95N2.js} +1 -1
  135. package/out/web/assets/{architecture-7GRP2DOG-ZOwheW3n.js → architecture-7GRP2DOG-DhAM4HyO.js} +1 -1
  136. package/out/web/assets/{architectureDiagram-5GKGNRK7-DC2FDzVd.js → architectureDiagram-5GKGNRK7-Yk3TUJ_F.js} +1 -1
  137. package/out/web/assets/{blockDiagram-I7D4REHJ-BrKFWdAH.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +1 -1
  138. package/out/web/assets/{c4Diagram-7LVT6UL2-BHTHK1Yd.js → c4Diagram-7LVT6UL2-Bk9x7N-8.js} +1 -1
  139. package/out/web/assets/{chunk-4HAMMTFA-BYFkZwNj.js → chunk-4HAMMTFA-Dx5fk1_0.js} +1 -1
  140. package/out/web/assets/{chunk-75Z2AOVW-BSKfSKSA.js → chunk-75Z2AOVW-BszTQoM4.js} +1 -1
  141. package/out/web/assets/{chunk-DU6HZSFF-vIhsiW6n.js → chunk-DU6HZSFF-DK-As3rk.js} +1 -1
  142. package/out/web/assets/{chunk-F27PBJKO-BlYpW9xq.js → chunk-F27PBJKO-LmncZZIS.js} +1 -1
  143. package/out/web/assets/{chunk-GMAD6QVW-BIBz_Ug5.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +1 -1
  144. package/out/web/assets/{chunk-GVQU2GXP-NPSHxeqC.js → chunk-GVQU2GXP-Cno3ulje.js} +1 -1
  145. package/out/web/assets/{chunk-IMKFNOWR-YlJK8_Fp.js → chunk-IMKFNOWR-bIzfY9Jc.js} +1 -1
  146. package/out/web/assets/{chunk-L3NEJ4N5-DMYi4-af.js → chunk-L3NEJ4N5-Dl6eOa0D.js} +1 -1
  147. package/out/web/assets/{chunk-OSK3NFVY-Begd3-jA.js → chunk-OSK3NFVY-BaUGJ-cM.js} +1 -1
  148. package/out/web/assets/{chunk-P2QGCYS3-D1I4xFHN.js → chunk-P2QGCYS3-_066ndhH.js} +1 -1
  149. package/out/web/assets/{chunk-POPQ4Y6H-CafZZzNE.js → chunk-POPQ4Y6H-YT1AKhTv.js} +1 -1
  150. package/out/web/assets/{chunk-PWAF6VOD-Dwh6_Lnt.js → chunk-PWAF6VOD-CrEgDsxh.js} +1 -1
  151. package/out/web/assets/{chunk-SHT3W25Y-B0XrCSEb.js → chunk-SHT3W25Y-_KwaRLOl.js} +1 -1
  152. package/out/web/assets/{chunk-SVP7TREG-CxLsddxC.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
  153. package/out/web/assets/{chunk-TICWLB2K-y156QInb.js → chunk-TICWLB2K-BDyehCtg.js} +1 -1
  154. package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
  155. package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
  156. package/out/web/assets/{config-DqXJXeZM.css → config-2suVFPZg.css} +1 -1
  157. package/out/web/assets/config-CkRCdkxn.js +2 -0
  158. package/out/web/assets/{cynefin-OW5HDTMX-SMVvrLf-.js → cynefin-OW5HDTMX-Dl_31aaN.js} +1 -1
  159. package/out/web/assets/{cynefinDiagram-5FMLGOSQ-rZznN24T.js → cynefinDiagram-5FMLGOSQ-Bpir8jha.js} +1 -1
  160. package/out/web/assets/{dagre-GXQ25YYZ-BOToydFu.js → dagre-GXQ25YYZ-D9xu3nsq.js} +1 -1
  161. package/out/web/assets/{diagram-S7CK7UJ4-TMVaGQEJ.js → diagram-S7CK7UJ4-DeveoEeB.js} +1 -1
  162. package/out/web/assets/{diagram-UQ7AKVKN-C2JK-GP2.js → diagram-UQ7AKVKN-p4xWRESV.js} +1 -1
  163. package/out/web/assets/{diagram-VSXAHHWV-Cw8VSi2K.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
  164. package/out/web/assets/{diagram-VX7I27RA-DXp4fKk9.js → diagram-VX7I27RA-DRiCa3Vd.js} +1 -1
  165. package/out/web/assets/{diagram-Z3DM3KII-CzSicYiW.js → diagram-Z3DM3KII-Bt7zduQX.js} +1 -1
  166. package/out/web/assets/{ebnfDiagram-PWID7BFC-Dqj-zqPu.js → ebnfDiagram-PWID7BFC-DV9WpwdR.js} +1 -1
  167. package/out/web/assets/{erDiagram-RLTQ6QDP-DZwFZAuD.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +1 -1
  168. package/out/web/assets/{eventmodeling-NTZA5JFV-C0O-Lvgp.js → eventmodeling-NTZA5JFV-BTBMrfVa.js} +1 -1
  169. package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
  170. package/out/web/assets/{ganttDiagram-EL5Y4UJY-Da4ULXTl.js → ganttDiagram-EL5Y4UJY-DQLdsXrC.js} +1 -1
  171. package/out/web/assets/{gitGraph-4MIJSDKK-B9wNFZIP.js → gitGraph-4MIJSDKK-CL1OEOQ_.js} +1 -1
  172. package/out/web/assets/{gitGraphDiagram-WWUBYQGX-RjrhB0MP.js → gitGraphDiagram-WWUBYQGX-8cSi42H7.js} +1 -1
  173. package/out/web/assets/index-BocxnrDG.js +168 -0
  174. package/out/web/assets/index-yeh_r55a.css +1 -0
  175. package/out/web/assets/{info-A6RAGUB7-B3dfCpeY.js → info-A6RAGUB7-C1zsX5sc.js} +1 -1
  176. package/out/web/assets/{infoDiagram-27XIBGKW-QVWkLVx7.js → infoDiagram-27XIBGKW-DsRYtIyI.js} +1 -1
  177. package/out/web/assets/{ishikawaDiagram-5VMMS53U-CLFNIRI9.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +1 -1
  178. package/out/web/assets/{journeyDiagram-3NMN7TZE-fgOQadYF.js → journeyDiagram-3NMN7TZE-BTH-GyVR.js} +1 -1
  179. package/out/web/assets/{kanban-definition-UXKFOSKX-G3Q_9g9O.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +1 -1
  180. package/out/web/assets/{mermaid-parser.core-v3Jm7nco.js → mermaid-parser.core-BJwgCBBc.js} +3 -3
  181. package/out/web/assets/{mermaid.core-DTEjLUA1.js → mermaid.core-CsDDodN8.js} +4 -4
  182. package/out/web/assets/{mindmap-definition-YA3MSWOX-C_D42_fF.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +1 -1
  183. package/out/web/assets/{modelManager-BPLycQrN.js → modelManager-B3ZFMh8v.js} +2 -2
  184. package/out/web/assets/{oauthManager-BUK7ic7q.js → oauthManager-Ch5Du2fF.js} +1 -1
  185. package/out/web/assets/{packet-AYTQ26CC-B-R-SHlF.js → packet-AYTQ26CC-B6xUyE6r.js} +1 -1
  186. package/out/web/assets/{pegDiagram-XKGWAZYB-Da2q_giz.js → pegDiagram-XKGWAZYB-1LabVycL.js} +1 -1
  187. package/out/web/assets/{pie-WAS4IAKB-BC8F0N7-.js → pie-WAS4IAKB-DJwo-nf_.js} +1 -1
  188. package/out/web/assets/{pieDiagram-E7YTZNPT-Iy9REJNC.js → pieDiagram-E7YTZNPT-icmAqzy4.js} +1 -1
  189. package/out/web/assets/{quadrantDiagram-AXDQQJYC-ztsiYw7h.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +1 -1
  190. package/out/web/assets/{radar-RG4KPBEZ-B_1P8Z_z.js → radar-RG4KPBEZ-D7d-Xriw.js} +1 -1
  191. package/out/web/assets/{railroad-74A4TZTK-DuTqczSo.js → railroad-74A4TZTK-viuhdYbs.js} +1 -1
  192. package/out/web/assets/{railroad-abnf-HS5TGJTU-Df29WLmp.js → railroad-abnf-HS5TGJTU-DNXiCNea.js} +1 -1
  193. package/out/web/assets/{railroad-ebnf-LZEXJU2U-ePFMo-qp.js → railroad-ebnf-LZEXJU2U-nkSu1-0C.js} +1 -1
  194. package/out/web/assets/{railroad-peg-WCYAUIDC-DYXLW4_E.js → railroad-peg-WCYAUIDC-BhbMeQzz.js} +1 -1
  195. package/out/web/assets/{railroadDiagram-O6MQD6OU-LStnZf2S.js → railroadDiagram-O6MQD6OU-CLpG1jUX.js} +1 -1
  196. package/out/web/assets/{requirementDiagram-BXWQKSXE-DmzonMH-.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +1 -1
  197. package/out/web/assets/{sankeyDiagram-P5KCCOFB-B8vjmUU1.js → sankeyDiagram-P5KCCOFB-DxAwmq4x.js} +1 -1
  198. package/out/web/assets/{sequenceDiagram-WJ2MYXX4-CTnplOAX.js → sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js} +1 -1
  199. package/out/web/assets/{stateDiagram-D77RDMKH-BFrIaLdI.js → stateDiagram-D77RDMKH-D5QWfOhp.js} +1 -1
  200. package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
  201. package/out/web/assets/{swimlanes-42K2YHIH-BXGngUNU.js → swimlanes-42K2YHIH-CRs5UyA-.js} +1 -1
  202. package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
  203. package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
  204. package/out/web/assets/{timeline-definition-24CTP7MA-CegH7n43.js → timeline-definition-24CTP7MA-Denqd9qb.js} +1 -1
  205. package/out/web/assets/{treeView-Q6P3EWNA-DTpJv2Tw.js → treeView-Q6P3EWNA-0my3Z3s4.js} +1 -1
  206. package/out/web/assets/{treemap-WGGIJYW6-i8M9iJU6.js → treemap-WGGIJYW6-B_EjkLYq.js} +1 -1
  207. package/out/web/assets/{vennDiagram-4TSXK5OY-Dpzo1S8U.js → vennDiagram-4TSXK5OY-DxVg3Ewr.js} +1 -1
  208. package/out/web/assets/{wardley-WFR3VGLG-BrNsgtfw.js → wardley-WFR3VGLG-CGD-Tj4p.js} +1 -1
  209. package/out/web/assets/{wardleyDiagram-VM6X3IG4-lQ92LFE1.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +1 -1
  210. package/out/web/assets/{xychartDiagram-S5SC5T6Z-BaOaEoBd.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +1 -1
  211. package/out/web/index-web.html +2 -2
  212. package/out/web/index.html +2 -2
  213. package/package.json +2 -2
  214. package/shared-modules/CODEOWNERS +3 -3
  215. package/shared-modules/SDL2/SDL2_mixer.json +27 -0
  216. package/shared-modules/appstream/README.md +13 -0
  217. package/shared-modules/appstream/appstream-compose.json +111 -0
  218. package/shared-modules/dbus-glib/dbus-glib.json +2 -2
  219. package/shared-modules/libportal/libportal.json +23 -0
  220. package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
  221. package/shared-modules/linux-audio/fluidsynth2.json +2 -2
  222. package/shared-modules/linux-audio/libinstpatch.json +2 -2
  223. package/shared-modules/linux-audio/liblo-static.json +3 -0
  224. package/shared-modules/luajit/luajit.json +2 -2
  225. package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
  226. package/slash-commands.json +7 -2
  227. package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  228. package/src/renderer/shared/agentProgress.ts +51 -3
  229. package/src/renderer/shared/components/Settings/AutomationTab.tsx +2 -56
  230. package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +15 -1
  231. package/src/renderer/shared/components/Settings/GeneralTab.tsx +25 -12
  232. package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
  233. package/src/renderer/shared/components/Settings/ThemeTab.tsx +106 -3
  234. package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +67 -17
  235. package/src/renderer/shared/components/activity/AgentActivityTable.tsx +376 -134
  236. package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
  237. package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
  238. package/src/renderer/shared/components/activity/McpToolResults.tsx +39 -2
  239. package/src/renderer/shared/components/activity/SubagentDagView.tsx +7 -7
  240. package/src/renderer/shared/components/activity/ViewedImages.tsx +41 -2
  241. package/src/renderer/shared/components/app/ChunkErrorBoundary.tsx +3 -3
  242. package/src/renderer/shared/components/app/CommandPalette.tsx +7 -7
  243. package/src/renderer/shared/components/app/DesktopTitlebar.tsx +2 -2
  244. package/src/renderer/shared/components/app/MintDashboard.tsx +27 -17
  245. package/src/renderer/shared/components/app/ModalErrorBoundary.tsx +3 -3
  246. package/src/renderer/shared/components/cards/CalculationCard.tsx +3 -3
  247. package/src/renderer/shared/components/cards/ImageGenCard.tsx +2 -2
  248. package/src/renderer/shared/components/cards/ImageSearchCard.tsx +2 -2
  249. package/src/renderer/shared/components/cards/MermaidCard.tsx +1 -1
  250. package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
  251. package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
  252. package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
  253. package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
  254. package/src/renderer/shared/components/cards/StockCard.tsx +7 -7
  255. package/src/renderer/shared/components/cards/ToolResultCards.tsx +11 -3
  256. package/src/renderer/shared/components/cards/UiMockupWidget.tsx +4 -4
  257. package/src/renderer/shared/components/cards/WeatherCard.tsx +5 -5
  258. package/src/renderer/shared/components/chat/ApprovalCard.test.tsx +11 -0
  259. package/src/renderer/shared/components/chat/ApprovalCard.tsx +21 -2
  260. package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
  261. package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
  262. package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
  263. package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
  264. package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
  265. package/src/renderer/shared/components/chat/ChatCodeBlock.tsx +15 -1
  266. package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +40 -2
  267. package/src/renderer/shared/components/chat/ChatMessageItem.tsx +180 -34
  268. package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
  269. package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
  270. package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
  271. package/src/renderer/shared/components/chat/ChatPanel.test.tsx +22 -0
  272. package/src/renderer/shared/components/chat/ChatPanel.tsx +227 -53
  273. package/src/renderer/shared/components/chat/DiffReviewModal.tsx +6 -6
  274. package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +142 -1
  275. package/src/renderer/shared/components/chat/GeminiLiveOverlay.tsx +45 -3
  276. package/src/renderer/shared/components/chat/ModelSelectorPopover.tsx +42 -14
  277. package/src/renderer/shared/components/chat/RewindModal.tsx +10 -10
  278. package/src/renderer/shared/components/chat/SlashSuggestions.test.tsx +14 -0
  279. package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
  280. package/src/renderer/shared/components/chat/SourcesBlock.tsx +236 -90
  281. package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +45 -1
  282. package/src/renderer/shared/components/chat/TurnFileChanges.tsx +202 -169
  283. package/src/renderer/shared/components/extensions/CliSessionsView.tsx +5 -4
  284. package/src/renderer/shared/components/extensions/McpRegistryPicker.tsx +3 -3
  285. package/src/renderer/shared/components/studio/LibraryPage.tsx +48 -5
  286. package/src/renderer/shared/components/workspace/ArtifactPreviewPanel.tsx +125 -13
  287. package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
  288. package/src/renderer/shared/components/workspace/CodeReviewPage.tsx +77 -16
  289. package/src/renderer/shared/components/workspace/GitBranchSelector.tsx +78 -22
  290. package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +13 -0
  291. package/src/renderer/shared/components/workspace/ToolSurfacePage.tsx +48 -28
  292. package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +266 -47
  293. package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +18 -6
  294. package/src/renderer/shared/components/workspace/background-terminals.css +11 -11
  295. package/src/renderer/shared/constants/config.ts +1 -5
  296. package/src/renderer/shared/constants/models.test.ts +51 -0
  297. package/src/renderer/shared/constants/models.ts +139 -32
  298. package/src/renderer/shared/conversation/lifecycle.test.ts +91 -3
  299. package/src/renderer/shared/conversation/lifecycle.ts +104 -5
  300. package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
  301. package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
  302. package/src/renderer/shared/css/agent-observability.css +133 -0
  303. package/src/renderer/shared/css/chat-ui-cards.css +13 -13
  304. package/src/renderer/shared/css/code-review.css +63 -34
  305. package/src/renderer/shared/css/code-sessions.css +47 -18
  306. package/src/renderer/shared/css/collapsed-paste.css +1 -1
  307. package/src/renderer/shared/css/error-notice.css +1 -1
  308. package/src/renderer/shared/css/file-changes.css +165 -80
  309. package/src/renderer/shared/css/library.css +105 -10
  310. package/src/renderer/shared/css/management-views.css +1 -1
  311. package/src/renderer/shared/css/mcp-results.css +5 -5
  312. package/src/renderer/shared/css/model-selector.css +18 -18
  313. package/src/renderer/shared/css/mods.css +7 -7
  314. package/src/renderer/shared/css/notification-notice.css +2 -2
  315. package/src/renderer/shared/css/queued-messages.css +5 -5
  316. package/src/renderer/shared/css/settings/general.css +18 -13
  317. package/src/renderer/shared/css/settings/profile.css +5 -5
  318. package/src/renderer/shared/css/settings/theme.css +5 -5
  319. package/src/renderer/shared/css/slash-choice.css +2 -2
  320. package/src/renderer/shared/css/slash-suggestions.css +3 -3
  321. package/src/renderer/shared/css/sources-block.css +41 -2
  322. package/src/renderer/shared/css/tools-panel.css +2 -2
  323. package/src/renderer/shared/css/ui-tailwind.css +8 -0
  324. package/src/renderer/shared/css/veo-studio.css +2 -2
  325. package/src/renderer/shared/css/viewed-images.css +7 -7
  326. package/src/renderer/shared/platform.ts +2 -1
  327. package/src/renderer/shared/theme/themeManager.test.ts +17 -0
  328. package/src/renderer/shared/theme/themeManager.ts +21 -4
  329. package/src/renderer/shared/theme/themes.ts +54 -0
  330. package/src/renderer/shared/theme/tokens.css +6 -4
  331. package/src/renderer/shared/types.ts +1 -1
  332. package/src/renderer/shared/utils/agentActivity.test.ts +468 -2
  333. package/src/renderer/shared/utils/agentActivity.ts +602 -35
  334. package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
  335. package/src/renderer/shared/utils/artifactBrowser.ts +17 -19
  336. package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
  337. package/src/renderer/shared/utils/attachedDocument.ts +49 -0
  338. package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
  339. package/src/renderer/shared/utils/fileIcons.ts +149 -16
  340. package/src/renderer/shared/utils/markdown.tsx +72 -19
  341. package/src/renderer/shared/utils/markdown.webImages.test.tsx +16 -0
  342. package/src/renderer/shared/utils/mcpResults.test.tsx +29 -0
  343. package/src/renderer/shared/utils/slashCommandProcessor.ts +4 -1
  344. package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
  345. package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
  346. package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
  347. package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
  348. package/src/renderer/shared/utils/workspaceDrag.ts +142 -0
  349. package/src/renderer/shared/utils/workspaceMedia.ts +10 -2
  350. package/src/renderer/src/App.tsx +14 -8
  351. package/src/renderer/src/agentProgress.ts +1 -0
  352. package/src/renderer/src/components/ScreenPicker.css +1 -1
  353. package/src/renderer/src/components/WorkspacePanel.tsx +6 -5
  354. package/src/renderer/src/css/app-shell.css +4 -4
  355. package/src/renderer/src/css/chat.css +1261 -241
  356. package/src/renderer/src/css/desktop-tools.css +11 -11
  357. package/src/renderer/src/css/sidebar-workspace.css +2 -2
  358. package/src/renderer/src/css/tool-surfaces.css +12 -12
  359. package/src/renderer/src/index.css +38 -20
  360. package/src/renderer/src/tauri.ts +22 -2
  361. package/src/renderer/src-web/App.tsx +14 -0
  362. package/src/renderer/src-web/agentProgress.ts +1 -0
  363. package/src/renderer/src-web/css/app-shell.css +4 -4
  364. package/src/renderer/src-web/css/chat.css +1278 -190
  365. package/src/renderer/src-web/css/mobile.css +29 -13
  366. package/src/renderer/src-web/css/settings/mobile.css +2 -2
  367. package/src/renderer/src-web/css/sidebar-workspace.css +1 -1
  368. package/src/renderer/src-web/index.css +38 -20
  369. package/src/renderer/src-web/tauri.ts +22 -2
  370. package/src/renderer/tests/browserArtifacts.browser.tsx +1 -1
  371. package/src/renderer/tests/library.browser.tsx +3 -0
  372. package/src/renderer/tests/liveVoice.browser.html +1 -0
  373. package/src/renderer/tests/workspaceDrop.browser.tsx +1 -1
  374. package/src-tauri/src/app/lifecycle.rs +1 -14
  375. package/src-tauri/src/background/headless.rs +14 -8
  376. package/src-tauri/src/commands/conversation.rs +38 -0
  377. package/src-tauri/src/native/mod.rs +0 -1
  378. package/src-tauri/src/native/system.rs +4 -47
  379. package/crates/mint-cli/src/interactive/chat_tui.rs +0 -8855
  380. package/crates/mint-core/src/agent/chat.rs +0 -4394
  381. package/crates/mint-core/src/browser/lifecycle.rs +0 -291
  382. package/crates/mint-core/src/browser/logging.rs +0 -20
  383. package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
  384. package/out/web/assets/ArtifactPreviewPanel-S0dPfYFx.js +0 -2
  385. package/out/web/assets/CalculationCard-CRjP3E1j.js +0 -1
  386. package/out/web/assets/CliSessionsView-C3Br2v5Y.css +0 -1
  387. package/out/web/assets/CodeReviewPage-CUJceQme.css +0 -1
  388. package/out/web/assets/CodeReviewPage-VCUgPla_.js +0 -2
  389. package/out/web/assets/ImageGenCard-BCXxkVJX.js +0 -1
  390. package/out/web/assets/ImageSearchCard-N3yQOr8Z.js +0 -1
  391. package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
  392. package/out/web/assets/LibraryPage-WlviRdR8.js +0 -2
  393. package/out/web/assets/LinkedFoldersView-CKRgroWK.js +0 -1
  394. package/out/web/assets/McpServersView-D3wHnuHg.js +0 -1
  395. package/out/web/assets/MermaidCard-NqrHdyJW.js +0 -2
  396. package/out/web/assets/MintDashboard-B2DJsDDO.css +0 -1
  397. package/out/web/assets/MintDashboard-COw_xol7.js +0 -63
  398. package/out/web/assets/RewindModal-DempuDod.js +0 -1
  399. package/out/web/assets/SettingsWindow-2YP3yj7s.js +0 -4
  400. package/out/web/assets/SettingsWindow-Dz8LQVgD.css +0 -1
  401. package/out/web/assets/StockCard-BDMCe7mx.js +0 -1
  402. package/out/web/assets/ToolSurfacePage-B0J_q0Jl.js +0 -38
  403. package/out/web/assets/ToolSurfacePage-CRIQ1owD.css +0 -1
  404. package/out/web/assets/UiMockupWidget-DD73QWg1.js +0 -1
  405. package/out/web/assets/WeatherCard-CPucIfq8.js +0 -1
  406. package/out/web/assets/WorkspacePdfPreview-Dx_lkjNu.js +0 -2
  407. package/out/web/assets/classDiagram-ZZMXUADV-BgJ5WtSp.js +0 -1
  408. package/out/web/assets/classDiagram-v2-VYDZK3BY-BgJ5WtSp.js +0 -1
  409. package/out/web/assets/config-CvIO5hkn.js +0 -2
  410. package/out/web/assets/flowDiagram-HODETNUW-uqCHJond.js +0 -1
  411. package/out/web/assets/index-BmpulIkg.js +0 -168
  412. package/out/web/assets/index-CkvBwhX_.css +0 -1
  413. package/out/web/assets/stateDiagram-v2-MP3YSRHH-uf3kdNW4.js +0 -1
  414. package/out/web/assets/swimlanesDiagram-VR7AAH4N-Y8q5bQce.js +0 -8
  415. package/shared-modules/libsecret/libsecret.json +0 -23
  416. package/shared-modules/libusb/libusb.json +0 -27
  417. package/shared-modules/lzo/lzo.json +0 -27
  418. package/src/renderer/src/components/BrowserShell.tsx +0 -108
  419. package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
  420. package/src/renderer/src/components/browser-shell.css +0 -185
  421. package/src-tauri/src/native/browser.rs +0 -252
@@ -0,0 +1,582 @@
1
+ use reqwest::Client;
2
+ use serde_json::{Value, json};
3
+
4
+ use super::streaming::{StreamFormat, collect_stream_reply};
5
+ use super::{
6
+ ProviderReply, effective_system_instruction, effective_temperature, effective_thinking,
7
+ extract_think_tag, provider_key, required_key, split_data_uri, wants_agent_json,
8
+ };
9
+ use crate::{
10
+ ChatError, ChatMessage, ChatRequest, ChatRole, ChatStreamEvent, ContentBlock, MintConfig,
11
+ ToolCall,
12
+ };
13
+
14
+ pub(super) async fn call_openai(
15
+ client: &Client,
16
+ config: &MintConfig,
17
+ request: &ChatRequest,
18
+ ) -> Result<ProviderReply, ChatError> {
19
+ let local = config.ai_provider == "local_openai";
20
+ let openrouter = config.ai_provider == "openrouter";
21
+ let deepseek = config.ai_provider == "deepseek";
22
+ let api_key = if openrouter {
23
+ provider_key(&config.openrouter_api_key, "OPENROUTER_API_KEY")
24
+ } else if deepseek {
25
+ provider_key(&config.deepseek_api_key, "DEEPSEEK_API_KEY")
26
+ } else {
27
+ provider_key(&config.openai_api_key, "OPENAI_API_KEY")
28
+ };
29
+ if openrouter {
30
+ required_key("openrouter", &api_key)?;
31
+ } else if deepseek {
32
+ required_key("deepseek", &api_key)?;
33
+ } else if !local {
34
+ required_key("openai", &api_key)?;
35
+ }
36
+ let base_url = if openrouter {
37
+ "https://openrouter.ai/api/v1"
38
+ } else if deepseek {
39
+ "https://api.deepseek.com"
40
+ } else if local {
41
+ config.local_api_base_url.trim_end_matches('/')
42
+ } else {
43
+ "https://api.openai.com/v1"
44
+ };
45
+ let model = if openrouter {
46
+ config.openrouter_model.clone()
47
+ } else if deepseek {
48
+ config.deepseek_model.clone()
49
+ } else if local {
50
+ config.local_model_name.clone()
51
+ } else {
52
+ config.openai_model.clone()
53
+ };
54
+ let response: Value = send_openai_style_request(
55
+ client
56
+ .post(format!("{base_url}/chat/completions"))
57
+ .bearer_auth(if local { "not-needed" } else { &api_key })
58
+ .json(&openai_chat_payload(config, &model, request, false)?),
59
+ &config.ai_provider,
60
+ )
61
+ .await?;
62
+ parse_openai_style_reply(model, &response)
63
+ }
64
+
65
+ pub(super) async fn stream_openai_reply<F>(
66
+ client: &Client,
67
+ config: &MintConfig,
68
+ request: &ChatRequest,
69
+ on_event: &mut F,
70
+ ) -> Result<ProviderReply, ChatError>
71
+ where
72
+ F: FnMut(ChatStreamEvent),
73
+ {
74
+ let local = config.ai_provider == "local_openai";
75
+ let openrouter = config.ai_provider == "openrouter";
76
+ let deepseek = config.ai_provider == "deepseek";
77
+ let api_key = if openrouter {
78
+ provider_key(&config.openrouter_api_key, "OPENROUTER_API_KEY")
79
+ } else if deepseek {
80
+ provider_key(&config.deepseek_api_key, "DEEPSEEK_API_KEY")
81
+ } else {
82
+ provider_key(&config.openai_api_key, "OPENAI_API_KEY")
83
+ };
84
+ if openrouter {
85
+ required_key("openrouter", &api_key)?;
86
+ } else if deepseek {
87
+ required_key("deepseek", &api_key)?;
88
+ } else if !local {
89
+ required_key("openai", &api_key)?;
90
+ }
91
+ let base_url = if openrouter {
92
+ "https://openrouter.ai/api/v1"
93
+ } else if deepseek {
94
+ "https://api.deepseek.com"
95
+ } else if local {
96
+ config.local_api_base_url.trim_end_matches('/')
97
+ } else {
98
+ "https://api.openai.com/v1"
99
+ };
100
+ let model = if openrouter {
101
+ config.openrouter_model.clone()
102
+ } else if deepseek {
103
+ config.deepseek_model.clone()
104
+ } else if local {
105
+ config.local_model_name.clone()
106
+ } else {
107
+ config.openai_model.clone()
108
+ };
109
+ let response = client
110
+ .post(format!("{base_url}/chat/completions"))
111
+ .bearer_auth(if local { "not-needed" } else { &api_key })
112
+ .json(&openai_chat_payload(config, &model, request, true)?)
113
+ .send()
114
+ .await?;
115
+ if !response.status().is_success() {
116
+ let status = response.status();
117
+ let body = response.text().await.unwrap_or_default();
118
+ return Err(ChatError::Api(format!("HTTP {status}: {body}")));
119
+ }
120
+ collect_stream_reply(response, StreamFormat::OpenAi, model, on_event).await
121
+ }
122
+
123
+ pub(super) async fn send_openai_style_request(
124
+ request: reqwest::RequestBuilder,
125
+ provider: &str,
126
+ ) -> Result<Value, ChatError> {
127
+ let response = request.send().await.map_err(|e| {
128
+ if e.is_connect() {
129
+ ChatError::NetworkUnavailable
130
+ } else {
131
+ ChatError::Request(e)
132
+ }
133
+ })?;
134
+ if response.status() == reqwest::StatusCode::PAYMENT_REQUIRED {
135
+ return Err(ChatError::InsufficientBalance(provider.to_string()));
136
+ }
137
+ if !response.status().is_success() {
138
+ let status = response.status();
139
+ let body = response.text().await.unwrap_or_default();
140
+ return Err(ChatError::Api(format!("HTTP {status}: {body}")));
141
+ }
142
+ Ok(response.json().await?)
143
+ }
144
+
145
+ pub(super) fn decode_tool_arguments(arguments: &Value) -> Value {
146
+ match arguments.as_str() {
147
+ Some(raw) => sanitize_and_parse_tool_arguments(raw).unwrap_or_else(|| arguments.clone()),
148
+ None => arguments.clone(),
149
+ }
150
+ }
151
+
152
+ pub(super) fn sanitize_and_parse_tool_arguments(raw: &str) -> Option<Value> {
153
+ let trimmed = raw.trim();
154
+ if trimmed.is_empty() {
155
+ return None;
156
+ }
157
+ if let Ok(val) = serde_json::from_str::<Value>(trimmed) {
158
+ return Some(val);
159
+ }
160
+
161
+ // 1. Strip markdown code fences if wrapped in ```json ... ```
162
+ let without_fences = if let Some(stripped) = trimmed.strip_prefix("```json") {
163
+ stripped.strip_suffix("```").unwrap_or(stripped).trim()
164
+ } else if let Some(stripped) = trimmed.strip_prefix("```") {
165
+ stripped.strip_suffix("```").unwrap_or(stripped).trim()
166
+ } else {
167
+ trimmed
168
+ };
169
+ if let Ok(val) = serde_json::from_str::<Value>(without_fences) {
170
+ return Some(val);
171
+ }
172
+
173
+ // 2. Strip known special tokens (DeepSeek DSML markers, thinking tags, end of sentence markers)
174
+ let cleaned = without_fences
175
+ .replace("</||DSML||", "")
176
+ .replace("<||DSML||", "")
177
+ .replace("<|end of sentence|>", "")
178
+ .replace("</think>", "")
179
+ .replace("<think>", "");
180
+ if let Ok(val) = serde_json::from_str::<Value>(cleaned.trim()) {
181
+ return Some(val);
182
+ }
183
+
184
+ // 3. Fallback: extract substring between first '{' and last '}'
185
+ if let Some(start) = cleaned.find('{')
186
+ && let Some(end) = cleaned.rfind('}')
187
+ && start < end
188
+ {
189
+ let candidate = &cleaned[start..=end];
190
+ if let Ok(val) = serde_json::from_str::<Value>(candidate) {
191
+ return Some(val);
192
+ }
193
+ // 4. Handle trailing commas before closing braces/brackets
194
+ let stripped_commas = strip_json_trailing_commas(candidate);
195
+ if let Ok(val) = serde_json::from_str::<Value>(&stripped_commas) {
196
+ return Some(val);
197
+ }
198
+ }
199
+
200
+ None
201
+ }
202
+
203
+ pub(super) fn strip_json_trailing_commas(input: &str) -> String {
204
+ let mut out = String::with_capacity(input.len());
205
+ let mut in_string = false;
206
+ let mut escape = false;
207
+ let chars: Vec<char> = input.chars().collect();
208
+ let len = chars.len();
209
+
210
+ let mut i = 0;
211
+ while i < len {
212
+ let c = chars[i];
213
+ if in_string {
214
+ out.push(c);
215
+ if escape {
216
+ escape = false;
217
+ } else if c == '\\' {
218
+ escape = true;
219
+ } else if c == '"' {
220
+ in_string = false;
221
+ }
222
+ } else if c == '"' {
223
+ in_string = true;
224
+ out.push(c);
225
+ } else if c == ',' {
226
+ let mut j = i + 1;
227
+ while j < len && chars[j].is_whitespace() {
228
+ j += 1;
229
+ }
230
+ if j < len && (chars[j] == '}' || chars[j] == ']') {
231
+ // Skip the trailing comma
232
+ } else {
233
+ out.push(c);
234
+ }
235
+ } else {
236
+ out.push(c);
237
+ }
238
+ i += 1;
239
+ }
240
+ out
241
+ }
242
+
243
+ pub(super) fn parse_openai_style_reply(
244
+ model: String,
245
+ response: &Value,
246
+ ) -> Result<ProviderReply, ChatError> {
247
+ let message = &response["choices"][0]["message"];
248
+ let raw_text = message["content"].as_str().unwrap_or_default().to_string();
249
+ let direct_thought = message["reasoning_content"]
250
+ .as_str()
251
+ .or_else(|| message["reasoning"].as_str())
252
+ .or_else(|| message["thought"].as_str())
253
+ .or_else(|| message["thinking"].as_str())
254
+ .map(|s| s.trim().to_string())
255
+ .filter(|s| !s.is_empty());
256
+
257
+ let (thought, text) = if direct_thought.is_none() {
258
+ extract_think_tag(&raw_text)
259
+ } else {
260
+ (direct_thought, raw_text)
261
+ };
262
+
263
+ let tool_calls: Vec<ToolCall> = message["tool_calls"]
264
+ .as_array()
265
+ .map(|calls| {
266
+ calls
267
+ .iter()
268
+ .filter_map(|call| {
269
+ let name = call["function"]["name"].as_str()?.to_string();
270
+ let input = decode_tool_arguments(&call["function"]["arguments"]);
271
+ Some(ToolCall {
272
+ id: call["id"].as_str().unwrap_or_default().to_string(),
273
+ name,
274
+ input,
275
+ thought_signature: None,
276
+ })
277
+ })
278
+ .collect()
279
+ })
280
+ .unwrap_or_default();
281
+ if text.is_empty() && tool_calls.is_empty() {
282
+ if let Some(th) = &thought {
283
+ return Ok(ProviderReply {
284
+ model,
285
+ text: th.clone(),
286
+ thought: Some(th.clone()),
287
+ tool_calls: None,
288
+ stop_reason: response["choices"][0]["finish_reason"]
289
+ .as_str()
290
+ .map(str::to_owned),
291
+ input_tokens: response["usage"]["prompt_tokens"]
292
+ .as_u64()
293
+ .map(|n| n as u32),
294
+ output_tokens: response["usage"]["completion_tokens"]
295
+ .as_u64()
296
+ .map(|n| n as u32),
297
+ });
298
+ }
299
+ return Err(ChatError::MissingResponseText);
300
+ }
301
+ Ok(ProviderReply {
302
+ model,
303
+ text,
304
+ thought,
305
+ tool_calls: (!tool_calls.is_empty()).then_some(tool_calls),
306
+ stop_reason: response["choices"][0]["finish_reason"]
307
+ .as_str()
308
+ .map(str::to_owned),
309
+ input_tokens: response["usage"]["prompt_tokens"]
310
+ .as_u64()
311
+ .map(|n| n as u32),
312
+ output_tokens: response["usage"]["completion_tokens"]
313
+ .as_u64()
314
+ .map(|n| n as u32),
315
+ })
316
+ }
317
+
318
+ pub(super) fn openai_chat_payload(
319
+ config: &MintConfig,
320
+ model: &str,
321
+ request: &ChatRequest,
322
+ stream: bool,
323
+ ) -> Result<Value, ChatError> {
324
+ let provider = config.ai_provider.trim().to_ascii_lowercase();
325
+ let effective_sys = effective_system_instruction(config, request, &provider, model);
326
+ let messages = if let Some(messages) = &request.messages {
327
+ let mut built = Vec::new();
328
+ if !effective_sys.is_empty() && !messages.iter().any(|m| m.role == ChatRole::System) {
329
+ built.push(json!({ "role": "system", "content": effective_sys }));
330
+ }
331
+ built.extend(openai_native_messages(messages)?);
332
+ built
333
+ } else {
334
+ vec![
335
+ json!({ "role": "system", "content": effective_sys }),
336
+ json!({
337
+ "role": "user",
338
+ "content": openai_message_content(
339
+ &request.message,
340
+ request.image_data_uri.as_deref(),
341
+ request.audio_data_uri.as_deref(),
342
+ )?,
343
+ }),
344
+ ]
345
+ };
346
+ let max_tokens = {
347
+ let is_groq = config
348
+ .resolve_custom_provider(&config.ai_provider)
349
+ .map(|cp| cp.base_url.contains("groq.com"))
350
+ .unwrap_or(false);
351
+ if is_groq {
352
+ if request.tools.is_some() { 2048 } else { 4096 }
353
+ } else {
354
+ 8192
355
+ }
356
+ };
357
+ let mut payload = json!({
358
+ "model": model,
359
+ "stream": stream,
360
+ "messages": messages,
361
+ "max_tokens": max_tokens,
362
+ });
363
+ if stream {
364
+ payload["stream_options"] = json!({ "include_usage": true });
365
+ }
366
+ let clean_model = model
367
+ .trim()
368
+ .to_ascii_lowercase()
369
+ .rsplit('/')
370
+ .next()
371
+ .unwrap_or("")
372
+ .to_string();
373
+ let is_openai_reasoning = clean_model.starts_with("o1")
374
+ || clean_model.starts_with("o3")
375
+ || clean_model.starts_with("o4")
376
+ || clean_model.starts_with("gpt-5");
377
+ let is_deepseek_native =
378
+ provider == "deepseek" && MintConfig::has_native_thinking_api("deepseek", model);
379
+ let is_openrouter_native =
380
+ provider == "openrouter" && MintConfig::has_native_thinking_api("openrouter", model);
381
+
382
+ let (thinking_enabled, thinking_effort) = effective_thinking(config, request, model);
383
+ if is_deepseek_native {
384
+ if thinking_enabled {
385
+ let effort_str = match thinking_effort.as_str() {
386
+ "low" => "low",
387
+ "medium" => "medium",
388
+ "high" => "high",
389
+ "extra_high" => "max",
390
+ _ => "high",
391
+ };
392
+ payload["reasoning_effort"] = json!(effort_str);
393
+ payload["thinking"] = json!({ "type": "enabled" });
394
+ } else {
395
+ payload["reasoning_effort"] = json!("none");
396
+ payload["thinking"] = json!({ "type": "disabled" });
397
+ payload["temperature"] = json!(effective_temperature(config, request));
398
+ }
399
+ } else if is_openrouter_native {
400
+ if thinking_enabled {
401
+ let effort_str = match thinking_effort.as_str() {
402
+ "low" => "low",
403
+ "medium" => "medium",
404
+ "high" => "high",
405
+ "extra_high" => "xhigh",
406
+ _ => "medium",
407
+ };
408
+ payload["reasoning"] = json!({ "effort": effort_str });
409
+ } else {
410
+ payload["reasoning"] = json!({ "exclude": true });
411
+ }
412
+ if !is_openai_reasoning {
413
+ payload["temperature"] = json!(effective_temperature(config, request));
414
+ }
415
+ } else if is_openai_reasoning {
416
+ if thinking_enabled {
417
+ let effort_str = match thinking_effort.as_str() {
418
+ "low" => "low",
419
+ "medium" => "medium",
420
+ "high" | "extra_high" => "high",
421
+ _ => "medium",
422
+ };
423
+ payload["reasoning_effort"] = json!(effort_str);
424
+ } else {
425
+ payload["reasoning_effort"] = json!("low");
426
+ }
427
+ } else {
428
+ payload["temperature"] = json!(effective_temperature(config, request));
429
+ }
430
+ if wants_agent_json(request) {
431
+ payload["response_format"] = json!({ "type": "json_object" });
432
+ }
433
+ if let Some(tools) = &request.tools {
434
+ payload["tools"] = json!(
435
+ tools
436
+ .iter()
437
+ .map(|tool| json!({
438
+ "type": "function",
439
+ "function": {
440
+ "name": tool.name,
441
+ "description": tool.description,
442
+ "parameters": tool.parameters,
443
+ }
444
+ }))
445
+ .collect::<Vec<_>>()
446
+ );
447
+ }
448
+ Ok(payload)
449
+ }
450
+
451
+ pub(super) fn openai_native_messages(messages: &[ChatMessage]) -> Result<Vec<Value>, ChatError> {
452
+ let mut result = Vec::new();
453
+ for message in messages {
454
+ match message.role {
455
+ ChatRole::System => {
456
+ result.push(json!({ "role": "system", "content": super::message_text(message) }));
457
+ }
458
+ ChatRole::User => {
459
+ result.push(json!({ "role": "user", "content": openai_user_content(message)? }));
460
+ }
461
+ ChatRole::Assistant => {
462
+ let text = super::message_text(message);
463
+ let tool_calls: Vec<Value> = message
464
+ .content
465
+ .iter()
466
+ .filter_map(|block| match block {
467
+ ContentBlock::ToolUse {
468
+ id, name, input, ..
469
+ } => Some(json!({
470
+ "id": id,
471
+ "type": "function",
472
+ "function": { "name": name, "arguments": input.to_string() }
473
+ })),
474
+ _ => None,
475
+ })
476
+ .collect();
477
+ let mut entry = json!({ "role": "assistant" });
478
+ entry["content"] = if text.is_empty() {
479
+ Value::Null
480
+ } else {
481
+ json!(text)
482
+ };
483
+ if !tool_calls.is_empty() {
484
+ entry["tool_calls"] = json!(tool_calls);
485
+ entry["reasoning_content"] = json!("");
486
+ }
487
+ result.push(entry);
488
+ }
489
+ ChatRole::Tool => {
490
+ let mut pending_images: Vec<&str> = Vec::new();
491
+ for block in &message.content {
492
+ match block {
493
+ ContentBlock::ToolResult {
494
+ tool_use_id,
495
+ content,
496
+ ..
497
+ } => {
498
+ result.push(json!({
499
+ "role": "tool",
500
+ "tool_call_id": tool_use_id,
501
+ "content": content,
502
+ }));
503
+ }
504
+ ContentBlock::Image { data_uri } => pending_images.push(data_uri),
505
+ _ => {}
506
+ }
507
+ }
508
+ if !pending_images.is_empty() {
509
+ let parts: Vec<Value> = pending_images
510
+ .iter()
511
+ .map(|data_uri| {
512
+ json!({ "type": "image_url", "image_url": { "url": data_uri } })
513
+ })
514
+ .collect();
515
+ result.push(json!({ "role": "user", "content": parts }));
516
+ }
517
+ }
518
+ }
519
+ }
520
+ Ok(result)
521
+ }
522
+
523
+ pub(super) fn openai_message_content(
524
+ text: &str,
525
+ image_data_uri: Option<&str>,
526
+ audio_data_uri: Option<&str>,
527
+ ) -> Result<Value, ChatError> {
528
+ if image_data_uri.is_none() && audio_data_uri.is_none() {
529
+ return Ok(json!(text));
530
+ }
531
+ let mut parts = vec![json!({ "type": "text", "text": text })];
532
+ for img in image_data_uri.into_iter().flat_map(str::split_whitespace) {
533
+ parts.push(json!({ "type": "image_url", "image_url": { "url": img } }));
534
+ }
535
+ for aud in audio_data_uri.into_iter().flat_map(str::split_whitespace) {
536
+ parts.push(openai_audio_part(aud)?);
537
+ }
538
+ Ok(json!(parts))
539
+ }
540
+
541
+ pub(super) fn openai_user_content(message: &ChatMessage) -> Result<Value, ChatError> {
542
+ if let [ContentBlock::Text { text }] = message.content.as_slice() {
543
+ return Ok(json!(text));
544
+ }
545
+ let parts: Vec<Value> = message
546
+ .content
547
+ .iter()
548
+ .map(|block| -> Result<Value, ChatError> {
549
+ Ok(match block {
550
+ ContentBlock::Text { text } => json!({ "type": "text", "text": text }),
551
+ ContentBlock::Image { data_uri } => {
552
+ json!({ "type": "image_url", "image_url": { "url": data_uri } })
553
+ }
554
+ ContentBlock::Audio { data_uri } => openai_audio_part(data_uri)?,
555
+ ContentBlock::ToolUse { .. }
556
+ | ContentBlock::ToolResult { .. }
557
+ | ContentBlock::Video { .. } => Value::Null,
558
+ })
559
+ })
560
+ .filter_map(|part| match part {
561
+ Ok(Value::Null) => None,
562
+ other => Some(other),
563
+ })
564
+ .collect::<Result<_, _>>()?;
565
+ Ok(json!(parts))
566
+ }
567
+
568
+ pub(super) fn openai_audio_part(data_uri: &str) -> Result<Value, ChatError> {
569
+ let (mime_type, data) = split_data_uri(data_uri, "audio/")?;
570
+ Ok(json!({
571
+ "type": "input_audio",
572
+ "input_audio": { "data": data, "format": openai_audio_format(&mime_type) }
573
+ }))
574
+ }
575
+
576
+ pub(super) fn openai_audio_format(mime_type: &str) -> &'static str {
577
+ if mime_type.contains("wav") {
578
+ "wav"
579
+ } else {
580
+ "mp3"
581
+ }
582
+ }