@pheem49/mint 1.16.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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +2 -2
- package/README.md +8 -2
- package/Release_Note.md +222 -72
- package/apps/mint-companion/src-tauri/src/compact.rs +69 -0
- package/apps/mint-companion/src-tauri/src/main.rs +218 -11
- package/apps/mint-companion/vite.config.ts +2 -1
- package/crates/mint-cli/src/agent/diff_render.rs +66 -44
- package/crates/mint-cli/src/agent/live_status.rs +64 -6
- package/crates/mint-cli/src/agent/markdown_render.rs +342 -5
- package/crates/mint-cli/src/agent.rs +202 -47
- package/crates/mint-cli/src/commands/agent.rs +223 -117
- package/crates/mint-cli/src/commands/browser.rs +114 -0
- package/crates/mint-cli/src/commands/config.rs +14 -1
- package/crates/mint-cli/src/commands/integrations.rs +3 -3
- package/crates/mint-cli/src/commands/knowledge.rs +16 -2
- package/crates/mint-cli/src/commands/mod.rs +15 -4
- package/crates/mint-cli/src/commands/tailnet_web.rs +3 -7
- package/crates/mint-cli/src/companion_navigation.rs +268 -0
- package/crates/mint-cli/src/interactive/chat_tui/dialog.rs +572 -0
- package/crates/mint-cli/src/interactive/chat_tui/mod.rs +1427 -0
- package/crates/mint-cli/src/interactive/chat_tui/render.rs +1524 -0
- package/crates/mint-cli/src/interactive/chat_tui/selection.rs +401 -0
- package/crates/mint-cli/src/interactive/chat_tui/state.rs +1833 -0
- package/crates/mint-cli/src/interactive/chat_tui/tests.rs +3414 -0
- package/crates/mint-cli/src/interactive/chat_tui/thought.rs +202 -0
- package/crates/mint-cli/src/interactive/chat_tui/transcript.rs +728 -0
- package/crates/mint-cli/src/interactive/chat_tui/watermark.rs +148 -0
- package/crates/mint-cli/src/interactive/command_ui.rs +92 -5
- package/crates/mint-cli/src/interactive/composer.rs +186 -0
- package/crates/mint-cli/src/interactive/format.rs +78 -3
- package/crates/mint-cli/src/interactive/input_box.rs +61 -1
- package/crates/mint-cli/src/interactive/resume_picker.rs +367 -25
- package/crates/mint-cli/src/interactive/slash_commands.rs +758 -373
- package/crates/mint-cli/src/interactive/tool_activity.rs +468 -56
- package/crates/mint-cli/src/interactive.rs +74 -5
- package/crates/mint-cli/src/main.rs +26 -2
- package/crates/mint-cli/src/markdown.rs +2 -37
- package/crates/mint-cli/src/onboard.rs +3897 -1321
- package/crates/mint-cli/src/setup.rs +1342 -624
- package/crates/mint-cli/src/terminal_theme.rs +159 -35
- package/crates/mint-cli/tests/library_intake.rs +1 -0
- package/crates/mint-cli/tests/mods_testing.rs +5 -1
- package/crates/mint-companion-protocol/src/lib.rs +113 -0
- package/crates/mint-core/src/agent/chat/anthropic.rs +318 -0
- package/crates/mint-core/src/agent/chat/custom.rs +149 -0
- package/crates/mint-core/src/agent/chat/gemini.rs +518 -0
- package/crates/mint-core/src/agent/chat/mod.rs +857 -0
- package/crates/mint-core/src/agent/chat/ollama.rs +321 -0
- package/crates/mint-core/src/agent/chat/openai.rs +582 -0
- package/crates/mint-core/src/agent/chat/streaming.rs +469 -0
- package/crates/mint-core/src/agent/chat/tests.rs +1479 -0
- package/crates/mint-core/src/agent/memory.rs +129 -8
- package/crates/mint-core/src/agent/safety.rs +56 -4
- package/crates/mint-core/src/agent/skill_inventory.rs +3 -1
- package/crates/mint-core/src/agent/skills.rs +1 -0
- package/crates/mint-core/src/agent/tasks.rs +2 -2
- package/crates/mint-core/src/api_server/routes/auth.rs +1 -4
- package/crates/mint-core/src/api_server/routes/chat.rs +53 -17
- package/crates/mint-core/src/api_server/routes/gemini_live.rs +14 -1
- package/crates/mint-core/src/api_server/routes/html_preview.rs +1 -8
- package/crates/mint-core/src/api_server/routes/misc.rs +116 -0
- package/crates/mint-core/src/api_server/routes/profile_oauth.rs +43 -26
- package/crates/mint-core/src/api_server/routes/sessions.rs +114 -76
- package/crates/mint-core/src/api_server/routes/skills_subagents.rs +11 -7
- package/crates/mint-core/src/api_server/routes/status_health.rs +18 -8
- package/crates/mint-core/src/api_server.rs +211 -65
- package/crates/mint-core/src/avatar_bridge.rs +5 -24
- package/crates/mint-core/src/browser/cdp.rs +92 -30
- package/crates/mint-core/src/browser/headless.rs +582 -0
- package/crates/mint-core/src/browser/input.rs +7 -62
- package/crates/mint-core/src/browser/interact.rs +0 -14
- package/crates/mint-core/src/browser/mod.rs +10 -30
- package/crates/mint-core/src/browser/navigate.rs +83 -44
- package/crates/mint-core/src/browser/runtime.rs +282 -0
- package/crates/mint-core/src/browser/session.rs +240 -400
- package/crates/mint-core/src/browser/workflow.rs +496 -0
- package/crates/mint-core/src/companion/navigation.rs +115 -0
- package/crates/mint-core/src/companion.rs +109 -1
- package/crates/mint-core/src/cron/store.rs +2 -2
- package/crates/mint-core/src/integrations/mcp.rs +6 -8
- package/crates/mint-core/src/integrations/mcp_result.rs +45 -0
- package/crates/mint-core/src/lib.rs +10 -11
- package/crates/mint-core/src/media/gemini_live.rs +636 -86
- package/crates/mint-core/src/media/image_proxy.rs +563 -0
- package/crates/mint-core/src/media/library/migration.rs +26 -0
- package/crates/mint-core/src/media/library/tests.rs +1 -1
- package/crates/mint-core/src/media/library/thumbnails.rs +8 -2
- package/crates/mint-core/src/media/pictures.rs +1 -1
- package/crates/mint-core/src/media/tool_images.rs +287 -0
- package/crates/mint-core/src/media/tts/playback.rs +609 -0
- package/crates/mint-core/src/media/tts.rs +1 -0
- package/crates/mint-core/src/media.rs +2 -0
- package/crates/mint-core/src/mods/manager.rs +101 -35
- package/crates/mint-core/src/orchestration/context_render.rs +878 -44
- package/crates/mint-core/src/orchestration/decision_parsing.rs +536 -39
- package/crates/mint-core/src/orchestration/diagnostics.rs +307 -0
- package/crates/mint-core/src/orchestration/full_skill_result_tests.rs +12 -0
- package/crates/mint-core/src/orchestration/image_regressions.rs +379 -0
- package/crates/mint-core/src/orchestration/loop_guard.rs +479 -0
- package/crates/mint-core/src/orchestration/mcp_outcome_tests.rs +864 -0
- package/crates/mint-core/src/orchestration/memory_skill.rs +88 -28
- package/crates/mint-core/src/orchestration/mod.rs +859 -4244
- package/crates/mint-core/src/orchestration/reliability.rs +581 -0
- package/crates/mint-core/src/orchestration/repeated_failures.rs +8 -0
- package/crates/mint-core/src/orchestration/review_regressions.rs +179 -28
- package/crates/mint-core/src/orchestration/tests.rs +1665 -0
- package/crates/mint-core/src/orchestration/tools/dispatch.rs +967 -0
- package/crates/mint-core/src/orchestration/tools/files.rs +32 -3
- package/crates/mint-core/src/orchestration/tools/mod.rs +5 -1
- package/crates/mint-core/src/orchestration/tools/planning.rs +102 -42
- package/crates/mint-core/src/orchestration/tools/plugins_mcp.rs +19 -4
- package/crates/mint-core/src/orchestration/tools/project.rs +1 -1
- package/crates/mint-core/src/orchestration/tools/shell.rs +157 -62
- package/crates/mint-core/src/orchestration/tools/web.rs +5 -1
- package/crates/mint-core/src/orchestration/trajectory_schema.rs +353 -0
- package/crates/mint-core/src/orchestration/truncation.rs +368 -0
- package/crates/mint-core/src/orchestration/types.rs +648 -0
- package/crates/mint-core/src/orchestration/verification.rs +36 -9
- package/crates/mint-core/src/orchestration/workspace_helpers.rs +76 -27
- package/crates/mint-core/src/prompts/agent.rs +69 -102
- package/crates/mint-core/src/prompts/tool_catalog.rs +104 -153
- package/crates/mint-core/src/search/files.rs +20 -0
- package/crates/mint-core/src/search/web_search.rs +44 -3
- package/crates/mint-core/src/slash/mod.rs +481 -55
- package/crates/mint-core/src/slash/models.rs +6 -1
- package/crates/mint-core/src/swarm/batch.rs +643 -0
- package/crates/mint-core/src/swarm/dossier.rs +152 -0
- package/crates/mint-core/src/swarm/labels.rs +100 -0
- package/crates/mint-core/src/swarm/mod.rs +158 -0
- package/crates/mint-core/src/swarm/permissions.rs +167 -0
- package/crates/mint-core/src/system/auth.rs +156 -7
- package/crates/mint-core/src/system/bg_shell.rs +78 -9
- package/crates/mint-core/src/system/config.rs +266 -35
- package/crates/mint-core/src/system/mint_web.rs +33 -12
- package/crates/mint-core/src/system/shell.rs +179 -10
- package/crates/mint-core/src/system/workspace.rs +204 -0
- package/crates/mint-core/tests/background_retention.rs +1 -1
- package/crates/mint-core/tests/memory_persistence.rs +27 -0
- package/crates/mint-mod-runtime/src/lib.rs +37 -23
- package/out/web/assets/ArtifactPreviewPanel-Cx6BVOOF.js +2 -0
- package/out/web/assets/ArtifactPreviewPanel-DAT_Y7i0.css +1 -0
- package/out/web/assets/CalculationCard-D2E74u5q.js +1 -0
- package/out/web/assets/CliSessionsView-ZbdtSjvK.css +1 -0
- package/out/web/assets/CliSessionsView-z5i6qGyb.js +1 -0
- package/out/web/assets/CodeReviewPage-BK2bOScd.css +1 -0
- package/out/web/assets/CodeReviewPage-Dp6zHuHu.js +2 -0
- package/out/web/assets/CommandPalette-DaKlzbKs.js +1 -0
- package/out/web/assets/ImageGenCard-BMCJHP9O.js +1 -0
- package/out/web/assets/ImageSearchCard-Cld_SOUg.js +1 -0
- package/out/web/assets/ImageStudioPanel-B5N7TjeB.js +1 -0
- package/out/web/assets/LibraryPage-CMB89rJ0.css +1 -0
- package/out/web/assets/LibraryPage-DbZEi4W_.js +2 -0
- package/out/web/assets/LinkedFoldersView-9B9DIPoZ.js +1 -0
- package/out/web/assets/McpRegistryPicker-DnXPlzI7.js +1 -0
- package/out/web/assets/McpServersView-CiNBNUl4.js +1 -0
- package/out/web/assets/MermaidCard-BBDNEXbY.js +2 -0
- package/out/web/assets/MintDashboard-C_UYPoNV.js +119 -0
- package/out/web/assets/MintDashboard-DTd8C_c8.css +1 -0
- package/out/web/assets/PlanCard-C2x5pajq.js +1 -0
- package/out/web/assets/PluginsView-DoTg9MVa.js +1 -0
- package/out/web/assets/RewindModal-B5oowrR0.js +1 -0
- package/out/web/assets/ScheduledTasksView-BD5DVPkZ.js +1 -0
- package/out/web/assets/SelectField-Cy6CghrU.js +1 -0
- package/out/web/assets/SelectField-lFEkkWCU.css +1 -0
- package/out/web/assets/SettingsWindow-BYl_oCFh.js +4 -0
- package/out/web/assets/SettingsWindow-Ckcgnjt8.css +1 -0
- package/out/web/assets/SkillsView-CIeWwq5z.js +2 -0
- package/out/web/assets/StockCard-DxUciKyp.js +1 -0
- package/out/web/assets/ToolSurfacePage-qBgs_VHq.css +1 -0
- package/out/web/assets/ToolSurfacePage-saVbVrkE.js +38 -0
- package/out/web/assets/UiFeatureCard-CdY0hGWy.js +1 -0
- package/out/web/assets/UiGridCard-BBK3I5hC.js +1 -0
- package/out/web/assets/UiMockupWidget-CpR9EDuc.js +1 -0
- package/out/web/assets/{VeoStudioPanel-DOj7_WR3.css → VeoStudioPanel-9kU9YLEN.css} +1 -1
- package/out/web/assets/VeoStudioPanel-Co6-wEWM.js +4 -0
- package/out/web/assets/WeatherCard-Buzi7Vfu.js +1 -0
- package/out/web/assets/WorkspacePanel-BfHKkulU.js +1 -0
- package/out/web/assets/WorkspacePdfPreview-Bi8Zzyir.js +2 -0
- package/out/web/assets/abnfDiagram-VCTEODGH-CMfR95N2.js +1 -0
- package/out/web/assets/architecture-7GRP2DOG-DhAM4HyO.js +1 -0
- package/out/web/assets/architectureDiagram-5GKGNRK7-Yk3TUJ_F.js +36 -0
- package/out/web/assets/{blockDiagram-NRAW4CY4-BrrqzHW5.js → blockDiagram-I7D4REHJ-X9fBgJik.js} +4 -4
- package/out/web/assets/c4Diagram-7LVT6UL2-Bk9x7N-8.js +38 -0
- package/out/web/assets/chunk-2Q5K7J3B-D2JLPQzF.js +1 -0
- package/out/web/assets/{chunk-TEH6E4GO-G4Cdlyrt.js → chunk-4HAMMTFA-Dx5fk1_0.js} +3 -3
- package/out/web/assets/{chunk-5VM5RSS4-BiPuE0io.js → chunk-5VM5RSS4-BZ2oBPYQ.js} +1 -1
- package/out/web/assets/chunk-75Z2AOVW-BszTQoM4.js +2 -0
- package/out/web/assets/chunk-DU6HZSFF-DK-As3rk.js +128 -0
- package/out/web/assets/chunk-F27PBJKO-LmncZZIS.js +1 -0
- package/out/web/assets/chunk-FOHPRMQF-JXEeeJ9r.js +161 -0
- package/out/web/assets/{chunk-GMAD6QVW-BuhEcgHq.js → chunk-GMAD6QVW-DSQ_sDRJ.js} +12 -12
- package/out/web/assets/chunk-GVQU2GXP-Cno3ulje.js +1 -0
- package/out/web/assets/chunk-IMKFNOWR-bIzfY9Jc.js +231 -0
- package/out/web/assets/chunk-JWPE2WC7-BeEMUFDw.js +1 -0
- package/out/web/assets/chunk-L3NEJ4N5-Dl6eOa0D.js +1 -0
- package/out/web/assets/chunk-OSK3NFVY-BaUGJ-cM.js +10 -0
- package/out/web/assets/chunk-P2QGCYS3-_066ndhH.js +1 -0
- package/out/web/assets/chunk-POPQ4Y6H-YT1AKhTv.js +1 -0
- package/out/web/assets/chunk-PWAF6VOD-CrEgDsxh.js +1 -0
- package/out/web/assets/chunk-SHT3W25Y-_KwaRLOl.js +168 -0
- package/out/web/assets/{chunk-SVP7TREG-Bu_yYb-V.js → chunk-SVP7TREG-BQ1qb32L.js} +1 -1
- package/out/web/assets/{chunk-LCL6LL3I-BhXV9zyH.js → chunk-TICWLB2K-BDyehCtg.js} +4 -4
- package/out/web/assets/chunk-XXDRQBXY-B9YlhOkE.js +1 -0
- package/out/web/assets/chunk-Y2CYZVJY-BxCWjNVG.js +1 -0
- package/out/web/assets/classDiagram-ZZMXUADV-BoSpzRCJ.js +1 -0
- package/out/web/assets/classDiagram-v2-VYDZK3BY-BoSpzRCJ.js +1 -0
- package/out/web/assets/config-2suVFPZg.css +1 -0
- package/out/web/assets/config-CkRCdkxn.js +2 -0
- package/out/web/assets/core-Mi3s5D4_.js +1 -0
- package/out/web/assets/cose-bilkent-JH36ORCC-qlna31ty.js +1 -0
- package/out/web/assets/cynefin-OW5HDTMX-Dl_31aaN.js +1 -0
- package/out/web/assets/cynefinDiagram-5FMLGOSQ-Bpir8jha.js +62 -0
- package/out/web/assets/cytoscape.esm-DWtAUG0r.js +321 -0
- package/out/web/assets/dagre-CnWJJM5f.js +1 -0
- package/out/web/assets/dagre-GXQ25YYZ-D9xu3nsq.js +4 -0
- package/out/web/assets/diagram-S7CK7UJ4-DeveoEeB.js +30 -0
- package/out/web/assets/diagram-UQ7AKVKN-p4xWRESV.js +41 -0
- package/out/web/assets/{diagram-VSXAHHWV-BJ86Ov2Y.js → diagram-VSXAHHWV-C7qz9NDF.js} +1 -1
- package/out/web/assets/diagram-VX7I27RA-DRiCa3Vd.js +24 -0
- package/out/web/assets/diagram-Z3DM3KII-Bt7zduQX.js +24 -0
- package/out/web/assets/dist-9ISJ7r-g.js +1 -0
- package/out/web/assets/dist-js-BV3mI7uw.js +1 -0
- package/out/web/assets/ebnfDiagram-PWID7BFC-DV9WpwdR.js +1 -0
- package/out/web/assets/{erDiagram-SSCWMZ5O-CzzmnZWT.js → erDiagram-RLTQ6QDP-BJ_nwG2m.js} +3 -3
- package/out/web/assets/event-D7D-EBy9.js +1 -0
- package/out/web/assets/eventmodeling-NTZA5JFV-BTBMrfVa.js +1 -0
- package/out/web/assets/flowDiagram-HODETNUW-Cdlq3cJL.js +1 -0
- package/out/web/assets/ganttDiagram-EL5Y4UJY-DQLdsXrC.js +292 -0
- package/out/web/assets/gitGraph-4MIJSDKK-CL1OEOQ_.js +1 -0
- package/out/web/assets/gitGraphDiagram-WWUBYQGX-8cSi42H7.js +106 -0
- package/out/web/assets/graphlib-Bh9gHply.js +1 -0
- package/out/web/assets/index-BocxnrDG.js +168 -0
- package/out/web/assets/index-yeh_r55a.css +1 -0
- package/out/web/assets/info-A6RAGUB7-C1zsX5sc.js +1 -0
- package/out/web/assets/infoDiagram-27XIBGKW-DsRYtIyI.js +2 -0
- package/out/web/assets/{ishikawaDiagram-5VMMS53U-s38yo28y.js → ishikawaDiagram-5VMMS53U-B2crwIXj.js} +4 -4
- package/out/web/assets/journeyDiagram-3NMN7TZE-BTH-GyVR.js +139 -0
- package/out/web/assets/jsx-runtime-DmUwWVTj.js +1 -0
- package/out/web/assets/{kanban-definition-3QL26DDD-DodjgySL.js → kanban-definition-UXKFOSKX-BeRfCnuA.js} +6 -6
- package/out/web/assets/katex-MCdQaEAD.js +257 -0
- package/out/web/assets/mermaid-parser.core-BJwgCBBc.js +7 -0
- package/out/web/assets/mermaid.core-CsDDodN8.js +44 -0
- package/out/web/assets/{mindmap-definition-FBJOCRG2-260WBDxZ.js → mindmap-definition-YA3MSWOX-B42efOQ1.js} +4 -4
- package/out/web/assets/modelManager-B3ZFMh8v.js +2 -0
- package/out/web/assets/oauthManager-Ch5Du2fF.js +1 -0
- package/out/web/assets/packet-AYTQ26CC-B6xUyE6r.js +1 -0
- package/out/web/assets/pdf-Bcahc2yC.js +56 -0
- package/out/web/assets/pdf.worker.min-Dkey6ZUl.mjs +27 -0
- package/out/web/assets/pegDiagram-XKGWAZYB-1LabVycL.js +1 -0
- package/out/web/assets/pie-WAS4IAKB-DJwo-nf_.js +1 -0
- package/out/web/assets/pieDiagram-E7YTZNPT-icmAqzy4.js +39 -0
- package/out/web/assets/{quadrantDiagram-AXDQQJYC-B8D3gU5d.js → quadrantDiagram-AXDQQJYC-CZ-wEx41.js} +4 -4
- package/out/web/assets/radar-RG4KPBEZ-D7d-Xriw.js +1 -0
- package/out/web/assets/railroad-74A4TZTK-viuhdYbs.js +1 -0
- package/out/web/assets/railroad-abnf-HS5TGJTU-DNXiCNea.js +1 -0
- package/out/web/assets/railroad-ebnf-LZEXJU2U-nkSu1-0C.js +1 -0
- package/out/web/assets/railroad-peg-WCYAUIDC-BhbMeQzz.js +1 -0
- package/out/web/assets/railroadDiagram-O6MQD6OU-CLpG1jUX.js +1 -0
- package/out/web/assets/{requirementDiagram-EFPCY7ZU-D2X-Ewik.js → requirementDiagram-BXWQKSXE-kRDrvWrc.js} +3 -3
- package/out/web/assets/rolldown-runtime-CNOWq4Kh.js +1 -0
- package/out/web/assets/rough.esm-CNnFptCR.js +1 -0
- package/out/web/assets/sankeyDiagram-P5KCCOFB-DxAwmq4x.js +40 -0
- package/out/web/assets/sequenceDiagram-WJ2MYXX4-aLgY-6Wz.js +162 -0
- package/out/web/assets/sizeCapture-INFHLROL-C8Q8DbV5.js +1 -0
- package/out/web/assets/src-CRDfRlHM.js +1 -0
- package/out/web/assets/stateDiagram-D77RDMKH-D5QWfOhp.js +1 -0
- package/out/web/assets/stateDiagram-v2-MP3YSRHH-D6OiZoAP.js +1 -0
- package/out/web/assets/studio-BBZQ_-QK.js +1 -0
- package/out/web/assets/swimlanes-42K2YHIH-CRs5UyA-.js +1 -0
- package/out/web/assets/swimlanesDiagram-VR7AAH4N-UgEXIsV0.js +8 -0
- package/out/web/assets/syntaxHighlight-C-wAGT84.js +3 -0
- package/out/web/assets/timeline-definition-24CTP7MA-Denqd9qb.js +120 -0
- package/out/web/assets/treeView-Q6P3EWNA-0my3Z3s4.js +1 -0
- package/out/web/assets/treemap-WGGIJYW6-B_EjkLYq.js +1 -0
- package/out/web/assets/vennDiagram-4TSXK5OY-DxVg3Ewr.js +34 -0
- package/out/web/assets/wardley-WFR3VGLG-CGD-Tj4p.js +1 -0
- package/out/web/assets/{wardleyDiagram-VM6X3IG4-DAdXELbQ.js → wardleyDiagram-VM6X3IG4-CmfS39Xm.js} +35 -35
- package/out/web/assets/window-CkUOP7t0.js +1 -0
- package/out/web/assets/workspaces-SYPrmbgC.js +1 -0
- package/out/web/assets/{xychartDiagram-S5SC5T6Z-IZIXf3Vo.js → xychartDiagram-S5SC5T6Z-BVM9bKis.js} +4 -4
- package/out/web/index-web.html +4 -4
- package/out/web/index.html +4 -4
- package/out/web/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/out/web/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/out/web/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/out/web/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/out/web/pdfjs/cmaps/LICENSE +36 -0
- package/out/web/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/Roman.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/out/web/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/V.bcmap +0 -0
- package/out/web/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/out/web/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/out/web/pdfjs/standard_fonts/LICENSE_LIBERATION +363 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/out/web/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/out/web/pdfjs/wasm/LICENSE_JBIG2 +196 -0
- package/out/web/pdfjs/wasm/LICENSE_OPENJPEG +39 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_JBIG2 +13 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_OPENJPEG +22 -0
- package/out/web/pdfjs/wasm/LICENSE_PDFJS_QCMS +19 -0
- package/out/web/pdfjs/wasm/LICENSE_QCMS +21 -0
- package/out/web/pdfjs/wasm/jbig2.wasm +0 -0
- package/out/web/pdfjs/wasm/jbig2_nowasm_fallback.js +15 -0
- package/out/web/pdfjs/wasm/openjpeg.wasm +0 -0
- package/out/web/pdfjs/wasm/openjpeg_nowasm_fallback.js +17 -0
- package/out/web/pdfjs/wasm/qcms_bg.wasm +0 -0
- package/out/web/pdfjs/wasm/quickjs-eval.js +16 -0
- package/out/web/pdfjs/wasm/quickjs-eval.wasm +0 -0
- package/package.json +6 -4
- package/shared-modules/CODEOWNERS +3 -3
- package/shared-modules/SDL2/SDL2_mixer.json +27 -0
- package/shared-modules/appstream/README.md +13 -0
- package/shared-modules/appstream/appstream-compose.json +111 -0
- package/shared-modules/dbus-glib/dbus-glib.json +2 -2
- package/shared-modules/libportal/libportal.json +23 -0
- package/shared-modules/linux-audio/fluidsynth2-static.json +2 -2
- package/shared-modules/linux-audio/fluidsynth2.json +2 -2
- package/shared-modules/linux-audio/libinstpatch.json +2 -2
- package/shared-modules/linux-audio/liblo-static.json +3 -0
- package/shared-modules/luajit/luajit.json +2 -2
- package/shared-modules/squashfs-tools/squashfs-tools.json +0 -3
- package/slash-commands.json +14 -3
- package/src/bin/index.js +4 -1
- package/src/bin/pdfjs-assets.mjs +31 -0
- package/src/renderer/node_modules/.vite/vitest/CACHEDIR.TAG +3 -0
- package/src/renderer/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/src/renderer/shared/agentProgress.ts +62 -5
- package/src/renderer/shared/components/Settings/AgentsTab.tsx +70 -6
- package/src/renderer/shared/components/Settings/AudioTab.tsx +1 -1
- package/src/renderer/shared/components/Settings/AutomationTab.tsx +32 -29
- package/src/renderer/shared/components/Settings/AvatarCropModal.tsx +32 -12
- package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.test.tsx +65 -0
- package/src/renderer/shared/components/Settings/CustomProviderLogoPicker.tsx +456 -0
- package/src/renderer/shared/components/Settings/GeneralTab.tsx +65 -94
- package/src/renderer/shared/components/Settings/PluginsTab.tsx +101 -24
- package/src/renderer/shared/components/Settings/ProfileTab.tsx +1 -1
- package/src/renderer/shared/components/Settings/SettingsWindow.tsx +796 -0
- package/src/renderer/shared/components/Settings/ThemeTab.test.tsx +45 -0
- package/src/renderer/shared/components/Settings/ThemeTab.tsx +119 -4
- package/src/renderer/shared/components/activity/AgentActivityDrawer.tsx +270 -0
- package/src/renderer/shared/components/activity/AgentActivityTable.tsx +582 -0
- package/src/renderer/shared/components/activity/AskUserActivityCard.test.tsx +122 -0
- package/src/renderer/shared/components/activity/AskUserActivityCard.tsx +70 -0
- package/src/renderer/shared/components/{McpToolResults.tsx → activity/McpToolResults.tsx} +69 -13
- package/src/renderer/shared/components/{PlanChecklistWidget.tsx → activity/PlanChecklistWidget.tsx} +2 -2
- package/src/renderer/shared/components/activity/RunSummaryDashboard.test.tsx +127 -0
- package/src/renderer/shared/components/activity/RunSummaryDashboard.tsx +222 -0
- package/src/renderer/shared/components/activity/SubagentDagView.test.ts +123 -0
- package/src/renderer/shared/components/{SubagentDagView.tsx → activity/SubagentDagView.tsx} +104 -50
- package/src/renderer/shared/components/activity/SubagentStatusStrip.test.tsx +82 -0
- package/src/renderer/shared/components/activity/SubagentStatusStrip.tsx +107 -0
- package/src/renderer/shared/components/activity/ViewedImages.tsx +179 -0
- package/src/renderer/shared/components/{AuthGate.tsx → app/AuthGate.tsx} +3 -3
- package/src/renderer/shared/components/{ChunkErrorBoundary.tsx → app/ChunkErrorBoundary.tsx} +13 -13
- package/src/renderer/shared/components/{CommandPalette.tsx → app/CommandPalette.tsx} +30 -30
- package/src/renderer/shared/components/{DashboardSidebar.tsx → app/DashboardSidebar.tsx} +7 -5
- package/src/renderer/shared/components/{DesktopTitlebar.tsx → app/DesktopTitlebar.tsx} +9 -26
- package/src/renderer/shared/components/{ErrorNotice.tsx → app/ErrorNotice.tsx} +1 -1
- package/src/renderer/shared/components/{MintDashboard.tsx → app/MintDashboard.tsx} +217 -228
- package/src/renderer/shared/components/{ModalErrorBoundary.tsx → app/ModalErrorBoundary.tsx} +8 -8
- package/src/renderer/shared/components/{CalculationCard.tsx → cards/CalculationCard.tsx} +16 -30
- package/src/renderer/shared/components/{ImageGenCard.tsx → cards/ImageGenCard.tsx} +11 -24
- package/src/renderer/shared/components/{ImageSearchCard.tsx → cards/ImageSearchCard.tsx} +11 -24
- package/src/renderer/shared/components/{MermaidCard.tsx → cards/MermaidCard.tsx} +38 -22
- package/src/renderer/shared/components/cards/PlanCard.test.tsx +41 -0
- package/src/renderer/shared/components/cards/PlanCard.tsx +32 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.test.tsx +29 -0
- package/src/renderer/shared/components/cards/ReleaseNotesCard.tsx +137 -0
- package/src/renderer/shared/components/{StockCard.tsx → cards/StockCard.tsx} +22 -38
- package/src/renderer/shared/components/cards/ToolResultCards.tsx +94 -0
- package/src/renderer/shared/components/{UiFeatureCard.tsx → cards/UiFeatureCard.tsx} +6 -6
- package/src/renderer/shared/components/{UiGridCard.tsx → cards/UiGridCard.tsx} +3 -3
- package/src/renderer/shared/components/{UiMockupWidget.tsx → cards/UiMockupWidget.tsx} +4 -4
- package/src/renderer/shared/components/{WeatherCard.tsx → cards/WeatherCard.tsx} +6 -6
- package/src/renderer/shared/components/{ApprovalCard.test.tsx → chat/ApprovalCard.test.tsx} +12 -1
- package/src/renderer/shared/components/{ApprovalCard.tsx → chat/ApprovalCard.tsx} +26 -7
- package/src/renderer/shared/components/chat/ArtifactCards.test.tsx +69 -0
- package/src/renderer/shared/components/chat/ArtifactCards.tsx +238 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.test.tsx +39 -0
- package/src/renderer/shared/components/chat/AttachedDocumentCard.tsx +220 -0
- package/src/renderer/shared/components/chat/ChatCodeBlock.test.tsx +33 -0
- package/src/renderer/shared/components/{ChatCodeBlock.tsx → chat/ChatCodeBlock.tsx} +16 -2
- package/src/renderer/shared/components/chat/ChatMessageItem.test.tsx +183 -0
- package/src/renderer/shared/components/chat/ChatMessageItem.tsx +388 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.css +227 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.test.tsx +45 -0
- package/src/renderer/shared/components/chat/ChatModeSelector.tsx +198 -0
- package/src/renderer/shared/components/chat/ChatPanel.test.tsx +53 -0
- package/src/renderer/shared/components/{ChatPanel.tsx → chat/ChatPanel.tsx} +462 -537
- package/src/renderer/shared/components/{CollapsiblePasteInput.tsx → chat/CollapsiblePasteInput.tsx} +2 -2
- package/src/renderer/shared/components/{DiffReviewModal.tsx → chat/DiffReviewModal.tsx} +44 -44
- package/src/renderer/shared/components/chat/GeminiLiveOverlay.test.tsx +165 -0
- package/src/renderer/shared/components/{GeminiLiveOverlay.tsx → chat/GeminiLiveOverlay.tsx} +47 -5
- package/src/renderer/shared/components/{ModelSelectorPopover.tsx → chat/ModelSelectorPopover.tsx} +44 -16
- package/src/renderer/shared/components/chat/QueuedMessagesBanner.test.tsx +89 -0
- package/src/renderer/shared/components/chat/QueuedMessagesBanner.tsx +201 -0
- package/src/renderer/shared/components/{RewindModal.tsx → chat/RewindModal.tsx} +14 -14
- package/src/renderer/shared/components/{SlashChoicePanel.tsx → chat/SlashChoicePanel.tsx} +2 -2
- package/src/renderer/shared/components/{SlashSuggestions.test.tsx → chat/SlashSuggestions.test.tsx} +14 -0
- package/src/renderer/shared/components/{SlashSuggestions.tsx → chat/SlashSuggestions.tsx} +4 -4
- package/src/renderer/shared/components/chat/SourcesBlock.test.tsx +53 -0
- package/src/renderer/shared/components/{SourcesBlock.tsx → chat/SourcesBlock.tsx} +262 -123
- package/src/renderer/shared/components/{ThinkingBlock.tsx → chat/ThinkingBlock.tsx} +1 -1
- package/src/renderer/shared/components/chat/TurnFileChanges.test.tsx +130 -0
- package/src/renderer/shared/components/chat/TurnFileChanges.tsx +463 -0
- package/src/renderer/shared/components/{VoicePill.css → chat/VoicePill.css} +1 -1
- package/src/renderer/shared/components/{VoicePill.jsx → chat/VoicePill.jsx} +3 -2
- package/src/renderer/shared/components/{CliSessionsView.test.tsx → extensions/CliSessionsView.test.tsx} +2 -2
- package/src/renderer/shared/components/{CliSessionsView.tsx → extensions/CliSessionsView.tsx} +19 -16
- package/src/renderer/shared/components/{LinkedFoldersView.tsx → extensions/LinkedFoldersView.tsx} +3 -3
- package/src/renderer/shared/components/{McpRegistryPicker.tsx → extensions/McpRegistryPicker.tsx} +6 -6
- package/src/renderer/shared/components/{McpServersView.tsx → extensions/McpServersView.tsx} +105 -36
- package/src/renderer/shared/components/{McpToolAllowlist.tsx → extensions/McpToolAllowlist.tsx} +2 -2
- package/src/renderer/shared/components/{ModUi.test.tsx → extensions/ModUi.test.tsx} +9 -4
- package/src/renderer/shared/components/{ModsPanel.tsx → extensions/ModsPanel.tsx} +59 -7
- package/src/renderer/shared/components/{PluginsView.tsx → extensions/PluginsView.tsx} +6 -6
- package/src/renderer/shared/components/{ScheduledTasksView.tsx → extensions/ScheduledTasksView.tsx} +5 -5
- package/src/renderer/shared/components/extensions/SkillsView.test.tsx +31 -0
- package/src/renderer/shared/components/{SkillsView.tsx → extensions/SkillsView.tsx} +20 -4
- package/src/renderer/shared/components/{ImageStudioPanel.tsx → studio/ImageStudioPanel.tsx} +6 -6
- package/src/renderer/shared/components/{LibraryItemCard.tsx → studio/LibraryItemCard.tsx} +2 -2
- package/src/renderer/shared/components/{LibraryPage.tsx → studio/LibraryPage.tsx} +53 -10
- package/src/renderer/shared/components/{LibraryPreview.tsx → studio/LibraryPreview.tsx} +2 -2
- package/src/renderer/shared/components/{VeoStudioPanel.tsx → studio/VeoStudioPanel.tsx} +7 -7
- package/src/renderer/shared/components/{GlideSelect.css → ui/GlideSelect.css} +7 -6
- package/src/renderer/shared/components/ui/ProviderBrandIcon.test.tsx +67 -0
- package/src/renderer/shared/components/ui/ProviderBrandIcon.tsx +167 -0
- package/src/renderer/shared/components/ui/SelectField.tsx +506 -0
- package/src/renderer/shared/components/ui/error-recovery.css +10 -10
- package/src/renderer/shared/components/ui/error-recovery.tsx +1 -1
- package/src/renderer/shared/components/{ArtifactPreviewPanel.tsx → workspace/ArtifactPreviewPanel.tsx} +374 -118
- package/src/renderer/shared/components/workspace/BackgroundTerminals.test.tsx +63 -0
- package/src/renderer/shared/components/{BackgroundTerminals.tsx → workspace/BackgroundTerminals.tsx} +40 -9
- package/src/renderer/shared/components/workspace/CodeReviewPage.test.tsx +50 -0
- package/src/renderer/shared/components/{CodeReviewPage.tsx → workspace/CodeReviewPage.tsx} +124 -24
- package/src/renderer/shared/components/{GitBranchSelector.tsx → workspace/GitBranchSelector.tsx} +80 -24
- package/src/renderer/shared/components/workspace/SessionSources.tsx +113 -0
- package/src/renderer/shared/components/{TerminalDock.tsx → workspace/TerminalDock.tsx} +5 -2
- package/src/renderer/shared/components/workspace/ToolSurfacePage.test.tsx +81 -0
- package/src/renderer/shared/components/{ToolSurfacePage.tsx → workspace/ToolSurfacePage.tsx} +361 -90
- package/src/renderer/shared/components/{ToolsPanelToggle.tsx → workspace/ToolsPanelToggle.tsx} +1 -1
- package/src/renderer/shared/components/workspace/WorkspaceFileInfo.tsx +28 -0
- package/src/renderer/shared/components/workspace/WorkspaceMediaPreview.tsx +17 -0
- package/src/renderer/shared/components/{WorkspacePaneResizer.tsx → workspace/WorkspacePaneResizer.tsx} +1 -1
- package/src/renderer/shared/components/workspace/WorkspacePdfPreview.tsx +308 -0
- package/src/renderer/shared/components/workspace/WorkspacePreviewActions.test.tsx +101 -0
- package/src/renderer/shared/components/{WorkspacePreviewActions.tsx → workspace/WorkspacePreviewActions.tsx} +3 -3
- package/src/renderer/shared/components/{WorkspaceSelector.tsx → workspace/WorkspaceSelector.tsx} +99 -7
- package/src/renderer/shared/components/{background-terminals.css → workspace/background-terminals.css} +11 -11
- package/src/renderer/shared/components/{workspace-selector.css → workspace/workspace-selector.css} +84 -2
- package/src/renderer/shared/constants/config.ts +6 -3
- package/src/renderer/shared/constants/models.test.ts +51 -0
- package/src/renderer/shared/constants/models.ts +139 -32
- package/src/renderer/shared/conversation/coordinator.test.ts +46 -0
- package/src/renderer/shared/conversation/coordinator.ts +50 -2
- package/src/renderer/shared/conversation/lifecycle.test.ts +254 -4
- package/src/renderer/shared/conversation/lifecycle.ts +268 -12
- package/src/renderer/shared/conversation/progressiveStream.test.ts +83 -0
- package/src/renderer/shared/conversation/progressiveStream.ts +219 -0
- package/src/renderer/shared/conversation/projectNavigation.test.ts +18 -0
- package/src/renderer/shared/conversation/projectNavigation.ts +11 -0
- package/src/renderer/shared/conversation/transport.test.ts +20 -0
- package/src/renderer/shared/conversation/transport.ts +51 -9
- package/src/renderer/shared/conversation/useConversationCoordinator.ts +42 -12
- package/src/renderer/shared/css/agent-observability.css +205 -11
- package/src/renderer/shared/css/chat-ui-cards.css +48 -48
- package/src/renderer/shared/css/code-review.css +139 -110
- package/src/renderer/shared/css/code-sessions.css +56 -27
- package/src/renderer/shared/css/collapsed-paste.css +1 -1
- package/src/renderer/shared/css/error-notice.css +1 -1
- package/src/renderer/shared/css/file-changes.css +298 -51
- package/src/renderer/shared/css/library.css +105 -10
- package/src/renderer/shared/css/management-views.css +79 -38
- package/src/renderer/shared/css/mcp-results.css +48 -5
- package/src/renderer/shared/css/model-selector.css +18 -18
- package/src/renderer/shared/css/mods.css +7 -7
- package/src/renderer/shared/css/notification-notice.css +2 -2
- package/src/renderer/shared/css/queued-messages.css +221 -0
- package/src/renderer/shared/css/session-sources.css +12 -7
- package/src/renderer/shared/css/settings/agents.css +2 -2
- package/src/renderer/{src → shared}/css/settings/base.css +101 -107
- package/src/renderer/shared/css/settings/general.css +397 -100
- package/src/renderer/shared/css/settings/plugins.css +8 -8
- package/src/renderer/shared/css/settings/profile.css +5 -5
- package/src/renderer/shared/css/settings/theme.css +49 -37
- package/src/renderer/shared/css/slash-choice.css +2 -2
- package/src/renderer/shared/css/slash-suggestions.css +3 -3
- package/src/renderer/shared/css/sources-block.css +41 -2
- package/src/renderer/shared/css/tools-panel.css +2 -2
- package/src/renderer/shared/css/ui-tailwind.css +10 -2
- package/src/renderer/shared/css/veo-studio.css +2 -2
- package/src/renderer/shared/css/viewed-images.css +93 -4
- package/src/renderer/shared/css/workspace-file-info.css +12 -0
- package/src/renderer/shared/css/workspace-media-preview.css +13 -0
- package/src/renderer/shared/env.d.ts +9 -0
- package/src/renderer/shared/platform.test.ts +143 -0
- package/src/renderer/shared/platform.ts +12 -5
- package/src/renderer/shared/speech/player.ts +160 -0
- package/src/renderer/shared/speech/types.ts +11 -0
- package/src/renderer/shared/theme/themeManager.test.ts +37 -0
- package/src/renderer/shared/theme/themeManager.ts +24 -5
- package/src/renderer/shared/theme/themes.ts +71 -4
- package/src/renderer/shared/theme/tokens.css +452 -53
- package/src/renderer/shared/types.ts +57 -2
- package/src/renderer/shared/utils/agentActivity.test.ts +633 -8
- package/src/renderer/shared/utils/agentActivity.ts +776 -59
- package/src/renderer/shared/utils/artifactBrowser.test.ts +37 -32
- package/src/renderer/shared/utils/artifactBrowser.ts +18 -20
- package/src/renderer/shared/utils/artifacts.ts +55 -0
- package/src/renderer/shared/utils/attachedDocument.test.ts +73 -0
- package/src/renderer/shared/utils/attachedDocument.ts +49 -0
- package/src/renderer/shared/utils/contextCompaction.test.tsx +1 -1
- package/src/renderer/shared/utils/externalLinks.ts +91 -0
- package/src/renderer/shared/utils/fileIcons.test.ts +59 -0
- package/src/renderer/shared/utils/fileIcons.ts +149 -16
- package/src/renderer/shared/utils/lazyWithRetry.ts +13 -0
- package/src/renderer/shared/utils/markdown.tsx +238 -51
- package/src/renderer/shared/utils/markdown.webImages.test.tsx +140 -2
- package/src/renderer/shared/utils/mcpResults.test.tsx +53 -1
- package/src/renderer/shared/utils/pdfRange.ts +16 -0
- package/src/renderer/shared/utils/previewObjectUrls.ts +20 -0
- package/src/renderer/shared/utils/sessionSources.test.ts +41 -1
- package/src/renderer/shared/utils/sessionSources.ts +2 -0
- package/src/renderer/shared/utils/slashCommandProcessor.ts +7 -1
- package/src/renderer/shared/utils/speech.ts +18 -4
- package/src/renderer/shared/utils/syntaxHighlight.test.tsx +11 -1
- package/src/renderer/shared/utils/syntaxHighlight.tsx +51 -3
- package/src/renderer/shared/utils/useGeminiLiveVoice.ts +324 -51
- package/src/renderer/shared/utils/useNativeVoiceInput.ts +19 -5
- package/src/renderer/shared/utils/useSessionSources.ts +4 -0
- package/src/renderer/shared/utils/viewedImages.test.ts +129 -0
- package/src/renderer/shared/utils/viewedImages.ts +81 -10
- package/src/renderer/shared/utils/workspaceDrag.test.ts +153 -0
- package/src/renderer/shared/utils/workspaceDrag.ts +172 -0
- package/src/renderer/shared/utils/workspaceImage.ts +17 -0
- package/src/renderer/shared/utils/workspaceListPreferences.test.ts +64 -0
- package/src/renderer/shared/utils/workspaceListPreferences.ts +54 -0
- package/src/renderer/shared/utils/workspaceMedia.ts +49 -0
- package/src/renderer/shared/utils/workspacePreview.test.ts +147 -2
- package/src/renderer/shared/utils/workspacePreview.ts +34 -3
- package/src/renderer/shared/utils/workspaces.ts +3 -2
- package/src/renderer/src/App.tsx +20 -16
- package/src/renderer/src/agentProgress.ts +1 -0
- package/src/renderer/src/components/ChatPanel.tsx +1 -1
- package/src/renderer/src/components/DashboardSidebar.tsx +2 -2
- package/src/renderer/src/components/ImageStudioPanel.tsx +1 -1
- package/src/renderer/src/components/LiveTranslateControls.tsx +1 -1
- package/src/renderer/src/components/MintDashboard.tsx +1 -1
- package/src/renderer/src/components/ProactiveGlow.tsx +1 -1
- package/src/renderer/src/components/ScreenPicker.css +1 -1
- package/src/renderer/src/components/ScreenPicker.tsx +1 -1
- package/src/renderer/src/components/SettingsWindow.tsx +6 -830
- package/src/renderer/src/components/SpotlightWindow.tsx +4 -32
- package/src/renderer/src/components/VeoStudioPanel.tsx +1 -1
- package/src/renderer/src/components/WorkspacePanel.tsx +49 -15
- package/src/renderer/src/components/browser-suggestions-popup.css +8 -8
- package/src/renderer/src/css/app-shell.css +26 -106
- package/src/renderer/src/css/chat.css +1499 -292
- package/src/renderer/src/css/desktop-tools.css +17 -348
- package/src/renderer/src/css/sidebar-workspace.css +22 -2
- package/src/renderer/src/css/spotlight.css +2 -2
- package/src/renderer/src/css/tool-surfaces.css +335 -65
- package/src/renderer/src/css/widget.css +6 -5
- package/src/renderer/src/env.d.ts +13 -0
- package/src/renderer/src/index.css +53 -83
- package/src/renderer/src/tauri.ts +99 -13
- package/src/renderer/src/voiceInput.ts +3 -1
- package/src/renderer/src-web/App.tsx +20 -8
- package/src/renderer/src-web/agentProgress.ts +1 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1 -1
- package/src/renderer/src-web/components/DashboardSidebar.tsx +2 -2
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +1 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +1 -1
- package/src/renderer/src-web/components/SettingsWindow.tsx +6 -851
- package/src/renderer/src-web/components/VeoStudioPanel.tsx +1 -1
- package/src/renderer/src-web/css/app-shell.css +20 -104
- package/src/renderer/src-web/css/chat.css +1536 -261
- package/src/renderer/src-web/css/mobile.css +29 -13
- package/src/renderer/src-web/css/settings/mobile.css +2 -2
- package/src/renderer/src-web/css/sidebar-workspace.css +21 -1
- package/src/renderer/src-web/env.d.ts +14 -0
- package/src/renderer/src-web/index.css +53 -83
- package/src/renderer/src-web/tauri.ts +93 -15
- package/src/renderer/tests/browserArtifacts.browser.html +1 -0
- package/src/renderer/tests/browserArtifacts.browser.tsx +68 -0
- package/src/renderer/tests/chatEscape.browser.html +1 -0
- package/src/renderer/tests/chatEscape.browser.tsx +25 -0
- package/src/renderer/tests/chatPerformance.browser.html +1 -1
- package/src/renderer/tests/collapsedPaste.browser.html +1 -1
- package/src/renderer/tests/composerMenus.browser.html +3 -3
- package/src/renderer/tests/contextCompaction.browser.html +1 -1
- package/src/renderer/tests/errorRecovery.browser.html +1 -1
- package/src/renderer/tests/fixtures/preview.webm +0 -0
- package/src/renderer/tests/glideSelect.browser.html +1 -2
- package/src/renderer/tests/glideSelect.preview.html +1 -1
- package/src/renderer/tests/library.browser.tsx +4 -1
- package/src/renderer/tests/liveOrb.browser.html +1 -1
- package/src/renderer/tests/liveVoice.browser.html +1 -0
- package/src/renderer/tests/mediaFixtures.ts +32 -0
- package/src/renderer/tests/modInput.browser.html +1 -1
- package/src/renderer/tests/sessionSources.browser.tsx +76 -15
- package/src/renderer/tests/toolsPanel.browser.tsx +5 -5
- package/src/renderer/tests/viewedImages.browser.tsx +3 -3
- package/src/renderer/tests/voiceDictation.browser.html +101 -0
- package/src/renderer/tests/voicePill.browser.html +3 -3
- package/src/renderer/tests/voicePill.preview.html +1 -1
- package/src/renderer/tests/webSources.browser.tsx +2 -98
- package/src/renderer/tests/workspaceDrop.browser.html +1 -0
- package/src/renderer/tests/workspaceDrop.browser.tsx +183 -0
- package/src/renderer/tests/workspaceExpansion.browser.html +1 -0
- package/src/renderer/tests/workspaceExpansion.browser.tsx +66 -0
- package/src/renderer/tests/workspaceRemoval.browser.html +66 -0
- package/src/renderer/tests/workspaceUndo.browser.html +10 -2
- package/src-tauri/src/app/lifecycle.rs +45 -15
- package/src-tauri/src/background/headless.rs +14 -8
- package/src-tauri/src/commands/conversation.rs +137 -73
- package/src-tauri/src/commands/integrations.rs +7 -1
- package/src-tauri/src/commands/media.rs +7 -0
- package/src-tauri/src/commands/workspace.rs +38 -0
- package/src-tauri/src/native/mod.rs +0 -1
- package/src-tauri/src/native/system.rs +4 -47
- package/tsconfig.json +15 -1
- package/vite.config.ts +2 -1
- package/vite.config.web.ts +2 -0
- package/crates/mint-cli/src/interactive/chat_tui.rs +0 -7853
- package/crates/mint-core/src/agent/chat.rs +0 -4155
- package/crates/mint-core/src/browser/lifecycle.rs +0 -191
- package/crates/mint-core/src/browser/logging.rs +0 -20
- package/crates/mint-core/src/orchestration/tools/browser.rs +0 -31
- package/out/web/assets/CliSessionsView-CtzUJpK_.css +0 -1
- package/out/web/assets/CliSessionsView-D7hw0Mgj.js +0 -1
- package/out/web/assets/CodeReviewPage-C7bEOJfd.js +0 -4
- package/out/web/assets/CodeReviewPage-Dte9uLiL.css +0 -1
- package/out/web/assets/CommandPalette-0RrF-AOT.js +0 -1
- package/out/web/assets/ImageStudioPanel-CdxkAf_r.js +0 -1
- package/out/web/assets/LibraryPage--Qfisp6D.js +0 -2
- package/out/web/assets/LibraryPage-DAxUTAg7.css +0 -1
- package/out/web/assets/LinkedFoldersView-2nfZbDgN.js +0 -1
- package/out/web/assets/McpRegistryPicker-h9uS8_wT.js +0 -1
- package/out/web/assets/McpServersView-slfQJzNB.js +0 -1
- package/out/web/assets/MintDashboard-BSR5XpXh.js +0 -25
- package/out/web/assets/MintDashboard-CWUcCGhd.css +0 -1
- package/out/web/assets/PluginsView-l7LSm503.js +0 -1
- package/out/web/assets/ScheduledTasksView-CttmLbq1.js +0 -1
- package/out/web/assets/SettingsWindow-BCMLYvEV.js +0 -4
- package/out/web/assets/SettingsWindow-DcMVmU9u.css +0 -1
- package/out/web/assets/SkillsView-rNs1qr_U.js +0 -2
- package/out/web/assets/ToolSurfacePage-B9DzEPWN.js +0 -37
- package/out/web/assets/ToolSurfacePage-CTfgggF2.css +0 -1
- package/out/web/assets/VeoStudioPanel-DiJs_Cg2.js +0 -4
- package/out/web/assets/WorkspacePanel-CbJ1PFoZ.js +0 -1
- package/out/web/assets/WorkspaceSelector-B2xIp04D.js +0 -1
- package/out/web/assets/WorkspaceSelector-Ca8uJOsS.css +0 -1
- package/out/web/assets/abnfDiagram-VCTEODGH-CD0CjoGl.js +0 -1
- package/out/web/assets/architecture-7GRP2DOG-RKV83OeZ.js +0 -1
- package/out/web/assets/architectureDiagram-5GKGNRK7-nN_HAFct.js +0 -36
- package/out/web/assets/c4Diagram-UCG6FXSJ-DTdV8_zE.js +0 -38
- package/out/web/assets/chunk-2Q5K7J3B-Dmqrq8Tp.js +0 -1
- package/out/web/assets/chunk-75Z2AOVW-_M0z5olV.js +0 -2
- package/out/web/assets/chunk-CKBBP62Z-BpQEgeHF.js +0 -2
- package/out/web/assets/chunk-CLGD4ZFX-D54yGvGS.js +0 -1
- package/out/web/assets/chunk-CacB07dV.js +0 -1
- package/out/web/assets/chunk-DSNCTWBM-C-gvT2ru.js +0 -2
- package/out/web/assets/chunk-DU6HZSFF-CnFngIhW.js +0 -128
- package/out/web/assets/chunk-F27PBJKO-BqtWrnEO.js +0 -1
- package/out/web/assets/chunk-FOHPRMQF-BaVC1uA7.js +0 -161
- package/out/web/assets/chunk-G27WJ6UU-Ckoq5mi2.js +0 -231
- package/out/web/assets/chunk-JQ64N6SF-CF8T7GiZ.js +0 -1
- package/out/web/assets/chunk-JWPE2WC7-BwtYOHqU.js +0 -1
- package/out/web/assets/chunk-LNGE3PJU-AdBoOWw2.js +0 -34
- package/out/web/assets/chunk-OBVCFTLP-CRIkunUB.js +0 -1
- package/out/web/assets/chunk-P2QGCYS3-UFEU6BxZ.js +0 -1
- package/out/web/assets/chunk-POPQ4Y6H-CQcrNbjr.js +0 -1
- package/out/web/assets/chunk-PWAF6VOD-Cc2uVDsG.js +0 -1
- package/out/web/assets/chunk-R7TYR2AO-DiCxzdAY.js +0 -10
- package/out/web/assets/chunk-RHFEMEQ7-b7hnPmrR.js +0 -168
- package/out/web/assets/chunk-XXDRQBXY-DnJkbUaw.js +0 -1
- package/out/web/assets/chunk-Y2CYZVJY-C9o-s6OY.js +0 -1
- package/out/web/assets/classDiagram-DTDB5LWJ-RnC2mYO4.js +0 -1
- package/out/web/assets/classDiagram-v2-JRS7N3AN-RnC2mYO4.js +0 -1
- package/out/web/assets/config-CnvCChUM.js +0 -2
- package/out/web/assets/config-DM6oIVO3.css +0 -1
- package/out/web/assets/core-DsKkQ7ig.js +0 -1
- package/out/web/assets/cose-bilkent-JH36ORCC--UN4aw5-.js +0 -1
- package/out/web/assets/cynefin-OW5HDTMX-CnzQfOGp.js +0 -1
- package/out/web/assets/cynefinDiagram-5FMLGOSQ-DVtQnfpm.js +0 -62
- package/out/web/assets/cytoscape.esm-Df1UN4Y1.js +0 -321
- package/out/web/assets/dagre-3AP2YEHR-DG76_k5_.js +0 -4
- package/out/web/assets/dagre-BWadQu2F.js +0 -1
- package/out/web/assets/diagram-S7CK7UJ4-CaouUoze.js +0 -30
- package/out/web/assets/diagram-UQ7AKVKN-B4bEc26B.js +0 -41
- package/out/web/assets/diagram-VX7I27RA-7hH5IPYf.js +0 -24
- package/out/web/assets/diagram-Z3DM3KII-CCM4clo8.js +0 -24
- package/out/web/assets/dist-BoTOASLF.js +0 -1
- package/out/web/assets/dist-js-BkYNznHX.js +0 -1
- package/out/web/assets/ebnfDiagram-PWID7BFC-Da7WAM3k.js +0 -1
- package/out/web/assets/event-Cdt7Byyj.js +0 -1
- package/out/web/assets/eventmodeling-NTZA5JFV-xcfsA0XD.js +0 -1
- package/out/web/assets/flowDiagram-A5DVABFB-JdtH1673.js +0 -1
- package/out/web/assets/ganttDiagram-EL5Y4UJY-B3ClFGmL.js +0 -292
- package/out/web/assets/gitGraph-4MIJSDKK-BZ_y_cDU.js +0 -1
- package/out/web/assets/gitGraphDiagram-WWUBYQGX-drhRSYm3.js +0 -106
- package/out/web/assets/graphlib-CTR5ZhLf.js +0 -1
- package/out/web/assets/index-BZ9xgmMP.css +0 -1
- package/out/web/assets/index-Ca8cCS4h.js +0 -168
- package/out/web/assets/info-A6RAGUB7-B7I3K0F1.js +0 -1
- package/out/web/assets/infoDiagram-RXCK75RN-deX_1MPh.js +0 -2
- package/out/web/assets/journeyDiagram-EYS64GPL-CZn2kB8b.js +0 -139
- package/out/web/assets/jsx-runtime-DN4NhQUK.js +0 -1
- package/out/web/assets/katex-O3jvqgnU.js +0 -257
- package/out/web/assets/markdown-CMhYIcWB.js +0 -39
- package/out/web/assets/mermaid-parser.core-Whu2rlJC.js +0 -7
- package/out/web/assets/mermaid.core-CKyPo8VH.js +0 -11
- package/out/web/assets/modelManager-CUaGWMWu.js +0 -2
- package/out/web/assets/oauthManager-DlNhXJr6.js +0 -1
- package/out/web/assets/packet-AYTQ26CC-CEeIRKRG.js +0 -1
- package/out/web/assets/pegDiagram-XKGWAZYB-BYadI-KO.js +0 -1
- package/out/web/assets/pie-WAS4IAKB-D8uf4dh5.js +0 -1
- package/out/web/assets/pieDiagram-E7YTZNPT-CjSDy4hO.js +0 -39
- package/out/web/assets/radar-RG4KPBEZ-DZJxnokY.js +0 -1
- package/out/web/assets/railroad-74A4TZTK-DBysUiUi.js +0 -1
- package/out/web/assets/railroad-abnf-HS5TGJTU-CHWxoJYg.js +0 -1
- package/out/web/assets/railroad-ebnf-LZEXJU2U-DDv7X6bY.js +0 -1
- package/out/web/assets/railroad-peg-WCYAUIDC-DpfcYYNJ.js +0 -1
- package/out/web/assets/railroadDiagram-O6MQD6OU-CYtyv0t3.js +0 -1
- package/out/web/assets/rough.esm-DFbTUy8a.js +0 -1
- package/out/web/assets/sankeyDiagram-P5KCCOFB-Bn2x6C4Y.js +0 -40
- package/out/web/assets/sequenceDiagram-WJ2MYXX4-BvFLKtPd.js +0 -162
- package/out/web/assets/sizeCapture-X5ZJPWSS-DKeOCaN2.js +0 -1
- package/out/web/assets/src-DnfeXQPr.js +0 -1
- package/out/web/assets/stateDiagram-HBIQ2CUA-Bx0GWeaY.js +0 -1
- package/out/web/assets/stateDiagram-v2-4QOOHH4V--hol1yg_.js +0 -1
- package/out/web/assets/studio-B1mk40wD.js +0 -1
- package/out/web/assets/swimlanes-XN3QIQJK-CCma0T7L.js +0 -1
- package/out/web/assets/swimlanesDiagram-VK2B7HYN-BkohoVsU.js +0 -8
- package/out/web/assets/timeline-definition-24CTP7MA-iCRhhJ7C.js +0 -120
- package/out/web/assets/treeView-Q6P3EWNA-DZ-y08qp.js +0 -1
- package/out/web/assets/treemap-WGGIJYW6-De0CxzMx.js +0 -1
- package/out/web/assets/vennDiagram-4TSXK5OY-w_7aZ6xe.js +0 -34
- package/out/web/assets/wardley-WFR3VGLG-DT0y0xJ4.js +0 -1
- package/out/web/assets/window-BJCVtyET.js +0 -1
- package/shared-modules/libsecret/libsecret.json +0 -23
- package/shared-modules/libusb/libusb.json +0 -27
- package/shared-modules/lzo/lzo.json +0 -27
- package/src/renderer/shared/components/AgentActivityDrawer.tsx +0 -204
- package/src/renderer/shared/components/AgentActivityTable.tsx +0 -301
- package/src/renderer/shared/components/ChatMessageItem.test.tsx +0 -68
- package/src/renderer/shared/components/ChatMessageItem.tsx +0 -326
- package/src/renderer/shared/components/ChatPanel.test.tsx +0 -21
- package/src/renderer/shared/components/FormattedMessage.tsx +0 -11
- package/src/renderer/shared/components/GeminiLiveOverlay.test.tsx +0 -24
- package/src/renderer/shared/components/GlideSelect.d.ts +0 -31
- package/src/renderer/shared/components/GlideSelect.jsx +0 -266
- package/src/renderer/shared/components/InlineThoughtNote.tsx +0 -27
- package/src/renderer/shared/components/ModSessionControls.tsx +0 -8
- package/src/renderer/shared/components/ModUi.tsx +0 -26
- package/src/renderer/shared/components/ProgressActivity.tsx +0 -17
- package/src/renderer/shared/components/ReviewHistory.tsx +0 -42
- package/src/renderer/shared/components/RunSummaryDashboard.tsx +0 -143
- package/src/renderer/shared/components/SelectField.tsx +0 -38
- package/src/renderer/shared/components/SessionSources.tsx +0 -48
- package/src/renderer/shared/components/SessionSourcesPopover.tsx +0 -61
- package/src/renderer/shared/components/ToolSurfacePage.test.tsx +0 -32
- package/src/renderer/shared/components/ViewedImages.tsx +0 -36
- package/src/renderer/shared/components/WorkspaceContextPopover.tsx +0 -138
- package/src/renderer/shared/components/WorkspacePreviewActions.test.tsx +0 -44
- package/src/renderer/shared/components/ui/demo.tsx +0 -26
- package/src/renderer/shared/css/session-sources-popover.css +0 -9
- package/src/renderer/src/components/BrowserShell.tsx +0 -108
- package/src/renderer/src/components/BrowserSuggestionsPopup.tsx +0 -91
- package/src/renderer/src/components/CalculationCard.tsx +0 -3
- package/src/renderer/src/components/ImageGenCard.tsx +0 -2
- package/src/renderer/src/components/ImageSearchCard.tsx +0 -2
- package/src/renderer/src/components/MermaidCard.tsx +0 -1
- package/src/renderer/src/components/PicturesLibrary.tsx +0 -2
- package/src/renderer/src/components/Settings/AgentsTab.tsx +0 -4
- package/src/renderer/src/components/Settings/AudioTab.tsx +0 -3
- package/src/renderer/src/components/Settings/AutomationTab.tsx +0 -3
- package/src/renderer/src/components/Settings/GeneralTab.tsx +0 -3
- package/src/renderer/src/components/Settings/MemoryTab.tsx +0 -3
- package/src/renderer/src/components/Settings/PluginsTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ProfileTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +0 -3
- package/src/renderer/src/components/Settings/ThemeTab.tsx +0 -3
- package/src/renderer/src/components/StockCard.tsx +0 -3
- package/src/renderer/src/components/WeatherCard.tsx +0 -3
- package/src/renderer/src/components/browser-shell.css +0 -185
- package/src/renderer/src/css/pictures.css +0 -2
- package/src/renderer/src-web/components/PicturesLibrary.tsx +0 -2
- package/src/renderer/src-web/components/Settings/AgentsTab.tsx +0 -4
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ProfileTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +0 -3
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +0 -3
- package/src/renderer/src-web/css/pictures.css +0 -2
- package/src/renderer/src-web/css/settings/base.css +0 -1266
- package/src/renderer/src-web/css/spotlight.css +0 -102
- package/src/renderer/src-web/css/widget.css +0 -213
- package/src-tauri/src/native/browser.rs +0 -252
- /package/src/renderer/shared/components/{ContextCompactionStatus.tsx → chat/ContextCompactionStatus.tsx} +0 -0
- /package/src/renderer/shared/components/{GeminiLiveOverlay.css → chat/GeminiLiveOverlay.css} +0 -0
- /package/src/renderer/shared/components/{VirtualChatHistory.tsx → chat/VirtualChatHistory.tsx} +0 -0
- /package/src/renderer/shared/components/{VoicePill.d.ts → chat/VoicePill.d.ts} +0 -0
- /package/src/renderer/shared/components/{VoicePill.test.tsx → chat/VoicePill.test.tsx} +0 -0
- /package/src/renderer/shared/components/{ScreenCaptureDialog.css → studio/ScreenCaptureDialog.css} +0 -0
- /package/src/renderer/shared/components/{ScreenCaptureDialog.tsx → studio/ScreenCaptureDialog.tsx} +0 -0
- /package/src/renderer/shared/components/{SelectField.test.tsx → ui/SelectField.test.tsx} +0 -0
|
@@ -0,0 +1,1479 @@
|
|
|
1
|
+
use super::anthropic::*;
|
|
2
|
+
use super::gemini::*;
|
|
3
|
+
use super::ollama::*;
|
|
4
|
+
use super::openai::*;
|
|
5
|
+
use super::streaming::*;
|
|
6
|
+
use super::*;
|
|
7
|
+
use reqwest::Client;
|
|
8
|
+
use serde_json::json;
|
|
9
|
+
|
|
10
|
+
#[test]
|
|
11
|
+
fn rejects_missing_api_key() {
|
|
12
|
+
let error = required_key("gemini", "").unwrap_err();
|
|
13
|
+
assert!(matches!(error, ChatError::MissingApiKey(provider) if provider == "gemini"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
#[test]
|
|
17
|
+
fn rejects_video_attachments_for_non_gemini_providers() {
|
|
18
|
+
let request = ChatRequest {
|
|
19
|
+
message: "describe this video".into(),
|
|
20
|
+
system_instruction: String::new(),
|
|
21
|
+
chat_id: None,
|
|
22
|
+
image_data_uri: None,
|
|
23
|
+
audio_data_uri: None,
|
|
24
|
+
video_data_uri: Some("data:video/mp4;base64,AAAA".into()),
|
|
25
|
+
document_attachment: None,
|
|
26
|
+
workspace_path: None,
|
|
27
|
+
agent_id: None,
|
|
28
|
+
plan_mode: false,
|
|
29
|
+
pinned_mcp_server: None,
|
|
30
|
+
messages: None,
|
|
31
|
+
tools: None,
|
|
32
|
+
temperature: None,
|
|
33
|
+
..Default::default()
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
let error = require_supported_attachments("openai", &request).unwrap_err();
|
|
37
|
+
assert!(matches!(error, ChatError::UnsupportedAttachments(provider) if provider == "openai"));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[test]
|
|
41
|
+
fn parses_stream_provider_formats() {
|
|
42
|
+
let openai = parse_stream_value(
|
|
43
|
+
StreamFormat::OpenAi,
|
|
44
|
+
r#"data: {"choices":[{"delta":{"content":"hello"}}]}"#,
|
|
45
|
+
)
|
|
46
|
+
.unwrap();
|
|
47
|
+
assert_eq!(openai["choices"][0]["delta"]["content"], "hello");
|
|
48
|
+
let ollama =
|
|
49
|
+
parse_stream_value(StreamFormat::Ollama, r#"{"message":{"content":"hi"}}"#).unwrap();
|
|
50
|
+
assert_eq!(ollama["message"]["content"], "hi");
|
|
51
|
+
assert!(parse_stream_value(StreamFormat::Anthropic, "data: [DONE]").is_none());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[test]
|
|
55
|
+
fn malformed_tool_arguments_survive_provider_parsing_for_rejection() {
|
|
56
|
+
let malformed = r#"{"path":"~/Downloads/YOUTUBE_DL""#;
|
|
57
|
+
let function = json!({"name": "list_files", "arguments": malformed});
|
|
58
|
+
let openai = parse_openai_style_reply(
|
|
59
|
+
"test".into(),
|
|
60
|
+
&json!({
|
|
61
|
+
"choices": [{"message": {"tool_calls": [{"id": "call_1", "function": function}]}}]
|
|
62
|
+
}),
|
|
63
|
+
)
|
|
64
|
+
.unwrap();
|
|
65
|
+
let ollama = parse_ollama_reply(
|
|
66
|
+
"test".into(),
|
|
67
|
+
&json!({
|
|
68
|
+
"message": {"tool_calls": [{"function": function}]}
|
|
69
|
+
}),
|
|
70
|
+
)
|
|
71
|
+
.unwrap();
|
|
72
|
+
let mut accumulator = StreamReplyAccumulator::default();
|
|
73
|
+
accumulator.consume_openai(&json!({
|
|
74
|
+
"choices": [{"delta": {"tool_calls": [{"index": 0, "id": "call_1", "function": function}]}}]
|
|
75
|
+
}), &mut |_| {});
|
|
76
|
+
let streamed = accumulator.finish("test".into()).unwrap();
|
|
77
|
+
for reply in [openai, ollama, streamed] {
|
|
78
|
+
let calls = reply.tool_calls.unwrap();
|
|
79
|
+
assert_eq!(calls.len(), 1);
|
|
80
|
+
assert_eq!(calls[0].input, Value::String(malformed.into()));
|
|
81
|
+
}
|
|
82
|
+
assert_eq!(decode_tool_arguments(&json!("{}")), json!({}));
|
|
83
|
+
assert_eq!(decode_tool_arguments(&Value::Null), Value::Null);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[test]
|
|
87
|
+
fn accumulates_openai_reasoning_tools_and_usage() {
|
|
88
|
+
let mut accumulator = StreamReplyAccumulator::default();
|
|
89
|
+
let mut events = Vec::new();
|
|
90
|
+
accumulator.consume_openai(
|
|
91
|
+
&json!({
|
|
92
|
+
"choices": [{"delta": {"reasoning_content": "inspect "}}]
|
|
93
|
+
}),
|
|
94
|
+
&mut |event| events.push(event),
|
|
95
|
+
);
|
|
96
|
+
accumulator.consume_openai(&json!({
|
|
97
|
+
"choices": [{"delta": {"reasoning_content": "files", "tool_calls": [{
|
|
98
|
+
"index": 0, "id": "call_1", "function": {"name": "read_file", "arguments": "{\"path\":"}
|
|
99
|
+
}]}}]
|
|
100
|
+
}), &mut |event| events.push(event));
|
|
101
|
+
accumulator.consume_openai(
|
|
102
|
+
&json!({
|
|
103
|
+
"choices": [{"delta": {"tool_calls": [{
|
|
104
|
+
"index": 0, "function": {"arguments": "\"README.md\"}"}
|
|
105
|
+
}]}, "finish_reason": "tool_calls"}],
|
|
106
|
+
"usage": {"prompt_tokens": 10, "completion_tokens": 5}
|
|
107
|
+
}),
|
|
108
|
+
&mut |event| events.push(event),
|
|
109
|
+
);
|
|
110
|
+
accumulator.flush_tag_buffer(&mut |event| events.push(event));
|
|
111
|
+
let reply = accumulator.finish("test".into()).unwrap();
|
|
112
|
+
assert_eq!(reply.thought.as_deref(), Some("inspect files"));
|
|
113
|
+
assert_eq!(reply.stop_reason.as_deref(), Some("tool_calls"));
|
|
114
|
+
assert_eq!(reply.input_tokens, Some(10));
|
|
115
|
+
assert_eq!(reply.tool_calls.unwrap()[0].input["path"], "README.md");
|
|
116
|
+
assert_eq!(
|
|
117
|
+
events
|
|
118
|
+
.iter()
|
|
119
|
+
.filter(|event| matches!(event, ChatStreamEvent::ReasoningDelta { .. }))
|
|
120
|
+
.count(),
|
|
121
|
+
2
|
|
122
|
+
);
|
|
123
|
+
assert_eq!(
|
|
124
|
+
events
|
|
125
|
+
.iter()
|
|
126
|
+
.filter(|event| matches!(event, ChatStreamEvent::ToolCallDelta { .. }))
|
|
127
|
+
.count(),
|
|
128
|
+
2
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#[test]
|
|
133
|
+
fn streams_think_tags_split_across_chunks() {
|
|
134
|
+
let mut accumulator = StreamReplyAccumulator::default();
|
|
135
|
+
let mut events = Vec::new();
|
|
136
|
+
for chunk in ["ก่อน<thi", "nk>คิด", "อยู่</th", "ink>หลัง"] {
|
|
137
|
+
accumulator.push_text(chunk, &mut |event| events.push(event));
|
|
138
|
+
}
|
|
139
|
+
accumulator.flush_tag_buffer(&mut |event| events.push(event));
|
|
140
|
+
let reply = accumulator.finish("test".into()).unwrap();
|
|
141
|
+
assert_eq!(reply.text, "ก่อนหลัง");
|
|
142
|
+
assert_eq!(reply.thought.as_deref(), Some("คิดอยู่"));
|
|
143
|
+
let streamed_reasoning: String = events
|
|
144
|
+
.iter()
|
|
145
|
+
.filter_map(|event| match event {
|
|
146
|
+
ChatStreamEvent::ReasoningDelta { delta } => Some(delta.as_str()),
|
|
147
|
+
_ => None,
|
|
148
|
+
})
|
|
149
|
+
.collect();
|
|
150
|
+
assert_eq!(streamed_reasoning, "คิดอยู่");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn builds_gemini_multimodal_parts() {
|
|
155
|
+
let parts = gemini_parts(&ChatRequest {
|
|
156
|
+
message: "describe".into(),
|
|
157
|
+
system_instruction: String::new(),
|
|
158
|
+
chat_id: None,
|
|
159
|
+
image_data_uri: Some("data:image/png;base64,aGk= data:image/jpeg;base64,Ynl5".into()),
|
|
160
|
+
audio_data_uri: None,
|
|
161
|
+
video_data_uri: None,
|
|
162
|
+
document_attachment: None,
|
|
163
|
+
workspace_path: None,
|
|
164
|
+
agent_id: None,
|
|
165
|
+
plan_mode: false,
|
|
166
|
+
pinned_mcp_server: None,
|
|
167
|
+
messages: None,
|
|
168
|
+
tools: None,
|
|
169
|
+
temperature: None,
|
|
170
|
+
..Default::default()
|
|
171
|
+
})
|
|
172
|
+
.unwrap();
|
|
173
|
+
assert_eq!(parts.len(), 3);
|
|
174
|
+
assert_eq!(parts[1]["inlineData"]["mimeType"], "image/png");
|
|
175
|
+
assert_eq!(parts[2]["inlineData"]["mimeType"], "image/jpeg");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn sample_tool() -> ToolSpec {
|
|
179
|
+
ToolSpec {
|
|
180
|
+
name: "read_file".into(),
|
|
181
|
+
description: "Reads a file".into(),
|
|
182
|
+
parameters: json!({
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": { "path": { "type": "string" } },
|
|
185
|
+
"required": ["path"]
|
|
186
|
+
}),
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn native_request(messages: Vec<ChatMessage>, tools: Option<Vec<ToolSpec>>) -> ChatRequest {
|
|
191
|
+
ChatRequest {
|
|
192
|
+
message: String::new(),
|
|
193
|
+
system_instruction: "You are Mint.".into(),
|
|
194
|
+
chat_id: None,
|
|
195
|
+
image_data_uri: None,
|
|
196
|
+
audio_data_uri: None,
|
|
197
|
+
video_data_uri: None,
|
|
198
|
+
document_attachment: None,
|
|
199
|
+
workspace_path: None,
|
|
200
|
+
agent_id: None,
|
|
201
|
+
plan_mode: false,
|
|
202
|
+
pinned_mcp_server: None,
|
|
203
|
+
messages: Some(messages),
|
|
204
|
+
tools,
|
|
205
|
+
temperature: None,
|
|
206
|
+
..Default::default()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
#[test]
|
|
211
|
+
fn anthropic_native_payload_includes_tools_and_tool_result_messages() {
|
|
212
|
+
let messages = vec![
|
|
213
|
+
ChatMessage::text(ChatRole::User, "read main.rs"),
|
|
214
|
+
ChatMessage {
|
|
215
|
+
role: ChatRole::Assistant,
|
|
216
|
+
content: vec![ContentBlock::ToolUse {
|
|
217
|
+
id: "call_1".into(),
|
|
218
|
+
name: "read_file".into(),
|
|
219
|
+
input: json!({ "path": "main.rs" }),
|
|
220
|
+
thought_signature: None,
|
|
221
|
+
}],
|
|
222
|
+
},
|
|
223
|
+
ChatMessage {
|
|
224
|
+
role: ChatRole::Tool,
|
|
225
|
+
content: vec![ContentBlock::ToolResult {
|
|
226
|
+
tool_use_id: "call_1".into(),
|
|
227
|
+
content: "fn main() {}".into(),
|
|
228
|
+
is_error: false,
|
|
229
|
+
}],
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
let request = native_request(messages, Some(vec![sample_tool()]));
|
|
233
|
+
let cfg = MintConfig::default();
|
|
234
|
+
let payload = anthropic_chat_payload(&cfg, "claude-x", &request, false).unwrap();
|
|
235
|
+
|
|
236
|
+
assert_eq!(payload["system"][0]["text"], "You are Mint.");
|
|
237
|
+
assert_eq!(payload["tools"][0]["name"], "read_file");
|
|
238
|
+
assert_eq!(payload["tools"][0]["input_schema"]["type"], "object");
|
|
239
|
+
let msgs = payload["messages"].as_array().unwrap();
|
|
240
|
+
assert_eq!(msgs.len(), 3);
|
|
241
|
+
assert_eq!(msgs[1]["role"], "assistant");
|
|
242
|
+
assert_eq!(msgs[1]["content"][0]["type"], "tool_use");
|
|
243
|
+
assert_eq!(msgs[2]["role"], "user");
|
|
244
|
+
assert_eq!(msgs[2]["content"][0]["type"], "tool_result");
|
|
245
|
+
assert_eq!(msgs[2]["content"][0]["tool_use_id"], "call_1");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[test]
|
|
249
|
+
fn anthropic_payload_marks_the_system_prompt_as_cacheable() {
|
|
250
|
+
let request = native_request(vec![ChatMessage::text(ChatRole::User, "hi")], None);
|
|
251
|
+
let cfg = MintConfig::default();
|
|
252
|
+
let payload = anthropic_chat_payload(&cfg, "claude-x", &request, false).unwrap();
|
|
253
|
+
|
|
254
|
+
assert_eq!(payload["system"][0]["type"], "text");
|
|
255
|
+
assert_eq!(payload["system"][0]["text"], "You are Mint.");
|
|
256
|
+
assert_eq!(payload["system"][0]["cache_control"]["type"], "ephemeral");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn review_anthropic_tool_results_preserve_failure_and_success_flags() {
|
|
261
|
+
for (text, is_error) in [
|
|
262
|
+
("User denied MCP tool call", true),
|
|
263
|
+
("MCP call was not approved; user feedback", true),
|
|
264
|
+
("Error: literal diagnostic label", false),
|
|
265
|
+
] {
|
|
266
|
+
let request = native_request(
|
|
267
|
+
vec![ChatMessage {
|
|
268
|
+
role: ChatRole::Tool,
|
|
269
|
+
content: vec![ContentBlock::ToolResult {
|
|
270
|
+
tool_use_id: "mcp-call".into(),
|
|
271
|
+
content: text.into(),
|
|
272
|
+
is_error,
|
|
273
|
+
}],
|
|
274
|
+
}],
|
|
275
|
+
None,
|
|
276
|
+
);
|
|
277
|
+
let payload =
|
|
278
|
+
anthropic_chat_payload(&MintConfig::default(), "claude-x", &request, false).unwrap();
|
|
279
|
+
assert_eq!(payload["messages"][0]["content"][0]["is_error"], is_error);
|
|
280
|
+
assert_eq!(payload["messages"][0]["content"][0]["content"], text);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[test]
|
|
285
|
+
fn anthropic_payload_omits_the_cache_breakpoint_for_an_empty_system_prompt() {
|
|
286
|
+
let mut request = native_request(vec![ChatMessage::text(ChatRole::User, "hi")], None);
|
|
287
|
+
request.system_instruction = String::new();
|
|
288
|
+
let cfg = MintConfig::default();
|
|
289
|
+
let payload = anthropic_chat_payload(&cfg, "claude-x", &request, false).unwrap();
|
|
290
|
+
|
|
291
|
+
// Falls back to the plain-string shape rather than an empty cacheable
|
|
292
|
+
// block, matching the pre-caching behavior exactly for this case.
|
|
293
|
+
assert_eq!(payload["system"], "");
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn anthropic_payload_marks_only_the_last_tool_as_the_cache_breakpoint() {
|
|
298
|
+
let second_tool = ToolSpec {
|
|
299
|
+
name: "write_file".into(),
|
|
300
|
+
description: "Writes a file".into(),
|
|
301
|
+
parameters: json!({ "type": "object" }),
|
|
302
|
+
};
|
|
303
|
+
let request = native_request(
|
|
304
|
+
vec![ChatMessage::text(ChatRole::User, "hi")],
|
|
305
|
+
Some(vec![sample_tool(), second_tool]),
|
|
306
|
+
);
|
|
307
|
+
let cfg = MintConfig::default();
|
|
308
|
+
let payload = anthropic_chat_payload(&cfg, "claude-x", &request, false).unwrap();
|
|
309
|
+
|
|
310
|
+
let tools = payload["tools"].as_array().unwrap();
|
|
311
|
+
assert_eq!(tools.len(), 2);
|
|
312
|
+
assert!(tools[0].get("cache_control").is_none());
|
|
313
|
+
assert_eq!(tools[1]["cache_control"]["type"], "ephemeral");
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[test]
|
|
317
|
+
fn anthropic_attaches_cache_control_to_penultimate_turn() {
|
|
318
|
+
let request = native_request(
|
|
319
|
+
vec![
|
|
320
|
+
ChatMessage::text(ChatRole::User, "step 1 user"),
|
|
321
|
+
ChatMessage::text(ChatRole::Assistant, "step 1 assistant"),
|
|
322
|
+
ChatMessage::text(ChatRole::User, "step 2 user"),
|
|
323
|
+
],
|
|
324
|
+
None,
|
|
325
|
+
);
|
|
326
|
+
let cfg = MintConfig::default();
|
|
327
|
+
let payload = anthropic_chat_payload(&cfg, "claude-x", &request, false).unwrap();
|
|
328
|
+
let messages = payload["messages"].as_array().unwrap();
|
|
329
|
+
assert_eq!(messages.len(), 3);
|
|
330
|
+
assert!(messages[0]["content"][0].get("cache_control").is_none());
|
|
331
|
+
assert_eq!(
|
|
332
|
+
messages[1]["content"][0]["cache_control"]["type"],
|
|
333
|
+
"ephemeral"
|
|
334
|
+
);
|
|
335
|
+
assert!(messages[2]["content"][0].get("cache_control").is_none());
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
#[test]
|
|
339
|
+
fn parses_anthropic_reply_with_text_and_tool_use_blocks() {
|
|
340
|
+
let response = json!({
|
|
341
|
+
"stop_reason": "tool_use",
|
|
342
|
+
"content": [
|
|
343
|
+
{ "type": "text", "text": "Let me check that file." },
|
|
344
|
+
{ "type": "tool_use", "id": "toolu_1", "name": "read_file", "input": { "path": "main.rs" } }
|
|
345
|
+
]
|
|
346
|
+
});
|
|
347
|
+
let reply = parse_anthropic_reply("claude-x".into(), &response).unwrap();
|
|
348
|
+
assert_eq!(reply.text, "Let me check that file.");
|
|
349
|
+
let calls = reply.tool_calls.unwrap();
|
|
350
|
+
assert_eq!(calls.len(), 1);
|
|
351
|
+
assert_eq!(calls[0].name, "read_file");
|
|
352
|
+
assert_eq!(calls[0].input["path"], "main.rs");
|
|
353
|
+
assert_eq!(reply.stop_reason.as_deref(), Some("tool_use"));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#[test]
|
|
357
|
+
fn parses_anthropic_reply_with_tool_use_only_and_no_text() {
|
|
358
|
+
let response = json!({
|
|
359
|
+
"content": [
|
|
360
|
+
{ "type": "tool_use", "id": "toolu_1", "name": "run_shell", "input": { "command": "ls" } }
|
|
361
|
+
]
|
|
362
|
+
});
|
|
363
|
+
let reply = parse_anthropic_reply("claude-x".into(), &response).unwrap();
|
|
364
|
+
assert_eq!(reply.text, "");
|
|
365
|
+
assert_eq!(reply.tool_calls.unwrap().len(), 1);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#[test]
|
|
369
|
+
fn openai_native_payload_encodes_tool_calls_and_results() {
|
|
370
|
+
let messages = vec![
|
|
371
|
+
ChatMessage::text(ChatRole::User, "list files"),
|
|
372
|
+
ChatMessage {
|
|
373
|
+
role: ChatRole::Assistant,
|
|
374
|
+
content: vec![ContentBlock::ToolUse {
|
|
375
|
+
id: "call_1".into(),
|
|
376
|
+
name: "list_files".into(),
|
|
377
|
+
input: json!({ "path": "." }),
|
|
378
|
+
thought_signature: None,
|
|
379
|
+
}],
|
|
380
|
+
},
|
|
381
|
+
ChatMessage {
|
|
382
|
+
role: ChatRole::Tool,
|
|
383
|
+
content: vec![ContentBlock::ToolResult {
|
|
384
|
+
tool_use_id: "call_1".into(),
|
|
385
|
+
content: "Cargo.toml\nsrc/".into(),
|
|
386
|
+
is_error: false,
|
|
387
|
+
}],
|
|
388
|
+
},
|
|
389
|
+
];
|
|
390
|
+
let request = native_request(messages, Some(vec![sample_tool()]));
|
|
391
|
+
let cfg = MintConfig::default();
|
|
392
|
+
let payload = openai_chat_payload(&cfg, "gpt-x", &request, false).unwrap();
|
|
393
|
+
|
|
394
|
+
assert_eq!(payload["tools"][0]["type"], "function");
|
|
395
|
+
assert_eq!(payload["tools"][0]["function"]["name"], "read_file");
|
|
396
|
+
let msgs = payload["messages"].as_array().unwrap();
|
|
397
|
+
// system + user + assistant(tool_calls) + tool
|
|
398
|
+
assert_eq!(msgs.len(), 4);
|
|
399
|
+
assert_eq!(msgs[2]["role"], "assistant");
|
|
400
|
+
assert_eq!(msgs[2]["tool_calls"][0]["function"]["name"], "list_files");
|
|
401
|
+
assert_eq!(msgs[3]["role"], "tool");
|
|
402
|
+
assert_eq!(msgs[3]["tool_call_id"], "call_1");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#[test]
|
|
406
|
+
fn openai_native_payload_forwards_a_screenshot_riding_a_tool_result_as_a_user_turn() {
|
|
407
|
+
// OpenAI's Chat Completions `tool` role only accepts string content, so an
|
|
408
|
+
// `Image` block attached to a tool result (e.g. `view_image`) must
|
|
409
|
+
// surface as a follow-up `user` message instead of being dropped.
|
|
410
|
+
let messages = vec![ChatMessage {
|
|
411
|
+
role: ChatRole::Tool,
|
|
412
|
+
content: vec![
|
|
413
|
+
ContentBlock::ToolResult {
|
|
414
|
+
tool_use_id: "call_1".into(),
|
|
415
|
+
content: "[Screenshot captured — see attached image]".into(),
|
|
416
|
+
is_error: false,
|
|
417
|
+
},
|
|
418
|
+
ContentBlock::Image {
|
|
419
|
+
data_uri: "data:image/png;base64,QUJD".into(),
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
}];
|
|
423
|
+
let built = openai_native_messages(&messages).unwrap();
|
|
424
|
+
assert_eq!(built.len(), 2);
|
|
425
|
+
assert_eq!(built[0]["role"], "tool");
|
|
426
|
+
assert_eq!(built[0]["tool_call_id"], "call_1");
|
|
427
|
+
assert_eq!(built[1]["role"], "user");
|
|
428
|
+
assert_eq!(built[1]["content"][0]["type"], "image_url");
|
|
429
|
+
assert_eq!(
|
|
430
|
+
built[1]["content"][0]["image_url"]["url"],
|
|
431
|
+
"data:image/png;base64,QUJD"
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
#[test]
|
|
436
|
+
fn openai_native_messages_attaches_reasoning_content_for_tool_calls() {
|
|
437
|
+
let messages = vec![ChatMessage {
|
|
438
|
+
role: ChatRole::Assistant,
|
|
439
|
+
content: vec![ContentBlock::ToolUse {
|
|
440
|
+
id: "call_abc".into(),
|
|
441
|
+
name: "read_file".into(),
|
|
442
|
+
input: json!({"path": "src/main.rs"}),
|
|
443
|
+
thought_signature: None,
|
|
444
|
+
}],
|
|
445
|
+
}];
|
|
446
|
+
let built = openai_native_messages(&messages).unwrap();
|
|
447
|
+
assert_eq!(built.len(), 1);
|
|
448
|
+
assert_eq!(built[0]["role"], "assistant");
|
|
449
|
+
assert_eq!(built[0]["reasoning_content"], json!(""));
|
|
450
|
+
assert_eq!(built[0]["tool_calls"][0]["function"]["name"], "read_file");
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
#[test]
|
|
454
|
+
fn openai_serializes_compacted_history_with_role_alternation() {
|
|
455
|
+
let messages = vec![
|
|
456
|
+
ChatMessage::text(ChatRole::User, "Fix the bug"),
|
|
457
|
+
ChatMessage::text(
|
|
458
|
+
ChatRole::Assistant,
|
|
459
|
+
"Summary of earlier steps:\n- read file.rs\n- found syntax error",
|
|
460
|
+
),
|
|
461
|
+
ChatMessage::text(
|
|
462
|
+
ChatRole::User,
|
|
463
|
+
"Please continue the task from where you left off based on the summary above.",
|
|
464
|
+
),
|
|
465
|
+
ChatMessage {
|
|
466
|
+
role: ChatRole::Assistant,
|
|
467
|
+
content: vec![ContentBlock::ToolUse {
|
|
468
|
+
id: "call_1".into(),
|
|
469
|
+
name: "apply_patch".into(),
|
|
470
|
+
input: json!({"path": "file.rs", "patch": "..."}),
|
|
471
|
+
thought_signature: None,
|
|
472
|
+
}],
|
|
473
|
+
},
|
|
474
|
+
ChatMessage {
|
|
475
|
+
role: ChatRole::Tool,
|
|
476
|
+
content: vec![ContentBlock::ToolResult {
|
|
477
|
+
tool_use_id: "call_1".into(),
|
|
478
|
+
content: "Patch applied".into(),
|
|
479
|
+
is_error: false,
|
|
480
|
+
}],
|
|
481
|
+
},
|
|
482
|
+
];
|
|
483
|
+
let built = openai_native_messages(&messages).unwrap();
|
|
484
|
+
assert_eq!(built.len(), 5);
|
|
485
|
+
assert_eq!(built[0]["role"], "user");
|
|
486
|
+
assert_eq!(built[1]["role"], "assistant");
|
|
487
|
+
assert_eq!(built[2]["role"], "user");
|
|
488
|
+
assert_eq!(built[3]["role"], "assistant");
|
|
489
|
+
assert_eq!(built[4]["role"], "tool");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
#[test]
|
|
493
|
+
fn anthropic_serializes_compacted_history_with_role_alternation() {
|
|
494
|
+
let messages = vec![
|
|
495
|
+
ChatMessage::text(ChatRole::User, "Fix the bug"),
|
|
496
|
+
ChatMessage::text(ChatRole::Assistant, "Summary of earlier steps"),
|
|
497
|
+
ChatMessage::text(ChatRole::User, "Please continue"),
|
|
498
|
+
ChatMessage {
|
|
499
|
+
role: ChatRole::Assistant,
|
|
500
|
+
content: vec![ContentBlock::ToolUse {
|
|
501
|
+
id: "toolu_1".into(),
|
|
502
|
+
name: "read_file".into(),
|
|
503
|
+
input: json!({"path": "file.rs"}),
|
|
504
|
+
thought_signature: None,
|
|
505
|
+
}],
|
|
506
|
+
},
|
|
507
|
+
ChatMessage {
|
|
508
|
+
role: ChatRole::Tool,
|
|
509
|
+
content: vec![ContentBlock::ToolResult {
|
|
510
|
+
tool_use_id: "toolu_1".into(),
|
|
511
|
+
content: "Code content".into(),
|
|
512
|
+
is_error: false,
|
|
513
|
+
}],
|
|
514
|
+
},
|
|
515
|
+
];
|
|
516
|
+
let built = anthropic_messages(&messages).unwrap();
|
|
517
|
+
assert_eq!(built.len(), 5);
|
|
518
|
+
assert_eq!(built[0]["role"], "user");
|
|
519
|
+
assert_eq!(built[1]["role"], "assistant");
|
|
520
|
+
assert_eq!(built[2]["role"], "user");
|
|
521
|
+
assert_eq!(built[3]["role"], "assistant");
|
|
522
|
+
assert_eq!(built[4]["role"], "user");
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
#[test]
|
|
526
|
+
fn gemini_serializes_compacted_history_with_role_alternation() {
|
|
527
|
+
let messages = vec![
|
|
528
|
+
ChatMessage::text(ChatRole::User, "Fix the bug"),
|
|
529
|
+
ChatMessage::text(ChatRole::Assistant, "Summary of earlier steps"),
|
|
530
|
+
ChatMessage::text(ChatRole::User, "Please continue"),
|
|
531
|
+
ChatMessage {
|
|
532
|
+
role: ChatRole::Assistant,
|
|
533
|
+
content: vec![ContentBlock::ToolUse {
|
|
534
|
+
id: "call_1".into(),
|
|
535
|
+
name: "read_file".into(),
|
|
536
|
+
input: json!({"path": "file.rs"}),
|
|
537
|
+
thought_signature: None,
|
|
538
|
+
}],
|
|
539
|
+
},
|
|
540
|
+
ChatMessage {
|
|
541
|
+
role: ChatRole::Tool,
|
|
542
|
+
content: vec![ContentBlock::ToolResult {
|
|
543
|
+
tool_use_id: "call_1".into(),
|
|
544
|
+
content: "Code content".into(),
|
|
545
|
+
is_error: false,
|
|
546
|
+
}],
|
|
547
|
+
},
|
|
548
|
+
];
|
|
549
|
+
let built = gemini_native_contents(&messages).unwrap();
|
|
550
|
+
assert!(built.len() >= 4);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[test]
|
|
554
|
+
fn openai_allows_audio_attachments() {
|
|
555
|
+
let request = ChatRequest {
|
|
556
|
+
message: "transcribe this".into(),
|
|
557
|
+
system_instruction: String::new(),
|
|
558
|
+
chat_id: None,
|
|
559
|
+
image_data_uri: None,
|
|
560
|
+
audio_data_uri: Some("data:audio/wav;base64,QUJD".into()),
|
|
561
|
+
video_data_uri: None,
|
|
562
|
+
document_attachment: None,
|
|
563
|
+
workspace_path: None,
|
|
564
|
+
agent_id: None,
|
|
565
|
+
plan_mode: false,
|
|
566
|
+
pinned_mcp_server: None,
|
|
567
|
+
messages: None,
|
|
568
|
+
tools: None,
|
|
569
|
+
temperature: None,
|
|
570
|
+
..Default::default()
|
|
571
|
+
};
|
|
572
|
+
assert!(require_supported_attachments("openai", &request).is_ok());
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#[test]
|
|
576
|
+
fn non_openai_openai_family_providers_still_reject_audio() {
|
|
577
|
+
// Only the real OpenAI API is confirmed to speak the `input_audio`
|
|
578
|
+
// schema — proxies/other services built on the same wire format
|
|
579
|
+
// (local_openai, openrouter, deepseek, huggingface) and Anthropic
|
|
580
|
+
// (no audio modality at all) must stay rejected.
|
|
581
|
+
let request = ChatRequest {
|
|
582
|
+
message: "transcribe this".into(),
|
|
583
|
+
system_instruction: String::new(),
|
|
584
|
+
chat_id: None,
|
|
585
|
+
image_data_uri: None,
|
|
586
|
+
audio_data_uri: Some("data:audio/wav;base64,QUJD".into()),
|
|
587
|
+
video_data_uri: None,
|
|
588
|
+
document_attachment: None,
|
|
589
|
+
workspace_path: None,
|
|
590
|
+
agent_id: None,
|
|
591
|
+
plan_mode: false,
|
|
592
|
+
pinned_mcp_server: None,
|
|
593
|
+
messages: None,
|
|
594
|
+
tools: None,
|
|
595
|
+
temperature: None,
|
|
596
|
+
..Default::default()
|
|
597
|
+
};
|
|
598
|
+
for provider in [
|
|
599
|
+
"local_openai",
|
|
600
|
+
"openrouter",
|
|
601
|
+
"deepseek",
|
|
602
|
+
"huggingface",
|
|
603
|
+
"anthropic",
|
|
604
|
+
] {
|
|
605
|
+
let error = require_supported_attachments(provider, &request).unwrap_err();
|
|
606
|
+
assert!(
|
|
607
|
+
matches!(&error, ChatError::UnsupportedAttachments(p) if p == provider),
|
|
608
|
+
"expected {provider} to reject audio, got {error:?}"
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
#[test]
|
|
614
|
+
fn openai_audio_part_splits_the_data_uri_and_maps_wav_format() {
|
|
615
|
+
let part = openai_audio_part("data:audio/wav;base64,QUJD").unwrap();
|
|
616
|
+
assert_eq!(part["type"], "input_audio");
|
|
617
|
+
assert_eq!(part["input_audio"]["data"], "QUJD");
|
|
618
|
+
assert_eq!(part["input_audio"]["format"], "wav");
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#[test]
|
|
622
|
+
fn openai_audio_part_defaults_unrecognized_formats_to_mp3() {
|
|
623
|
+
let part = openai_audio_part("data:audio/webm;base64,QUJD").unwrap();
|
|
624
|
+
assert_eq!(part["input_audio"]["format"], "mp3");
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#[test]
|
|
628
|
+
fn openai_audio_part_rejects_a_malformed_data_uri() {
|
|
629
|
+
assert!(openai_audio_part("not-a-data-uri").is_err());
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
#[test]
|
|
633
|
+
fn openai_message_content_embeds_audio_alongside_text() {
|
|
634
|
+
let content =
|
|
635
|
+
openai_message_content("what is this?", None, Some("data:audio/mp3;base64,QUJD")).unwrap();
|
|
636
|
+
let parts = content.as_array().unwrap();
|
|
637
|
+
assert_eq!(parts[0]["type"], "text");
|
|
638
|
+
assert_eq!(parts[1]["type"], "input_audio");
|
|
639
|
+
assert_eq!(parts[1]["input_audio"]["data"], "QUJD");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#[test]
|
|
643
|
+
fn openai_user_content_encodes_an_audio_content_block() {
|
|
644
|
+
let message = ChatMessage {
|
|
645
|
+
role: ChatRole::User,
|
|
646
|
+
content: vec![
|
|
647
|
+
ContentBlock::Text {
|
|
648
|
+
text: "what is this?".into(),
|
|
649
|
+
},
|
|
650
|
+
ContentBlock::Audio {
|
|
651
|
+
data_uri: "data:audio/wav;base64,QUJD".into(),
|
|
652
|
+
},
|
|
653
|
+
],
|
|
654
|
+
};
|
|
655
|
+
let content = openai_user_content(&message).unwrap();
|
|
656
|
+
let parts = content.as_array().unwrap();
|
|
657
|
+
assert_eq!(parts.len(), 2);
|
|
658
|
+
assert_eq!(parts[1]["type"], "input_audio");
|
|
659
|
+
assert_eq!(parts[1]["input_audio"]["format"], "wav");
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
#[test]
|
|
663
|
+
fn parses_openai_style_reply_with_tool_calls() {
|
|
664
|
+
let response = json!({
|
|
665
|
+
"choices": [{
|
|
666
|
+
"finish_reason": "tool_calls",
|
|
667
|
+
"message": {
|
|
668
|
+
"content": null,
|
|
669
|
+
"tool_calls": [{
|
|
670
|
+
"id": "call_1",
|
|
671
|
+
"function": { "name": "read_file", "arguments": "{\"path\":\"main.rs\"}" }
|
|
672
|
+
}]
|
|
673
|
+
}
|
|
674
|
+
}]
|
|
675
|
+
});
|
|
676
|
+
let reply = parse_openai_style_reply("gpt-x".into(), &response).unwrap();
|
|
677
|
+
assert_eq!(reply.text, "");
|
|
678
|
+
let calls = reply.tool_calls.unwrap();
|
|
679
|
+
assert_eq!(calls[0].name, "read_file");
|
|
680
|
+
assert_eq!(calls[0].input["path"], "main.rs");
|
|
681
|
+
assert_eq!(reply.stop_reason.as_deref(), Some("tool_calls"));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
#[test]
|
|
685
|
+
fn gemini_sanitize_schema_strips_additional_properties_at_any_depth() {
|
|
686
|
+
let schema = json!({
|
|
687
|
+
"type": "object",
|
|
688
|
+
"properties": {
|
|
689
|
+
"emotions": {
|
|
690
|
+
"type": "object",
|
|
691
|
+
"additionalProperties": { "type": "string" }
|
|
692
|
+
},
|
|
693
|
+
"nested": {
|
|
694
|
+
"type": "array",
|
|
695
|
+
"items": {
|
|
696
|
+
"type": "object",
|
|
697
|
+
"additionalProperties": true
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
let sanitized = gemini_sanitize_schema(&schema);
|
|
703
|
+
assert!(
|
|
704
|
+
sanitized["properties"]["emotions"]
|
|
705
|
+
.get("additionalProperties")
|
|
706
|
+
.is_none()
|
|
707
|
+
);
|
|
708
|
+
assert!(
|
|
709
|
+
sanitized["properties"]["nested"]["items"]
|
|
710
|
+
.get("additionalProperties")
|
|
711
|
+
.is_none()
|
|
712
|
+
);
|
|
713
|
+
// Everything else is preserved untouched.
|
|
714
|
+
assert_eq!(sanitized["properties"]["emotions"]["type"], "object");
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
#[test]
|
|
718
|
+
fn gemini_native_payload_strips_additional_properties_from_tool_schemas() {
|
|
719
|
+
let tool = ToolSpec {
|
|
720
|
+
name: "avatar_signal".into(),
|
|
721
|
+
description: "set avatar state".into(),
|
|
722
|
+
parameters: json!({
|
|
723
|
+
"type": "object",
|
|
724
|
+
"properties": {
|
|
725
|
+
"emotions": {
|
|
726
|
+
"type": "object",
|
|
727
|
+
"additionalProperties": { "type": "string" }
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}),
|
|
731
|
+
};
|
|
732
|
+
let request = native_request(
|
|
733
|
+
vec![ChatMessage::text(ChatRole::User, "hi")],
|
|
734
|
+
Some(vec![tool]),
|
|
735
|
+
);
|
|
736
|
+
let payload = gemini_native_payload(&request).unwrap();
|
|
737
|
+
assert!(
|
|
738
|
+
payload["tools"][0]["functionDeclarations"][0]["parameters"]["properties"]["emotions"]
|
|
739
|
+
.get("additionalProperties")
|
|
740
|
+
.is_none()
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
#[test]
|
|
745
|
+
fn gemini_native_payload_uses_function_declarations_and_resolves_tool_result_names() {
|
|
746
|
+
let messages = vec![
|
|
747
|
+
ChatMessage::text(ChatRole::User, "check weather"),
|
|
748
|
+
ChatMessage {
|
|
749
|
+
role: ChatRole::Assistant,
|
|
750
|
+
content: vec![ContentBlock::ToolUse {
|
|
751
|
+
id: "call_0".into(),
|
|
752
|
+
name: "weather".into(),
|
|
753
|
+
input: json!({ "city": "Bangkok" }),
|
|
754
|
+
thought_signature: None,
|
|
755
|
+
}],
|
|
756
|
+
},
|
|
757
|
+
ChatMessage {
|
|
758
|
+
role: ChatRole::Tool,
|
|
759
|
+
content: vec![ContentBlock::ToolResult {
|
|
760
|
+
tool_use_id: "call_0".into(),
|
|
761
|
+
content: "32C, sunny".into(),
|
|
762
|
+
is_error: false,
|
|
763
|
+
}],
|
|
764
|
+
},
|
|
765
|
+
];
|
|
766
|
+
let request = native_request(messages, Some(vec![sample_tool()]));
|
|
767
|
+
let payload = gemini_native_payload(&request).unwrap();
|
|
768
|
+
|
|
769
|
+
assert_eq!(
|
|
770
|
+
payload["tools"][0]["functionDeclarations"][0]["name"],
|
|
771
|
+
"read_file"
|
|
772
|
+
);
|
|
773
|
+
let contents = payload["contents"].as_array().unwrap();
|
|
774
|
+
assert_eq!(contents[1]["role"], "model");
|
|
775
|
+
assert_eq!(contents[1]["parts"][0]["functionCall"]["name"], "weather");
|
|
776
|
+
assert_eq!(contents[2]["role"], "user");
|
|
777
|
+
// Resolved back to the function name "weather", not the call id "call_0".
|
|
778
|
+
assert_eq!(
|
|
779
|
+
contents[2]["parts"][0]["functionResponse"]["name"],
|
|
780
|
+
"weather"
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
#[test]
|
|
785
|
+
fn gemini_native_payload_replays_the_thought_signature_on_the_next_turn() {
|
|
786
|
+
// Gemini 3.x rejects a replayed `functionCall` that's missing the
|
|
787
|
+
// `thoughtSignature` the model originally attached to it (400
|
|
788
|
+
// INVALID_ARGUMENT) — without this, every second tool-calling turn
|
|
789
|
+
// failed and silently fell back to a different, weaker provider.
|
|
790
|
+
let messages = vec![
|
|
791
|
+
ChatMessage::text(ChatRole::User, "check weather"),
|
|
792
|
+
ChatMessage {
|
|
793
|
+
role: ChatRole::Assistant,
|
|
794
|
+
content: vec![ContentBlock::ToolUse {
|
|
795
|
+
id: "call_0".into(),
|
|
796
|
+
name: "weather".into(),
|
|
797
|
+
input: json!({ "city": "Bangkok" }),
|
|
798
|
+
thought_signature: Some("opaque-signature-abc".into()),
|
|
799
|
+
}],
|
|
800
|
+
},
|
|
801
|
+
];
|
|
802
|
+
let request = native_request(messages, Some(vec![sample_tool()]));
|
|
803
|
+
let payload = gemini_native_payload(&request).unwrap();
|
|
804
|
+
|
|
805
|
+
let contents = payload["contents"].as_array().unwrap();
|
|
806
|
+
assert_eq!(
|
|
807
|
+
contents[1]["parts"][0]["thoughtSignature"],
|
|
808
|
+
"opaque-signature-abc"
|
|
809
|
+
);
|
|
810
|
+
// Sibling of `functionCall`, not nested inside it.
|
|
811
|
+
assert_eq!(contents[1]["parts"][0]["functionCall"]["name"], "weather");
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
#[test]
|
|
815
|
+
fn gemini_native_payload_omits_thought_signature_when_absent() {
|
|
816
|
+
let messages = vec![ChatMessage {
|
|
817
|
+
role: ChatRole::Assistant,
|
|
818
|
+
content: vec![ContentBlock::ToolUse {
|
|
819
|
+
id: "call_0".into(),
|
|
820
|
+
name: "weather".into(),
|
|
821
|
+
input: json!({ "city": "Bangkok" }),
|
|
822
|
+
thought_signature: None,
|
|
823
|
+
}],
|
|
824
|
+
}];
|
|
825
|
+
let request = native_request(messages, Some(vec![sample_tool()]));
|
|
826
|
+
let payload = gemini_native_payload(&request).unwrap();
|
|
827
|
+
|
|
828
|
+
let contents = payload["contents"].as_array().unwrap();
|
|
829
|
+
assert!(contents[0]["parts"][0].get("thoughtSignature").is_none());
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#[test]
|
|
833
|
+
fn parses_gemini_reply_with_function_call_parts() {
|
|
834
|
+
let response = json!({
|
|
835
|
+
"candidates": [{
|
|
836
|
+
"finishReason": "STOP",
|
|
837
|
+
"content": {
|
|
838
|
+
"parts": [
|
|
839
|
+
{ "text": "Checking the weather now." },
|
|
840
|
+
{ "functionCall": { "name": "weather", "args": { "city": "Bangkok" } } }
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
}]
|
|
844
|
+
});
|
|
845
|
+
let reply = parse_gemini_reply("gemini-x".into(), &response).unwrap();
|
|
846
|
+
assert_eq!(reply.text, "Checking the weather now.");
|
|
847
|
+
let calls = reply.tool_calls.unwrap();
|
|
848
|
+
assert_eq!(calls[0].name, "weather");
|
|
849
|
+
assert_eq!(calls[0].input["city"], "Bangkok");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
#[test]
|
|
853
|
+
fn parses_gemini_reply_captures_the_thought_signature_off_the_function_call_part() {
|
|
854
|
+
let response = json!({
|
|
855
|
+
"candidates": [{
|
|
856
|
+
"finishReason": "STOP",
|
|
857
|
+
"content": {
|
|
858
|
+
"parts": [{
|
|
859
|
+
"functionCall": { "name": "weather", "args": { "city": "Bangkok" } },
|
|
860
|
+
"thoughtSignature": "opaque-signature-abc"
|
|
861
|
+
}]
|
|
862
|
+
}
|
|
863
|
+
}]
|
|
864
|
+
});
|
|
865
|
+
let reply = parse_gemini_reply("gemini-x".into(), &response).unwrap();
|
|
866
|
+
let calls = reply.tool_calls.unwrap();
|
|
867
|
+
assert_eq!(
|
|
868
|
+
calls[0].thought_signature.as_deref(),
|
|
869
|
+
Some("opaque-signature-abc")
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
#[test]
|
|
874
|
+
fn ollama_native_messages_send_tool_call_arguments_as_object_not_string() {
|
|
875
|
+
let messages = vec![
|
|
876
|
+
ChatMessage::text(ChatRole::User, "list files"),
|
|
877
|
+
ChatMessage {
|
|
878
|
+
role: ChatRole::Assistant,
|
|
879
|
+
content: vec![ContentBlock::ToolUse {
|
|
880
|
+
id: "call_1".into(),
|
|
881
|
+
name: "list_files".into(),
|
|
882
|
+
input: json!({ "path": "." }),
|
|
883
|
+
thought_signature: None,
|
|
884
|
+
}],
|
|
885
|
+
},
|
|
886
|
+
];
|
|
887
|
+
let built = ollama_native_messages(&messages);
|
|
888
|
+
assert_eq!(built[1]["tool_calls"][0]["function"]["name"], "list_files");
|
|
889
|
+
// Ollama expects `arguments` as a JSON object, unlike OpenAI's stringified form.
|
|
890
|
+
assert!(built[1]["tool_calls"][0]["function"]["arguments"].is_object());
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
#[test]
|
|
894
|
+
fn ollama_native_messages_forward_a_screenshot_riding_a_tool_result_as_a_user_turn() {
|
|
895
|
+
// Ollama's `/api/chat` `tool` role message has no image field, so an
|
|
896
|
+
// `Image` block attached to a tool result (e.g. `view_image`)
|
|
897
|
+
// must surface as a follow-up `user` message with the `images` field.
|
|
898
|
+
let messages = vec![ChatMessage {
|
|
899
|
+
role: ChatRole::Tool,
|
|
900
|
+
content: vec![
|
|
901
|
+
ContentBlock::ToolResult {
|
|
902
|
+
tool_use_id: "call_1".into(),
|
|
903
|
+
content: "[Screenshot captured — see attached image]".into(),
|
|
904
|
+
is_error: false,
|
|
905
|
+
},
|
|
906
|
+
ContentBlock::Image {
|
|
907
|
+
data_uri: "data:image/png;base64,QUJD".into(),
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
}];
|
|
911
|
+
let built = ollama_native_messages(&messages);
|
|
912
|
+
assert_eq!(built.len(), 2);
|
|
913
|
+
assert_eq!(built[0]["role"], "tool");
|
|
914
|
+
assert_eq!(built[1]["role"], "user");
|
|
915
|
+
assert_eq!(built[1]["images"][0], "QUJD");
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
#[test]
|
|
919
|
+
fn parses_ollama_reply_from_native_message_envelope() {
|
|
920
|
+
let response = json!({
|
|
921
|
+
"message": {
|
|
922
|
+
"content": "",
|
|
923
|
+
"tool_calls": [{
|
|
924
|
+
"function": { "name": "list_files", "arguments": { "path": "." } }
|
|
925
|
+
}]
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
let reply = parse_ollama_reply("llama3.1".into(), &response).unwrap();
|
|
929
|
+
assert_eq!(reply.tool_calls.unwrap()[0].name, "list_files");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
#[test]
|
|
933
|
+
fn extracts_input_and_output_tokens_from_each_provider_usage_shape() {
|
|
934
|
+
let anthropic = parse_anthropic_reply(
|
|
935
|
+
"claude-x".into(),
|
|
936
|
+
&json!({
|
|
937
|
+
"content": [{ "type": "text", "text": "hi" }],
|
|
938
|
+
"usage": { "input_tokens": 100, "output_tokens": 20 }
|
|
939
|
+
}),
|
|
940
|
+
)
|
|
941
|
+
.unwrap();
|
|
942
|
+
assert_eq!(
|
|
943
|
+
(anthropic.input_tokens, anthropic.output_tokens),
|
|
944
|
+
(Some(100), Some(20))
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
let openai = parse_openai_style_reply(
|
|
948
|
+
"gpt-x".into(),
|
|
949
|
+
&json!({
|
|
950
|
+
"choices": [{ "message": { "content": "hi" } }],
|
|
951
|
+
"usage": { "prompt_tokens": 130, "completion_tokens": 20, "total_tokens": 150 }
|
|
952
|
+
}),
|
|
953
|
+
)
|
|
954
|
+
.unwrap();
|
|
955
|
+
assert_eq!(
|
|
956
|
+
(openai.input_tokens, openai.output_tokens),
|
|
957
|
+
(Some(130), Some(20))
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
let gemini = parse_gemini_reply(
|
|
961
|
+
"gemini-x".into(),
|
|
962
|
+
&json!({
|
|
963
|
+
"candidates": [{ "content": { "parts": [{ "text": "hi" }] } }],
|
|
964
|
+
"usageMetadata": {
|
|
965
|
+
"promptTokenCount": 160,
|
|
966
|
+
"candidatesTokenCount": 40,
|
|
967
|
+
"totalTokenCount": 200
|
|
968
|
+
}
|
|
969
|
+
}),
|
|
970
|
+
)
|
|
971
|
+
.unwrap();
|
|
972
|
+
assert_eq!(
|
|
973
|
+
(gemini.input_tokens, gemini.output_tokens),
|
|
974
|
+
(Some(160), Some(40))
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
let ollama = parse_ollama_reply(
|
|
978
|
+
"llama3.1".into(),
|
|
979
|
+
&json!({
|
|
980
|
+
"message": { "content": "hi" },
|
|
981
|
+
"prompt_eval_count": 80,
|
|
982
|
+
"eval_count": 40
|
|
983
|
+
}),
|
|
984
|
+
)
|
|
985
|
+
.unwrap();
|
|
986
|
+
assert_eq!(
|
|
987
|
+
(ollama.input_tokens, ollama.output_tokens),
|
|
988
|
+
(Some(80), Some(40))
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
#[test]
|
|
992
|
+
fn anthropic_cached_prompt_tokens_count_toward_context_in_json_and_streams() {
|
|
993
|
+
let usage = json!({"input_tokens":100,"cache_read_input_tokens":98_000,"cache_creation_input_tokens":1_000,"output_tokens":400,"cache_creation":{"ephemeral_5m_input_tokens":1_000}});
|
|
994
|
+
let reply = parse_anthropic_reply(
|
|
995
|
+
"claude-test".into(),
|
|
996
|
+
&json!({"content":[{"type":"text","text":"Ready"}],"usage":usage}),
|
|
997
|
+
)
|
|
998
|
+
.unwrap();
|
|
999
|
+
assert_eq!(reply.input_tokens, Some(99_100));
|
|
1000
|
+
let mut stream = StreamReplyAccumulator::default();
|
|
1001
|
+
stream.consume_anthropic(
|
|
1002
|
+
&json!({"type":"message_start","message":{"usage":usage}}),
|
|
1003
|
+
&mut |_| {},
|
|
1004
|
+
);
|
|
1005
|
+
stream.consume_anthropic(
|
|
1006
|
+
&json!({"type":"content_block_delta","delta":{"type":"text_delta","text":"Ready"}}),
|
|
1007
|
+
&mut |_| {},
|
|
1008
|
+
);
|
|
1009
|
+
stream.consume_anthropic(
|
|
1010
|
+
&json!({"type":"message_delta","usage":{"output_tokens":400}}),
|
|
1011
|
+
&mut |_| {},
|
|
1012
|
+
);
|
|
1013
|
+
let reply = stream.finish("claude-test".into()).unwrap();
|
|
1014
|
+
assert_eq!(reply.input_tokens, Some(99_100));
|
|
1015
|
+
assert_eq!(reply.output_tokens, Some(400));
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
#[test]
|
|
1019
|
+
fn missing_usage_data_yields_no_token_counts() {
|
|
1020
|
+
let reply = parse_openai_style_reply(
|
|
1021
|
+
"gpt-x".into(),
|
|
1022
|
+
&json!({ "choices": [{ "message": { "content": "hi" } }] }),
|
|
1023
|
+
)
|
|
1024
|
+
.unwrap();
|
|
1025
|
+
assert_eq!((reply.input_tokens, reply.output_tokens), (None, None));
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
#[tokio::test]
|
|
1029
|
+
async fn send_openai_style_request_classifies_a_connect_failure_as_network_unavailable() {
|
|
1030
|
+
let client = Client::new();
|
|
1031
|
+
// Port 1 is reserved and nothing listens on it, so this fails fast
|
|
1032
|
+
// with a connection error rather than timing out.
|
|
1033
|
+
let result = send_openai_style_request(
|
|
1034
|
+
client.post("http://127.0.0.1:1/chat/completions"),
|
|
1035
|
+
"deepseek",
|
|
1036
|
+
)
|
|
1037
|
+
.await;
|
|
1038
|
+
assert!(
|
|
1039
|
+
matches!(result, Err(ChatError::NetworkUnavailable)),
|
|
1040
|
+
"expected NetworkUnavailable, got {result:?}"
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
#[tokio::test]
|
|
1045
|
+
async fn send_openai_style_request_classifies_402_as_insufficient_balance() {
|
|
1046
|
+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
1047
|
+
use tokio::net::TcpListener;
|
|
1048
|
+
|
|
1049
|
+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
1050
|
+
let addr = listener.local_addr().unwrap();
|
|
1051
|
+
tokio::spawn(async move {
|
|
1052
|
+
let (mut socket, _) = listener.accept().await.unwrap();
|
|
1053
|
+
let mut buf = [0u8; 1024];
|
|
1054
|
+
let _ = socket.read(&mut buf).await;
|
|
1055
|
+
let body = "{}";
|
|
1056
|
+
let response = format!(
|
|
1057
|
+
"HTTP/1.1 402 Payment Required\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{body}",
|
|
1058
|
+
body.len()
|
|
1059
|
+
);
|
|
1060
|
+
let _ = socket.write_all(response.as_bytes()).await;
|
|
1061
|
+
let _ = socket.shutdown().await;
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
let client = Client::new();
|
|
1065
|
+
let result = send_openai_style_request(
|
|
1066
|
+
client.post(format!("http://{addr}/chat/completions")),
|
|
1067
|
+
"deepseek",
|
|
1068
|
+
)
|
|
1069
|
+
.await;
|
|
1070
|
+
assert!(
|
|
1071
|
+
matches!(&result, Err(ChatError::InsufficientBalance(p)) if p == "deepseek"),
|
|
1072
|
+
"expected InsufficientBalance(\"deepseek\"), got {result:?}"
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
#[test]
|
|
1077
|
+
fn fallback_provider_order_moves_gemini_to_the_front() {
|
|
1078
|
+
let config = MintConfig {
|
|
1079
|
+
anthropic_api_key: "a".into(),
|
|
1080
|
+
openai_api_key: "b".into(),
|
|
1081
|
+
deepseek_api_key: "c".into(),
|
|
1082
|
+
api_key: "d".into(), // gemini
|
|
1083
|
+
hf_api_key: "e".into(),
|
|
1084
|
+
..MintConfig::default()
|
|
1085
|
+
};
|
|
1086
|
+
let order = fallback_provider_order(&config);
|
|
1087
|
+
assert_eq!(order[0], "gemini");
|
|
1088
|
+
// Everyone else keeps their existing relative order from
|
|
1089
|
+
// `available_providers()` — only gemini moves.
|
|
1090
|
+
let without_gemini: Vec<&String> = order.iter().skip(1).collect();
|
|
1091
|
+
assert_eq!(
|
|
1092
|
+
without_gemini,
|
|
1093
|
+
vec!["anthropic", "openai", "deepseek", "huggingface", "ollama"]
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
#[test]
|
|
1098
|
+
fn fallback_provider_order_is_unchanged_without_gemini_configured() {
|
|
1099
|
+
let config = MintConfig {
|
|
1100
|
+
deepseek_api_key: "c".into(),
|
|
1101
|
+
hf_api_key: "e".into(),
|
|
1102
|
+
..MintConfig::default()
|
|
1103
|
+
};
|
|
1104
|
+
assert_eq!(
|
|
1105
|
+
fallback_provider_order(&config),
|
|
1106
|
+
config.available_providers()
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
#[test]
|
|
1111
|
+
fn openai_payload_includes_smart_temperature_for_deepseek_and_omits_for_o1() {
|
|
1112
|
+
let request = native_request(vec![ChatMessage::text(ChatRole::User, "hi")], None);
|
|
1113
|
+
|
|
1114
|
+
// DeepSeek smart default is 0.6
|
|
1115
|
+
let ds_config = MintConfig {
|
|
1116
|
+
ai_provider: "deepseek".into(),
|
|
1117
|
+
deepseek_model: "deepseek-chat".into(),
|
|
1118
|
+
..MintConfig::default()
|
|
1119
|
+
};
|
|
1120
|
+
let ds_payload = openai_chat_payload(&ds_config, "deepseek-chat", &request, false).unwrap();
|
|
1121
|
+
assert_eq!(ds_payload["temperature"], 0.6);
|
|
1122
|
+
|
|
1123
|
+
// Standard OpenAI model default is 0.2
|
|
1124
|
+
let openai_config = MintConfig {
|
|
1125
|
+
ai_provider: "openai".into(),
|
|
1126
|
+
openai_model: "gpt-4o".into(),
|
|
1127
|
+
..MintConfig::default()
|
|
1128
|
+
};
|
|
1129
|
+
let openai_payload = openai_chat_payload(&openai_config, "gpt-4o", &request, false).unwrap();
|
|
1130
|
+
assert_eq!(openai_payload["temperature"], 0.2);
|
|
1131
|
+
|
|
1132
|
+
// Reasoning models (o1, o3) omit temperature parameter
|
|
1133
|
+
let o1_payload = openai_chat_payload(&openai_config, "o1-preview", &request, false).unwrap();
|
|
1134
|
+
assert!(o1_payload.get("temperature").is_none());
|
|
1135
|
+
|
|
1136
|
+
let o3_payload = openai_chat_payload(&openai_config, "o3-mini", &request, false).unwrap();
|
|
1137
|
+
assert!(o3_payload.get("temperature").is_none());
|
|
1138
|
+
|
|
1139
|
+
// Explicit request temperature override
|
|
1140
|
+
let mut override_req = request.clone();
|
|
1141
|
+
override_req.temperature = Some(0.75);
|
|
1142
|
+
let override_payload =
|
|
1143
|
+
openai_chat_payload(&ds_config, "deepseek-chat", &override_req, false).unwrap();
|
|
1144
|
+
assert_eq!(override_payload["temperature"], 0.75);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
#[test]
|
|
1148
|
+
fn anthropic_payload_includes_effective_temperature() {
|
|
1149
|
+
let request = native_request(vec![ChatMessage::text(ChatRole::User, "hi")], None);
|
|
1150
|
+
let config = MintConfig {
|
|
1151
|
+
ai_provider: "anthropic".into(),
|
|
1152
|
+
anthropic_model: "claude-sonnet-5".into(),
|
|
1153
|
+
thinking_enabled: false,
|
|
1154
|
+
..MintConfig::default()
|
|
1155
|
+
};
|
|
1156
|
+
let payload = anthropic_chat_payload(&config, "claude-sonnet-5", &request, false).unwrap();
|
|
1157
|
+
assert_eq!(payload["temperature"], 0.2);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
#[test]
|
|
1161
|
+
fn parse_openai_style_reply_extracts_deepseek_reasoning_content() {
|
|
1162
|
+
let response = json!({
|
|
1163
|
+
"choices": [{
|
|
1164
|
+
"message": {
|
|
1165
|
+
"role": "assistant",
|
|
1166
|
+
"content": "",
|
|
1167
|
+
"reasoning_content": "Evaluating 15 + 6 * (8 - 3) - 14 / 2 step by step...",
|
|
1168
|
+
"tool_calls": [{
|
|
1169
|
+
"id": "call_calc_1",
|
|
1170
|
+
"type": "function",
|
|
1171
|
+
"function": {
|
|
1172
|
+
"name": "calculation",
|
|
1173
|
+
"arguments": "{\"expression\":\"15 + 6 * (8 - 3) - 14 / 2\"}"
|
|
1174
|
+
}
|
|
1175
|
+
}]
|
|
1176
|
+
},
|
|
1177
|
+
"finish_reason": "tool_calls"
|
|
1178
|
+
}]
|
|
1179
|
+
});
|
|
1180
|
+
let reply = parse_openai_style_reply("deepseek-reasoner".into(), &response).unwrap();
|
|
1181
|
+
assert_eq!(reply.text, "");
|
|
1182
|
+
assert_eq!(
|
|
1183
|
+
reply.thought.as_deref(),
|
|
1184
|
+
Some("Evaluating 15 + 6 * (8 - 3) - 14 / 2 step by step...")
|
|
1185
|
+
);
|
|
1186
|
+
let calls = reply.tool_calls.unwrap();
|
|
1187
|
+
assert_eq!(calls.len(), 1);
|
|
1188
|
+
assert_eq!(calls[0].name, "calculation");
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
#[test]
|
|
1192
|
+
fn parse_openai_style_reply_extracts_openrouter_reasoning() {
|
|
1193
|
+
let response = json!({
|
|
1194
|
+
"choices": [{
|
|
1195
|
+
"message": {
|
|
1196
|
+
"role": "assistant",
|
|
1197
|
+
"content": "The answer is 38.",
|
|
1198
|
+
"reasoning": "First parentheses: 8 - 3 = 5..."
|
|
1199
|
+
},
|
|
1200
|
+
"finish_reason": "stop"
|
|
1201
|
+
}]
|
|
1202
|
+
});
|
|
1203
|
+
let reply = parse_openai_style_reply("deepseek/deepseek-r1".into(), &response).unwrap();
|
|
1204
|
+
assert_eq!(reply.text, "The answer is 38.");
|
|
1205
|
+
assert_eq!(
|
|
1206
|
+
reply.thought.as_deref(),
|
|
1207
|
+
Some("First parentheses: 8 - 3 = 5...")
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
#[test]
|
|
1212
|
+
fn parse_openai_style_reply_extracts_think_tags() {
|
|
1213
|
+
let response = json!({
|
|
1214
|
+
"choices": [{
|
|
1215
|
+
"message": {
|
|
1216
|
+
"role": "assistant",
|
|
1217
|
+
"content": "<think>\nLet me solve this problem.\n</think>\nThe result is 42."
|
|
1218
|
+
},
|
|
1219
|
+
"finish_reason": "stop"
|
|
1220
|
+
}]
|
|
1221
|
+
});
|
|
1222
|
+
let reply = parse_openai_style_reply("qwq-32b".into(), &response).unwrap();
|
|
1223
|
+
assert_eq!(reply.text, "The result is 42.");
|
|
1224
|
+
assert_eq!(reply.thought.as_deref(), Some("Let me solve this problem."));
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
#[test]
|
|
1228
|
+
fn parse_gemini_reply_extracts_thought_parts() {
|
|
1229
|
+
let response = json!({
|
|
1230
|
+
"candidates": [{
|
|
1231
|
+
"content": {
|
|
1232
|
+
"parts": [
|
|
1233
|
+
{ "text": "Thinking through the formula...", "thought": true },
|
|
1234
|
+
{ "text": "The answer is 38." }
|
|
1235
|
+
]
|
|
1236
|
+
},
|
|
1237
|
+
"finishReason": "STOP"
|
|
1238
|
+
}]
|
|
1239
|
+
});
|
|
1240
|
+
let reply = parse_gemini_reply("gemini-2.0-flash-thinking".into(), &response).unwrap();
|
|
1241
|
+
assert_eq!(reply.text, "The answer is 38.");
|
|
1242
|
+
assert_eq!(
|
|
1243
|
+
reply.thought.as_deref(),
|
|
1244
|
+
Some("Thinking through the formula...")
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
#[test]
|
|
1249
|
+
fn parse_anthropic_reply_extracts_thinking_blocks() {
|
|
1250
|
+
let response = json!({
|
|
1251
|
+
"content": [
|
|
1252
|
+
{ "type": "thinking", "thinking": "Let's break down the arithmetic..." },
|
|
1253
|
+
{ "type": "text", "text": "The result is 38." }
|
|
1254
|
+
],
|
|
1255
|
+
"stop_reason": "end_turn"
|
|
1256
|
+
});
|
|
1257
|
+
let reply = parse_anthropic_reply("claude-3-7-sonnet".into(), &response).unwrap();
|
|
1258
|
+
assert_eq!(reply.text, "The result is 38.");
|
|
1259
|
+
assert_eq!(
|
|
1260
|
+
reply.thought.as_deref(),
|
|
1261
|
+
Some("Let's break down the arithmetic...")
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
#[test]
|
|
1266
|
+
fn parse_ollama_reply_extracts_thinking_field() {
|
|
1267
|
+
let response = json!({
|
|
1268
|
+
"message": {
|
|
1269
|
+
"role": "assistant",
|
|
1270
|
+
"content": "Result is 38.",
|
|
1271
|
+
"thinking": "Ollama internal thinking trace..."
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
let reply = parse_ollama_reply("deepseek-r1:8b".into(), &response).unwrap();
|
|
1275
|
+
assert_eq!(reply.text, "Result is 38.");
|
|
1276
|
+
assert_eq!(
|
|
1277
|
+
reply.thought.as_deref(),
|
|
1278
|
+
Some("Ollama internal thinking trace...")
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
#[test]
|
|
1283
|
+
fn decode_tool_arguments_sanitizes_fences_tokens_and_trailing_commas() {
|
|
1284
|
+
// 1. Markdown fences
|
|
1285
|
+
let fenced =
|
|
1286
|
+
Value::String("```json\n{\"command\": \"curl -s https://example.com\"}\n```".into());
|
|
1287
|
+
let decoded = decode_tool_arguments(&fenced);
|
|
1288
|
+
assert_eq!(decoded["command"], "curl -s https://example.com");
|
|
1289
|
+
|
|
1290
|
+
// 2. DeepSeek DSML token
|
|
1291
|
+
let dsml = Value::String("{\"command\": \"ls -la\"}</||DSML||".into());
|
|
1292
|
+
let decoded_dsml = decode_tool_arguments(&dsml);
|
|
1293
|
+
assert_eq!(decoded_dsml["command"], "ls -la");
|
|
1294
|
+
|
|
1295
|
+
// 3. Trailing commas
|
|
1296
|
+
let trailing = Value::String("{\"command\": \"echo ok\", \"flag\": true, }".into());
|
|
1297
|
+
let decoded_trailing = decode_tool_arguments(&trailing);
|
|
1298
|
+
assert_eq!(decoded_trailing["command"], "echo ok");
|
|
1299
|
+
assert_eq!(decoded_trailing["flag"], true);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
#[test]
|
|
1303
|
+
fn gemini_thinking_config_includes_thoughts_flag_and_skips_non_native_models() {
|
|
1304
|
+
let mut config = MintConfig::default();
|
|
1305
|
+
config.ai_provider = "gemini".into();
|
|
1306
|
+
config.gemini_model = "gemini-2.5-flash".into();
|
|
1307
|
+
|
|
1308
|
+
let request_on = ChatRequest {
|
|
1309
|
+
message: "Solve 12 * 13".into(),
|
|
1310
|
+
thinking_enabled: Some(true),
|
|
1311
|
+
thinking_effort: Some("high".into()),
|
|
1312
|
+
..Default::default()
|
|
1313
|
+
};
|
|
1314
|
+
let payload_on = gemini_chat_payload(&config, &request_on).unwrap();
|
|
1315
|
+
assert_eq!(
|
|
1316
|
+
payload_on["generationConfig"]["thinkingConfig"]["includeThoughts"],
|
|
1317
|
+
true
|
|
1318
|
+
);
|
|
1319
|
+
assert_eq!(
|
|
1320
|
+
payload_on["generationConfig"]["thinkingConfig"]["thinkingBudget"],
|
|
1321
|
+
16384
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
let request_off = ChatRequest {
|
|
1325
|
+
message: "Hello".into(),
|
|
1326
|
+
thinking_enabled: Some(false),
|
|
1327
|
+
..Default::default()
|
|
1328
|
+
};
|
|
1329
|
+
let payload_off = gemini_chat_payload(&config, &request_off).unwrap();
|
|
1330
|
+
assert_eq!(
|
|
1331
|
+
payload_off["generationConfig"]["thinkingConfig"]["includeThoughts"],
|
|
1332
|
+
false
|
|
1333
|
+
);
|
|
1334
|
+
assert_eq!(
|
|
1335
|
+
payload_off["generationConfig"]["thinkingConfig"]["thinkingBudget"],
|
|
1336
|
+
0
|
|
1337
|
+
);
|
|
1338
|
+
|
|
1339
|
+
// Non-text image generation model does not attach thinkingConfig or prompt directive
|
|
1340
|
+
config.gemini_model = "nano-banana-pro-preview".into();
|
|
1341
|
+
let payload_banana = gemini_chat_payload(&config, &request_on).unwrap();
|
|
1342
|
+
assert!(
|
|
1343
|
+
payload_banana["generationConfig"]
|
|
1344
|
+
.get("thinkingConfig")
|
|
1345
|
+
.is_none()
|
|
1346
|
+
);
|
|
1347
|
+
let banana_sys = payload_banana["systemInstruction"]["parts"][0]["text"]
|
|
1348
|
+
.as_str()
|
|
1349
|
+
.unwrap_or("");
|
|
1350
|
+
assert!(!banana_sys.contains("<think>"));
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
#[test]
|
|
1354
|
+
fn deepseek_v4_and_prompt_fallback_thinking_payloads() {
|
|
1355
|
+
let mut config = MintConfig::default();
|
|
1356
|
+
config.ai_provider = "deepseek".into();
|
|
1357
|
+
config.deepseek_model = "deepseek-flash".into();
|
|
1358
|
+
|
|
1359
|
+
let req_on = ChatRequest {
|
|
1360
|
+
message: "Explain quantum entanglement".into(),
|
|
1361
|
+
system_instruction: "You are helpful.".into(),
|
|
1362
|
+
thinking_enabled: Some(true),
|
|
1363
|
+
thinking_effort: Some("extra_high".into()),
|
|
1364
|
+
..Default::default()
|
|
1365
|
+
};
|
|
1366
|
+
let payload_on = openai_chat_payload(&config, "deepseek-flash", &req_on, false).unwrap();
|
|
1367
|
+
assert_eq!(payload_on["reasoning_effort"], "max");
|
|
1368
|
+
assert_eq!(payload_on["thinking"]["type"], "enabled");
|
|
1369
|
+
assert!(payload_on.get("temperature").is_none());
|
|
1370
|
+
// Native thinking model should not get prompt-fallback directive
|
|
1371
|
+
let sys_on = payload_on["messages"][0]["content"].as_str().unwrap();
|
|
1372
|
+
assert_eq!(sys_on, "You are helpful.");
|
|
1373
|
+
|
|
1374
|
+
let req_off = ChatRequest {
|
|
1375
|
+
message: "Hi".into(),
|
|
1376
|
+
system_instruction: "You are helpful.".into(),
|
|
1377
|
+
thinking_enabled: Some(false),
|
|
1378
|
+
..Default::default()
|
|
1379
|
+
};
|
|
1380
|
+
let payload_off = openai_chat_payload(&config, "deepseek-flash", &req_off, false).unwrap();
|
|
1381
|
+
assert_eq!(payload_off["reasoning_effort"], "none");
|
|
1382
|
+
assert_eq!(payload_off["thinking"]["type"], "disabled");
|
|
1383
|
+
assert!(payload_off.get("temperature").is_some());
|
|
1384
|
+
|
|
1385
|
+
// Non-native chat model (`gpt-4o`) with thinking_enabled = true gets prompt-based CoT directive
|
|
1386
|
+
config.ai_provider = "openai".into();
|
|
1387
|
+
config.openai_model = "gpt-4o".into();
|
|
1388
|
+
let payload_fallback = openai_chat_payload(&config, "gpt-4o", &req_on, false).unwrap();
|
|
1389
|
+
let sys_fallback = payload_fallback["messages"][0]["content"].as_str().unwrap();
|
|
1390
|
+
assert!(sys_fallback.contains("[Thinking Mode: Enabled (Extra High Effort)]"));
|
|
1391
|
+
assert!(sys_fallback.contains("<think>...</think>"));
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
#[test]
|
|
1395
|
+
fn extract_think_tag_supports_both_think_and_thought_tags() {
|
|
1396
|
+
let (thought1, cleaned1) = extract_think_tag("<think>\nStep 1\n</think>\nFinal answer 1");
|
|
1397
|
+
assert_eq!(cleaned1, "Final answer 1");
|
|
1398
|
+
assert_eq!(thought1.as_deref(), Some("Step 1"));
|
|
1399
|
+
|
|
1400
|
+
let (thought2, cleaned2) = extract_think_tag("<thought>\nStep 2\n</thought>\nFinal answer 2");
|
|
1401
|
+
assert_eq!(cleaned2, "Final answer 2");
|
|
1402
|
+
assert_eq!(thought2.as_deref(), Some("Step 2"));
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
#[tokio::test]
|
|
1406
|
+
async fn collect_stream_reply_preserves_multibyte_utf8_split_across_chunks() {
|
|
1407
|
+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
1408
|
+
use tokio::net::TcpListener;
|
|
1409
|
+
|
|
1410
|
+
let expected_text = "ข้อมูลนี้ค้นหาและตรวจสอบผ่าน Brave Search เรียบร้อยแล้วค่ะ";
|
|
1411
|
+
let sse_line = format!(
|
|
1412
|
+
"data: {}\n\n",
|
|
1413
|
+
json!({
|
|
1414
|
+
"candidates": [{
|
|
1415
|
+
"content": {
|
|
1416
|
+
"parts": [{"text": expected_text}]
|
|
1417
|
+
}
|
|
1418
|
+
}]
|
|
1419
|
+
})
|
|
1420
|
+
);
|
|
1421
|
+
let bytes = sse_line.into_bytes();
|
|
1422
|
+
// Locate the 3-byte UTF-8 sequence for Thai tone mark `้` (0xE0, 0xB9, 0x89)
|
|
1423
|
+
// and split the HTTP chunk right after the first byte (0xE0).
|
|
1424
|
+
let split_index = bytes
|
|
1425
|
+
.windows(3)
|
|
1426
|
+
.position(|w| w == [0xE0, 0xB9, 0x89])
|
|
1427
|
+
.expect("should contain Thai tone mark UTF-8 bytes")
|
|
1428
|
+
+ 1;
|
|
1429
|
+
let first_chunk = bytes[..split_index].to_vec();
|
|
1430
|
+
let second_chunk = bytes[split_index..].to_vec();
|
|
1431
|
+
|
|
1432
|
+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
|
1433
|
+
let addr = listener.local_addr().unwrap();
|
|
1434
|
+
tokio::spawn(async move {
|
|
1435
|
+
let (mut socket, _) = listener.accept().await.unwrap();
|
|
1436
|
+
let mut buf = [0u8; 1024];
|
|
1437
|
+
let _ = socket.read(&mut buf).await;
|
|
1438
|
+
let headers = "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nTransfer-Encoding: chunked\r\nConnection: close\r\n\r\n";
|
|
1439
|
+
socket.write_all(headers.as_bytes()).await.unwrap();
|
|
1440
|
+
socket
|
|
1441
|
+
.write_all(format!("{:X}\r\n", first_chunk.len()).as_bytes())
|
|
1442
|
+
.await
|
|
1443
|
+
.unwrap();
|
|
1444
|
+
socket.write_all(&first_chunk).await.unwrap();
|
|
1445
|
+
socket.write_all(b"\r\n").await.unwrap();
|
|
1446
|
+
socket.flush().await.unwrap();
|
|
1447
|
+
|
|
1448
|
+
socket
|
|
1449
|
+
.write_all(format!("{:X}\r\n", second_chunk.len()).as_bytes())
|
|
1450
|
+
.await
|
|
1451
|
+
.unwrap();
|
|
1452
|
+
socket.write_all(&second_chunk).await.unwrap();
|
|
1453
|
+
socket.write_all(b"\r\n0\r\n\r\n").await.unwrap();
|
|
1454
|
+
let _ = socket.shutdown().await;
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
let client = Client::new();
|
|
1458
|
+
let response = client
|
|
1459
|
+
.get(format!("http://{addr}/stream"))
|
|
1460
|
+
.send()
|
|
1461
|
+
.await
|
|
1462
|
+
.unwrap();
|
|
1463
|
+
let mut streamed = String::new();
|
|
1464
|
+
let reply = collect_stream_reply(
|
|
1465
|
+
response,
|
|
1466
|
+
StreamFormat::Gemini,
|
|
1467
|
+
"gemini-3.1-flash-lite-preview".into(),
|
|
1468
|
+
&mut |event| {
|
|
1469
|
+
if let ChatStreamEvent::TextDelta { delta } = event {
|
|
1470
|
+
streamed.push_str(&delta);
|
|
1471
|
+
}
|
|
1472
|
+
},
|
|
1473
|
+
)
|
|
1474
|
+
.await
|
|
1475
|
+
.unwrap();
|
|
1476
|
+
|
|
1477
|
+
assert_eq!(reply.text, expected_text);
|
|
1478
|
+
assert_eq!(streamed, expected_text);
|
|
1479
|
+
}
|