mycode-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. mycode/__init__.py +1 -0
  2. mycode/cli/__init__.py +1 -0
  3. mycode/cli/chat.py +650 -0
  4. mycode/cli/main.py +245 -0
  5. mycode/cli/render.py +693 -0
  6. mycode/cli/runtime.py +271 -0
  7. mycode/cli/theme.py +109 -0
  8. mycode/core/__init__.py +30 -0
  9. mycode/core/agent.py +515 -0
  10. mycode/core/config.py +551 -0
  11. mycode/core/messages.py +166 -0
  12. mycode/core/models.py +144 -0
  13. mycode/core/models_catalog.json +2090 -0
  14. mycode/core/providers/__init__.py +86 -0
  15. mycode/core/providers/anthropic_like.py +366 -0
  16. mycode/core/providers/base.py +351 -0
  17. mycode/core/providers/gemini.py +321 -0
  18. mycode/core/providers/openai_chat.py +356 -0
  19. mycode/core/providers/openai_responses.py +326 -0
  20. mycode/core/session.py +537 -0
  21. mycode/core/system_prompt.md +10 -0
  22. mycode/core/system_prompt.py +319 -0
  23. mycode/core/tools.py +898 -0
  24. mycode/server/__init__.py +1 -0
  25. mycode/server/app.py +52 -0
  26. mycode/server/deps.py +29 -0
  27. mycode/server/routers/__init__.py +7 -0
  28. mycode/server/routers/chat.py +320 -0
  29. mycode/server/routers/sessions.py +77 -0
  30. mycode/server/routers/workspaces.py +92 -0
  31. mycode/server/run_manager.py +195 -0
  32. mycode/server/schemas.py +66 -0
  33. mycode/server/static/assets/EditDiff-C1ql7kft.js +12 -0
  34. mycode/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  35. mycode/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  36. mycode/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  37. mycode/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  38. mycode/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  39. mycode/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  40. mycode/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  41. mycode/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  42. mycode/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  43. mycode/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  44. mycode/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  45. mycode/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  46. mycode/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  47. mycode/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  48. mycode/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  49. mycode/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  50. mycode/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  51. mycode/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  52. mycode/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  53. mycode/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  54. mycode/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  55. mycode/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  56. mycode/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  57. mycode/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  58. mycode/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  59. mycode/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  60. mycode/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  61. mycode/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  62. mycode/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  63. mycode/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  64. mycode/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  65. mycode/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  66. mycode/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  67. mycode/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  68. mycode/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  69. mycode/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  70. mycode/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  71. mycode/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  72. mycode/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  73. mycode/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  74. mycode/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  75. mycode/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  76. mycode/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  77. mycode/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  78. mycode/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  79. mycode/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  80. mycode/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  81. mycode/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  82. mycode/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  83. mycode/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  84. mycode/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  85. mycode/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  86. mycode/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  87. mycode/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  88. mycode/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  89. mycode/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  90. mycode/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  91. mycode/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  92. mycode/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  93. mycode/server/static/assets/abap-BdImnpbu.js +1 -0
  94. mycode/server/static/assets/actionscript-3-CoDkCxhg.js +1 -0
  95. mycode/server/static/assets/ada-bCR0ucgS.js +1 -0
  96. mycode/server/static/assets/andromeeda-C4gqWexZ.js +1 -0
  97. mycode/server/static/assets/angular-html-DA-rfuFy.js +1 -0
  98. mycode/server/static/assets/angular-ts-BrjP3tb8.js +1 -0
  99. mycode/server/static/assets/apache-Pmp26Uib.js +1 -0
  100. mycode/server/static/assets/apex-D8_7TLub.js +1 -0
  101. mycode/server/static/assets/apl-CORt7UWP.js +1 -0
  102. mycode/server/static/assets/applescript-Co6uUVPk.js +1 -0
  103. mycode/server/static/assets/ara-BRHolxvo.js +1 -0
  104. mycode/server/static/assets/asciidoc-Ve4PFQV2.js +1 -0
  105. mycode/server/static/assets/asm-D_Q5rh1f.js +1 -0
  106. mycode/server/static/assets/astro-HNnZUWAn.js +1 -0
  107. mycode/server/static/assets/aurora-x-D-2ljcwZ.js +1 -0
  108. mycode/server/static/assets/auto-render-xntwXHOX.js +261 -0
  109. mycode/server/static/assets/awk-DMzUqQB5.js +1 -0
  110. mycode/server/static/assets/ayu-dark-DYE7WIF3.js +1 -0
  111. mycode/server/static/assets/ayu-light-BA47KaF1.js +1 -0
  112. mycode/server/static/assets/ayu-mirage-32ctXXKs.js +1 -0
  113. mycode/server/static/assets/ballerina-BFfxhgS-.js +1 -0
  114. mycode/server/static/assets/bat-BkioyH1T.js +1 -0
  115. mycode/server/static/assets/beancount-k_qm7-4y.js +1 -0
  116. mycode/server/static/assets/berry-uYugtg8r.js +1 -0
  117. mycode/server/static/assets/bibtex-CHM0blh-.js +1 -0
  118. mycode/server/static/assets/bicep-Bmn6On1c.js +1 -0
  119. mycode/server/static/assets/bird2-BIv1doCn.js +1 -0
  120. mycode/server/static/assets/blade-BjGOyj-B.js +1 -0
  121. mycode/server/static/assets/bsl-BO_Y6i37.js +1 -0
  122. mycode/server/static/assets/c-BIGW1oBm.js +1 -0
  123. mycode/server/static/assets/c3-eo99z4R2.js +1 -0
  124. mycode/server/static/assets/cadence-Bv_4Rxtq.js +1 -0
  125. mycode/server/static/assets/cairo-KRGpt6FW.js +1 -0
  126. mycode/server/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  127. mycode/server/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  128. mycode/server/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  129. mycode/server/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  130. mycode/server/static/assets/clarity-D53aC0YG.js +1 -0
  131. mycode/server/static/assets/clojure-P80f7IUj.js +1 -0
  132. mycode/server/static/assets/cmake-D1j8_8rp.js +1 -0
  133. mycode/server/static/assets/cobol-nBiQ_Alo.js +1 -0
  134. mycode/server/static/assets/codeowners-Bp6g37R7.js +1 -0
  135. mycode/server/static/assets/codeql-DsOJ9woJ.js +1 -0
  136. mycode/server/static/assets/coffee-Ch7k5sss.js +1 -0
  137. mycode/server/static/assets/common-lisp-Cg-RD9OK.js +1 -0
  138. mycode/server/static/assets/coq-DkFqJrB1.js +1 -0
  139. mycode/server/static/assets/cpp-CofmeUqb.js +1 -0
  140. mycode/server/static/assets/crystal-DNxU26gB.js +1 -0
  141. mycode/server/static/assets/csharp-COcwbKMJ.js +1 -0
  142. mycode/server/static/assets/css-CLj8gQPS.js +1 -0
  143. mycode/server/static/assets/csv-fuZLfV_i.js +1 -0
  144. mycode/server/static/assets/cue-D82EKSYY.js +1 -0
  145. mycode/server/static/assets/cypher-COkxafJQ.js +1 -0
  146. mycode/server/static/assets/d-85-TOEBH.js +1 -0
  147. mycode/server/static/assets/dark-plus-C3mMm8J8.js +1 -0
  148. mycode/server/static/assets/dart-bE4Kk8sk.js +1 -0
  149. mycode/server/static/assets/dax-CEL-wOlO.js +1 -0
  150. mycode/server/static/assets/desktop-BmXAJ9_W.js +1 -0
  151. mycode/server/static/assets/diff-D97Zzqfu.js +1 -0
  152. mycode/server/static/assets/docker-BcOcwvcX.js +1 -0
  153. mycode/server/static/assets/dotenv-Da5cRb03.js +1 -0
  154. mycode/server/static/assets/dracula-BzJJZx-M.js +1 -0
  155. mycode/server/static/assets/dracula-soft-BXkSAIEj.js +1 -0
  156. mycode/server/static/assets/dream-maker-BtqSS_iP.js +1 -0
  157. mycode/server/static/assets/edge-FbVlp4U3.js +1 -0
  158. mycode/server/static/assets/elixir-CkH2-t6x.js +1 -0
  159. mycode/server/static/assets/elm-DbKCFpqz.js +1 -0
  160. mycode/server/static/assets/emacs-lisp-CXvaQtF9.js +1 -0
  161. mycode/server/static/assets/erb-BYCe7drp.js +1 -0
  162. mycode/server/static/assets/erlang-DsQrWhSR.js +1 -0
  163. mycode/server/static/assets/everforest-dark-BgDCqdQA.js +1 -0
  164. mycode/server/static/assets/everforest-light-C8M2exoo.js +1 -0
  165. mycode/server/static/assets/fennel-BYunw83y.js +1 -0
  166. mycode/server/static/assets/fish-BvzEVeQv.js +1 -0
  167. mycode/server/static/assets/fluent-C4IJs8-o.js +1 -0
  168. mycode/server/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  169. mycode/server/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
  170. mycode/server/static/assets/fsharp-CXgrBDvD.js +1 -0
  171. mycode/server/static/assets/gdresource-BOOCDP_w.js +1 -0
  172. mycode/server/static/assets/gdscript-C5YyOfLZ.js +1 -0
  173. mycode/server/static/assets/gdshader-DkwncUOv.js +1 -0
  174. mycode/server/static/assets/genie-D0YGMca9.js +1 -0
  175. mycode/server/static/assets/gherkin-DyxjwDmM.js +1 -0
  176. mycode/server/static/assets/git-commit-F4YmCXRG.js +1 -0
  177. mycode/server/static/assets/git-rebase-r7XF79zn.js +1 -0
  178. mycode/server/static/assets/github-dark-DHJKELXO.js +1 -0
  179. mycode/server/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
  180. mycode/server/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  181. mycode/server/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  182. mycode/server/static/assets/github-light-DAi9KRSo.js +1 -0
  183. mycode/server/static/assets/github-light-default-D7oLnXFd.js +1 -0
  184. mycode/server/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  185. mycode/server/static/assets/gleam-BspZqrRM.js +1 -0
  186. mycode/server/static/assets/glimmer-js-ByusRIyA.js +1 -0
  187. mycode/server/static/assets/glimmer-ts-BfAWNZQY.js +1 -0
  188. mycode/server/static/assets/glsl-DplSGwfg.js +1 -0
  189. mycode/server/static/assets/gn-n2N0HUVH.js +1 -0
  190. mycode/server/static/assets/gnuplot-DdkO51Og.js +1 -0
  191. mycode/server/static/assets/go-C27-OAKa.js +1 -0
  192. mycode/server/static/assets/graphql-ChdNCCLP.js +1 -0
  193. mycode/server/static/assets/groovy-gcz8RCvz.js +1 -0
  194. mycode/server/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  195. mycode/server/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  196. mycode/server/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  197. mycode/server/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  198. mycode/server/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  199. mycode/server/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  200. mycode/server/static/assets/hack-i7_Ulhet.js +1 -0
  201. mycode/server/static/assets/haml-D5jkg6IW.js +1 -0
  202. mycode/server/static/assets/handlebars-BpdQsYii.js +1 -0
  203. mycode/server/static/assets/haskell-Df6bDoY_.js +1 -0
  204. mycode/server/static/assets/haxe-CzTSHFRz.js +1 -0
  205. mycode/server/static/assets/hcl-BWvSN4gD.js +1 -0
  206. mycode/server/static/assets/hjson-D5-asLiD.js +1 -0
  207. mycode/server/static/assets/hlsl-D3lLCCz7.js +1 -0
  208. mycode/server/static/assets/horizon-BUw7H-hv.js +1 -0
  209. mycode/server/static/assets/horizon-bright-CUuTKBJd.js +1 -0
  210. mycode/server/static/assets/houston-DnULxvSX.js +1 -0
  211. mycode/server/static/assets/html-derivative-DlHx6ybY.js +1 -0
  212. mycode/server/static/assets/html-pp8916En.js +1 -0
  213. mycode/server/static/assets/http-jrhK8wxY.js +1 -0
  214. mycode/server/static/assets/hurl-irOxFIW8.js +1 -0
  215. mycode/server/static/assets/hxml-Bvhsp5Yf.js +1 -0
  216. mycode/server/static/assets/hy-DFXneXwc.js +1 -0
  217. mycode/server/static/assets/imba-DGztddWO.js +1 -0
  218. mycode/server/static/assets/index-B4e4WQPq.css +1 -0
  219. mycode/server/static/assets/index-C2xTNJGd.js +203 -0
  220. mycode/server/static/assets/ini-BEwlwnbL.js +1 -0
  221. mycode/server/static/assets/java-CylS5w8V.js +1 -0
  222. mycode/server/static/assets/javascript-wDzz0qaB.js +1 -0
  223. mycode/server/static/assets/jinja-f2NsQr07.js +1 -0
  224. mycode/server/static/assets/jison-wvAkD_A8.js +1 -0
  225. mycode/server/static/assets/json-Cp-IABpG.js +1 -0
  226. mycode/server/static/assets/json5-C9tS-k6U.js +1 -0
  227. mycode/server/static/assets/jsonc-Des-eS-w.js +1 -0
  228. mycode/server/static/assets/jsonl-DcaNXYhu.js +1 -0
  229. mycode/server/static/assets/jsonnet-DFQXde-d.js +1 -0
  230. mycode/server/static/assets/jssm-C2t-YnRu.js +1 -0
  231. mycode/server/static/assets/jsx-g9-lgVsj.js +1 -0
  232. mycode/server/static/assets/julia-CxzCAyBv.js +1 -0
  233. mycode/server/static/assets/just-VxiPbLrw.js +1 -0
  234. mycode/server/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  235. mycode/server/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  236. mycode/server/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
  237. mycode/server/static/assets/katex-DnJR2-55.css +1 -0
  238. mycode/server/static/assets/kdl-DV7GczEv.js +1 -0
  239. mycode/server/static/assets/kotlin-BdnUsdx6.js +1 -0
  240. mycode/server/static/assets/kusto-wEQ09or8.js +1 -0
  241. mycode/server/static/assets/laserwave-DUszq2jm.js +1 -0
  242. mycode/server/static/assets/latex-CWtU0Tv5.js +1 -0
  243. mycode/server/static/assets/lean-BZvkOJ9d.js +1 -0
  244. mycode/server/static/assets/less-B1dDrJ26.js +1 -0
  245. mycode/server/static/assets/light-plus-B7mTdjB0.js +1 -0
  246. mycode/server/static/assets/liquid-C0sCDyMI.js +1 -0
  247. mycode/server/static/assets/llvm-DjAJT7YJ.js +1 -0
  248. mycode/server/static/assets/log-2UxHyX5q.js +1 -0
  249. mycode/server/static/assets/logo-BtOb2qkB.js +1 -0
  250. mycode/server/static/assets/lua-BaeVxFsk.js +1 -0
  251. mycode/server/static/assets/luau-C-HG3fhB.js +1 -0
  252. mycode/server/static/assets/make-CHLpvVh8.js +1 -0
  253. mycode/server/static/assets/markdown-Cvjx9yec.js +1 -0
  254. mycode/server/static/assets/marko-DjSrsDqO.js +1 -0
  255. mycode/server/static/assets/material-theme-D5KoaKCx.js +1 -0
  256. mycode/server/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
  257. mycode/server/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  258. mycode/server/static/assets/material-theme-ocean-CyktbL80.js +1 -0
  259. mycode/server/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  260. mycode/server/static/assets/matlab-D7o27uSR.js +1 -0
  261. mycode/server/static/assets/mdc-DTYItulj.js +1 -0
  262. mycode/server/static/assets/mdx-Cmh6b_Ma.js +1 -0
  263. mycode/server/static/assets/mermaid-mWjccvbQ.js +1 -0
  264. mycode/server/static/assets/min-dark-CafNBF8u.js +1 -0
  265. mycode/server/static/assets/min-light-CTRr51gU.js +1 -0
  266. mycode/server/static/assets/mipsasm-CKIfxQSi.js +1 -0
  267. mycode/server/static/assets/mojo-rZm6bMo-.js +1 -0
  268. mycode/server/static/assets/monokai-D4h5O-jR.js +1 -0
  269. mycode/server/static/assets/moonbit-_H4v1dQx.js +1 -0
  270. mycode/server/static/assets/move-IF9eRakj.js +1 -0
  271. mycode/server/static/assets/narrat-DRg8JJMk.js +1 -0
  272. mycode/server/static/assets/nextflow-C-mBbutL.js +1 -0
  273. mycode/server/static/assets/nextflow-groovy-vE_lwT2v.js +1 -0
  274. mycode/server/static/assets/nginx-BpAMiNFr.js +1 -0
  275. mycode/server/static/assets/night-owl-C39BiMTA.js +1 -0
  276. mycode/server/static/assets/night-owl-light-CMTm3GFP.js +1 -0
  277. mycode/server/static/assets/nim-BIad80T-.js +1 -0
  278. mycode/server/static/assets/nix-CwoSXNpI.js +1 -0
  279. mycode/server/static/assets/nord-Ddv68eIx.js +1 -0
  280. mycode/server/static/assets/nushell-Cz2AlsmD.js +1 -0
  281. mycode/server/static/assets/objective-c-DXmwc3jG.js +1 -0
  282. mycode/server/static/assets/objective-cpp-CLxacb5B.js +1 -0
  283. mycode/server/static/assets/ocaml-C0hk2d4L.js +1 -0
  284. mycode/server/static/assets/odin-BBf5iR-q.js +1 -0
  285. mycode/server/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  286. mycode/server/static/assets/one-light-C3Wv6jpd.js +1 -0
  287. mycode/server/static/assets/openscad-C4EeE6gA.js +1 -0
  288. mycode/server/static/assets/pascal-D93ZcfNL.js +1 -0
  289. mycode/server/static/assets/perl-NvoQZIq0.js +1 -0
  290. mycode/server/static/assets/php-R6g_5hLQ.js +1 -0
  291. mycode/server/static/assets/pkl-u5AG7uiY.js +1 -0
  292. mycode/server/static/assets/plastic-3e1v2bzS.js +1 -0
  293. mycode/server/static/assets/plsql-ChMvpjG-.js +1 -0
  294. mycode/server/static/assets/po-BTJTHyun.js +1 -0
  295. mycode/server/static/assets/poimandres-CS3Unz2-.js +1 -0
  296. mycode/server/static/assets/polar-C0HS_06l.js +1 -0
  297. mycode/server/static/assets/postcss-CXtECtnM.js +1 -0
  298. mycode/server/static/assets/powerquery-CEu0bR-o.js +1 -0
  299. mycode/server/static/assets/powershell-Dpen1YoG.js +1 -0
  300. mycode/server/static/assets/prisma-Dd19v3D-.js +1 -0
  301. mycode/server/static/assets/prolog-CbFg5uaA.js +1 -0
  302. mycode/server/static/assets/proto-C7zT0LnQ.js +1 -0
  303. mycode/server/static/assets/pug-DKIMFp6K.js +1 -0
  304. mycode/server/static/assets/puppet-BMWR74SV.js +1 -0
  305. mycode/server/static/assets/purescript-CklMAg4u.js +1 -0
  306. mycode/server/static/assets/python-B6aJPvgy.js +1 -0
  307. mycode/server/static/assets/qml-3beO22l8.js +1 -0
  308. mycode/server/static/assets/qmldir-C8lEn-DE.js +1 -0
  309. mycode/server/static/assets/qss-IeuSbFQv.js +1 -0
  310. mycode/server/static/assets/r-Dspwwk_N.js +1 -0
  311. mycode/server/static/assets/racket-BqYA7rlc.js +1 -0
  312. mycode/server/static/assets/raku-DXvB9xmW.js +1 -0
  313. mycode/server/static/assets/razor-BDqjjVU7.js +1 -0
  314. mycode/server/static/assets/red-bN70gL4F.js +1 -0
  315. mycode/server/static/assets/reg-C-SQnVFl.js +1 -0
  316. mycode/server/static/assets/regexp-CDVJQ6XC.js +1 -0
  317. mycode/server/static/assets/rel-C3B-1QV4.js +1 -0
  318. mycode/server/static/assets/riscv-BM1_JUlF.js +1 -0
  319. mycode/server/static/assets/ron-D8l8udqQ.js +1 -0
  320. mycode/server/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  321. mycode/server/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  322. mycode/server/static/assets/rose-pine-qdsjHGoJ.js +1 -0
  323. mycode/server/static/assets/rosmsg-BJDFO7_C.js +1 -0
  324. mycode/server/static/assets/rst-CRjBmOyv.js +1 -0
  325. mycode/server/static/assets/ruby-Wjq7vjNf.js +1 -0
  326. mycode/server/static/assets/rust-B1yitclQ.js +1 -0
  327. mycode/server/static/assets/sas-cz2c8ADy.js +1 -0
  328. mycode/server/static/assets/sass-Cj5Yp3dK.js +1 -0
  329. mycode/server/static/assets/scala-C151Ov-r.js +1 -0
  330. mycode/server/static/assets/scheme-C98Dy4si.js +1 -0
  331. mycode/server/static/assets/scss-D5BDwBP9.js +1 -0
  332. mycode/server/static/assets/sdbl-DVxCFoDh.js +1 -0
  333. mycode/server/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
  334. mycode/server/static/assets/shellscript-Yzrsuije.js +1 -0
  335. mycode/server/static/assets/shellsession-BADoaaVG.js +1 -0
  336. mycode/server/static/assets/slack-dark-BthQWCQV.js +1 -0
  337. mycode/server/static/assets/slack-ochin-DqwNpetd.js +1 -0
  338. mycode/server/static/assets/smalltalk-BERRCDM3.js +1 -0
  339. mycode/server/static/assets/snazzy-light-Bw305WKR.js +1 -0
  340. mycode/server/static/assets/solarized-dark-DXbdFlpD.js +1 -0
  341. mycode/server/static/assets/solarized-light-L9t79GZl.js +1 -0
  342. mycode/server/static/assets/solidity-rGO070M0.js +1 -0
  343. mycode/server/static/assets/soy-8wufbnw4.js +1 -0
  344. mycode/server/static/assets/sparql-rVzFXLq3.js +1 -0
  345. mycode/server/static/assets/splunk-BtCnVYZw.js +1 -0
  346. mycode/server/static/assets/sql-BLtJtn59.js +1 -0
  347. mycode/server/static/assets/ssh-config-_ykCGR6B.js +1 -0
  348. mycode/server/static/assets/stata-BH5u7GGu.js +1 -0
  349. mycode/server/static/assets/stylus-BEDo0Tqx.js +1 -0
  350. mycode/server/static/assets/surrealql-Bq5Q-fJD.js +1 -0
  351. mycode/server/static/assets/svelte-Cy7k_4gC.js +1 -0
  352. mycode/server/static/assets/swift-D82vCrfD.js +1 -0
  353. mycode/server/static/assets/synthwave-84-CbfX1IO0.js +1 -0
  354. mycode/server/static/assets/system-verilog-CnnmHF94.js +1 -0
  355. mycode/server/static/assets/systemd-4A_iFExJ.js +1 -0
  356. mycode/server/static/assets/talonscript-CkByrt1z.js +1 -0
  357. mycode/server/static/assets/tasl-QIJgUcNo.js +1 -0
  358. mycode/server/static/assets/tcl-dwOrl1Do.js +1 -0
  359. mycode/server/static/assets/templ-DhtptRzy.js +1 -0
  360. mycode/server/static/assets/terraform-BETggiCN.js +1 -0
  361. mycode/server/static/assets/tex-idrVyKtj.js +1 -0
  362. mycode/server/static/assets/tokyo-night-hegEt444.js +1 -0
  363. mycode/server/static/assets/toml-vGWfd6FD.js +1 -0
  364. mycode/server/static/assets/ts-tags-DQrlYJgV.js +1 -0
  365. mycode/server/static/assets/tsv-B_m7g4N7.js +1 -0
  366. mycode/server/static/assets/tsx-COt5Ahok.js +1 -0
  367. mycode/server/static/assets/turtle-BsS91CYL.js +1 -0
  368. mycode/server/static/assets/twig-xg9kU7Mw.js +1 -0
  369. mycode/server/static/assets/typescript-BPQ3VLAy.js +1 -0
  370. mycode/server/static/assets/typespec-CAFt9gP4.js +1 -0
  371. mycode/server/static/assets/typst-DHCkPAjA.js +1 -0
  372. mycode/server/static/assets/v-BcVCzyr7.js +1 -0
  373. mycode/server/static/assets/vala-CsfeWuGM.js +1 -0
  374. mycode/server/static/assets/vb-D17OF-Vu.js +1 -0
  375. mycode/server/static/assets/verilog-BQ8w6xss.js +1 -0
  376. mycode/server/static/assets/vesper-DU1UobuO.js +1 -0
  377. mycode/server/static/assets/vhdl-CeAyd5Ju.js +1 -0
  378. mycode/server/static/assets/viml-CJc9bBzg.js +1 -0
  379. mycode/server/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
  380. mycode/server/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
  381. mycode/server/static/assets/vitesse-light-CVO1_9PV.js +1 -0
  382. mycode/server/static/assets/vue-D2xRrEX4.js +1 -0
  383. mycode/server/static/assets/vue-html-AaS7Mt5G.js +1 -0
  384. mycode/server/static/assets/vue-vine-BoDAl6tE.js +1 -0
  385. mycode/server/static/assets/vyper-CDx5xZoG.js +1 -0
  386. mycode/server/static/assets/wasm-CG6Dc4jp.js +1 -0
  387. mycode/server/static/assets/wasm-MzD3tlZU.js +1 -0
  388. mycode/server/static/assets/wenyan-BV7otONQ.js +1 -0
  389. mycode/server/static/assets/wgsl-Dx-B1_4e.js +1 -0
  390. mycode/server/static/assets/wikitext-BhOHFoWU.js +1 -0
  391. mycode/server/static/assets/wit-5i3qLPDT.js +1 -0
  392. mycode/server/static/assets/wolfram-lXgVvXCa.js +1 -0
  393. mycode/server/static/assets/xml-sdJ4AIDG.js +1 -0
  394. mycode/server/static/assets/xsl-CtQFsRM5.js +1 -0
  395. mycode/server/static/assets/yaml-Buea-lGh.js +1 -0
  396. mycode/server/static/assets/zenscript-DVFEvuxE.js +1 -0
  397. mycode/server/static/assets/zig-VOosw3JB.js +1 -0
  398. mycode/server/static/favicon_slashes.svg +12 -0
  399. mycode/server/static/index.html +35 -0
  400. mycode_cli-0.1.0.dist-info/METADATA +186 -0
  401. mycode_cli-0.1.0.dist-info/RECORD +404 -0
  402. mycode_cli-0.1.0.dist-info/WHEEL +4 -0
  403. mycode_cli-0.1.0.dist-info/entry_points.txt +2 -0
  404. mycode_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
mycode/core/agent.py ADDED
@@ -0,0 +1,515 @@
1
+ """Agent loop with provider-specific adapters and one internal message model."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ from collections.abc import AsyncIterator, Awaitable, Callable
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from mycode.core.config import Settings, get_settings
13
+ from mycode.core.messages import (
14
+ ConversationMessage,
15
+ build_message,
16
+ flatten_message_text,
17
+ tool_result_block,
18
+ user_text_message,
19
+ )
20
+ from mycode.core.providers import get_provider_adapter
21
+ from mycode.core.providers.base import ProviderAdapter, ProviderRequest, ProviderStreamEvent
22
+ from mycode.core.session import (
23
+ COMPACT_SUMMARY_PROMPT,
24
+ DEFAULT_COMPACT_THRESHOLD,
25
+ apply_compact,
26
+ build_compact_event,
27
+ should_compact,
28
+ )
29
+ from mycode.core.system_prompt import build_system_prompt
30
+ from mycode.core.tools import ToolExecutionResult, ToolExecutor
31
+
32
+ logger = logging.getLogger(__name__)
33
+
34
+ PersistCallback = Callable[[dict[str, Any]], Awaitable[None]]
35
+
36
+
37
+ @dataclass
38
+ class Event:
39
+ """Streaming event emitted by the agent."""
40
+
41
+ type: str
42
+ data: dict[str, Any] = field(default_factory=dict)
43
+
44
+
45
+ class Agent:
46
+ """Minimal coding agent with one internal loop and provider adapters."""
47
+
48
+ def __init__(
49
+ self,
50
+ *,
51
+ model: str,
52
+ cwd: str,
53
+ session_dir: Path,
54
+ session_id: str | None = None,
55
+ provider: str | None = None,
56
+ api_key: str | None = None,
57
+ api_base: str | None = None,
58
+ messages: list[dict[str, Any]] | None = None,
59
+ max_turns: int | None = None,
60
+ max_tokens: int = 16_384,
61
+ context_window: int | None = 128_000,
62
+ compact_threshold: float | None = None,
63
+ reasoning_effort: str | None = None,
64
+ supports_image_input: bool | None = None,
65
+ settings: Settings | None = None,
66
+ system: str | None = None,
67
+ tool_executor: ToolExecutor | None = None,
68
+ ):
69
+ self.model = model
70
+ self.provider = provider or "anthropic"
71
+ self.cwd = str(Path(cwd).resolve(strict=False))
72
+ self.session_dir = session_dir
73
+ self.session_id = (session_id or session_dir.name).strip() or None
74
+ self.api_key = api_key
75
+ self.api_base = api_base
76
+ self.max_turns = max_turns
77
+ self.max_tokens = max_tokens
78
+ self.context_window = context_window
79
+ self.compact_threshold = compact_threshold if compact_threshold is not None else DEFAULT_COMPACT_THRESHOLD
80
+ self.reasoning_effort = reasoning_effort
81
+ self.supports_image_input: bool = bool(supports_image_input)
82
+ self.settings = settings or get_settings(self.cwd)
83
+ self.system = system or build_system_prompt(self.cwd, self.settings)
84
+ self._cancel_event = asyncio.Event()
85
+ self._provider_event_task: asyncio.Future[ProviderStreamEvent] | None = None
86
+
87
+ self.messages: list[ConversationMessage] = list(messages or [])
88
+ self.tools = tool_executor or ToolExecutor(cwd=self.cwd, session_dir=self.session_dir)
89
+ self.tools.supports_image_input = self.supports_image_input
90
+
91
+ def cancel(self) -> None:
92
+ self._cancel_event.set()
93
+ self.tools.cancel_active()
94
+ if self._provider_event_task and not self._provider_event_task.done():
95
+ self._provider_event_task.cancel()
96
+
97
+ def clear(self) -> None:
98
+ self.messages = []
99
+
100
+ @staticmethod
101
+ def _tool_done_event(tool_id: str, result: ToolExecutionResult) -> Event:
102
+ """Build the standard tool_done event payload."""
103
+
104
+ data = {
105
+ "tool_use_id": tool_id,
106
+ "model_text": result.model_text,
107
+ "display_text": result.display_text,
108
+ "is_error": result.is_error,
109
+ }
110
+ if result.content:
111
+ data["content"] = result.content
112
+ return Event(
113
+ "tool_done",
114
+ data,
115
+ )
116
+
117
+ async def _run_streaming_tool(self, *, tool_id: str, name: str, args: dict[str, Any]) -> AsyncIterator[Event]:
118
+ """Run one streaming tool and forward live output until it finishes."""
119
+
120
+ loop = asyncio.get_running_loop()
121
+ output_queue: asyncio.Queue[str | None] = asyncio.Queue()
122
+
123
+ def on_output(line: str) -> None:
124
+ loop.call_soon_threadsafe(output_queue.put_nowait, line)
125
+
126
+ async def run_in_thread() -> ToolExecutionResult:
127
+ try:
128
+ return await asyncio.to_thread(
129
+ self.tools.run_streaming,
130
+ name,
131
+ tool_call_id=tool_id,
132
+ args=args,
133
+ on_output=on_output,
134
+ )
135
+ finally:
136
+ loop.call_soon_threadsafe(output_queue.put_nowait, None)
137
+
138
+ task = asyncio.create_task(run_in_thread())
139
+ was_cancelled = False
140
+
141
+ # Streaming tools produce intermediate output, but they still end as one
142
+ # normal tool_done event so the outer loop can handle all tools the same way.
143
+ while True:
144
+ if self._cancel_event.is_set() and not was_cancelled:
145
+ was_cancelled = True
146
+ self.tools.cancel_active()
147
+
148
+ try:
149
+ output = await asyncio.wait_for(output_queue.get(), timeout=0.1)
150
+ except TimeoutError:
151
+ if task.done():
152
+ break
153
+ continue
154
+
155
+ if output is None:
156
+ break
157
+ if not was_cancelled:
158
+ yield Event("tool_output", {"tool_use_id": tool_id, "output": output})
159
+
160
+ if was_cancelled:
161
+ try:
162
+ await task
163
+ except Exception:
164
+ pass
165
+ result = ToolExecutionResult(
166
+ model_text="error: cancelled",
167
+ display_text="Cancelled",
168
+ is_error=True,
169
+ )
170
+ else:
171
+ try:
172
+ result = await task
173
+ except Exception as exc: # pragma: no cover - defensive
174
+ result = ToolExecutionResult(
175
+ model_text=f"error: {exc}",
176
+ display_text=str(exc),
177
+ is_error=True,
178
+ )
179
+
180
+ yield self._tool_done_event(tool_id, result)
181
+
182
+ async def _run_tool_call(self, tool_use: dict[str, Any]) -> AsyncIterator[Event]:
183
+ """Run one tool call and emit the standard tool events."""
184
+
185
+ tool_id = str(tool_use.get("id") or "")
186
+ name = str(tool_use.get("name") or "")
187
+ raw_args = tool_use.get("input")
188
+ args = raw_args if isinstance(raw_args, dict) else {}
189
+
190
+ yield Event("tool_start", {"tool_call": {"id": tool_id, "name": name, "input": args}})
191
+
192
+ if self._cancel_event.is_set():
193
+ yield self._tool_done_event(
194
+ tool_id,
195
+ ToolExecutionResult(
196
+ model_text="error: cancelled",
197
+ display_text="Cancelled",
198
+ is_error=True,
199
+ ),
200
+ )
201
+ return
202
+
203
+ tool = self.tools.get_tool(name)
204
+ if tool is None:
205
+ yield self._tool_done_event(
206
+ tool_id,
207
+ ToolExecutionResult(
208
+ model_text=f"error: unknown tool: {name}",
209
+ display_text=f"Unknown tool: {name}",
210
+ is_error=True,
211
+ ),
212
+ )
213
+ return
214
+
215
+ if tool.streams_output:
216
+ async for event in self._run_streaming_tool(tool_id=tool_id, name=name, args=args):
217
+ yield event
218
+ return
219
+
220
+ try:
221
+ result = self.tools.run(name, args=args)
222
+ except Exception as exc: # pragma: no cover - defensive
223
+ result = ToolExecutionResult(
224
+ model_text=f"error: {exc}",
225
+ display_text=str(exc),
226
+ is_error=True,
227
+ )
228
+
229
+ yield self._tool_done_event(tool_id, result)
230
+
231
+ async def _stream_provider_turn(
232
+ self,
233
+ adapter: ProviderAdapter,
234
+ request: ProviderRequest,
235
+ ) -> AsyncIterator[ProviderStreamEvent]:
236
+ """Iterate one provider turn with best-effort cancellation support."""
237
+
238
+ provider_stream: AsyncIterator[ProviderStreamEvent] = adapter.stream_turn(request)
239
+ try:
240
+ while True:
241
+ if self._cancel_event.is_set():
242
+ raise asyncio.CancelledError
243
+
244
+ self._provider_event_task = asyncio.ensure_future(anext(provider_stream))
245
+ try:
246
+ yield await self._provider_event_task
247
+ except StopAsyncIteration:
248
+ return
249
+ finally:
250
+ self._provider_event_task = None
251
+ finally:
252
+ close = getattr(provider_stream, "aclose", None)
253
+ if callable(close):
254
+ try:
255
+ await close()
256
+ except Exception:
257
+ pass
258
+
259
+ async def achat(
260
+ self,
261
+ user_input: str | ConversationMessage,
262
+ *,
263
+ on_persist: PersistCallback | None = None,
264
+ ) -> AsyncIterator[Event]:
265
+ """Run the full agent loop for one user message.
266
+
267
+ Each turn asks the provider for one assistant message. If the assistant
268
+ requests tools, the agent runs them locally, appends one user-side
269
+ tool_result message, and continues until the assistant stops using tools.
270
+ """
271
+
272
+ self._cancel_event.clear()
273
+ supports_image_input = self.supports_image_input
274
+ self.tools.supports_image_input = supports_image_input
275
+
276
+ if isinstance(user_input, str):
277
+ user_message = user_text_message(user_input)
278
+ else:
279
+ user_message = {
280
+ "role": str(user_input.get("role") or "user"),
281
+ "content": [dict(b) for b in user_input.get("content") or [] if isinstance(b, dict)],
282
+ }
283
+ if isinstance(user_input.get("meta"), dict):
284
+ user_message["meta"] = dict(user_input["meta"])
285
+
286
+ if user_message.get("role") != "user":
287
+ yield Event("error", {"message": "user input must be a user message"})
288
+ return
289
+
290
+ if not supports_image_input and any(
291
+ isinstance(block, dict) and block.get("type") == "image" for block in user_message.get("content") or []
292
+ ):
293
+ yield Event("error", {"message": "current model does not support image input"})
294
+ return
295
+
296
+ self.messages.append(user_message)
297
+ if on_persist:
298
+ await on_persist(user_message)
299
+
300
+ adapter = get_provider_adapter(self.provider)
301
+
302
+ turn_number = 0
303
+ while self.max_turns is None or turn_number < self.max_turns:
304
+ turn_number += 1
305
+ if self._cancel_event.is_set():
306
+ yield Event("error", {"message": "cancelled"})
307
+ return
308
+
309
+ assistant_message: ConversationMessage | None = None
310
+ request = ProviderRequest(
311
+ provider=self.provider,
312
+ model=self.model,
313
+ session_id=self.session_id,
314
+ messages=self.messages,
315
+ system=self.system,
316
+ tools=self.tools.definitions,
317
+ max_tokens=self.max_tokens,
318
+ api_key=self.api_key,
319
+ api_base=self.api_base,
320
+ reasoning_effort=self.reasoning_effort,
321
+ supports_image_input=supports_image_input,
322
+ )
323
+
324
+ try:
325
+ # Phase 1: ask the provider for exactly one assistant turn.
326
+ async for provider_event in self._stream_provider_turn(adapter, request):
327
+ if self._cancel_event.is_set():
328
+ yield Event("error", {"message": "cancelled"})
329
+ return
330
+
331
+ if provider_event.type == "thinking_delta":
332
+ delta_text = str(provider_event.data.get("text") or "")
333
+ if delta_text:
334
+ yield Event("reasoning", {"delta": delta_text})
335
+ continue
336
+
337
+ if provider_event.type == "text_delta":
338
+ delta_text = str(provider_event.data.get("text") or "")
339
+ if delta_text:
340
+ yield Event("text", {"delta": delta_text})
341
+ continue
342
+
343
+ if provider_event.type == "provider_error":
344
+ raise ValueError(str(provider_event.data.get("message") or "provider error"))
345
+
346
+ if provider_event.type != "message_done":
347
+ continue
348
+
349
+ message = provider_event.data.get("message")
350
+ if isinstance(message, dict):
351
+ assistant_message = message
352
+
353
+ except asyncio.CancelledError:
354
+ yield Event("error", {"message": "cancelled"})
355
+ return
356
+ except Exception as exc:
357
+ logger.exception("Provider request failed")
358
+ yield Event("error", {"message": str(exc)})
359
+ return
360
+
361
+ if not assistant_message:
362
+ yield Event("error", {"message": "provider produced no assistant message"})
363
+ return
364
+
365
+ self.messages.append(assistant_message)
366
+ if on_persist:
367
+ await on_persist(assistant_message)
368
+
369
+ # Phase 2: if the assistant requested tools, execute them locally and
370
+ # append one user-side tool_result message before continuing.
371
+ tool_calls = [
372
+ block
373
+ for block in assistant_message.get("content") or []
374
+ if isinstance(block, dict) and block.get("type") == "tool_use"
375
+ ]
376
+ if not tool_calls:
377
+ break
378
+
379
+ tool_results: list[dict[str, Any]] = []
380
+ for tool_call in tool_calls:
381
+ async for event in self._run_tool_call(tool_call):
382
+ yield event
383
+
384
+ if event.type != "tool_done":
385
+ continue
386
+
387
+ result_data = event.data
388
+ result_tool_id = str(result_data.get("tool_use_id") or "")
389
+ result_model_text = str(result_data.get("model_text") or "")
390
+ result_display_text = str(result_data.get("display_text") or "")
391
+ result_is_error = bool(result_data.get("is_error"))
392
+ result_content = result_data.get("content")
393
+ tool_results.append(
394
+ tool_result_block(
395
+ tool_use_id=result_tool_id,
396
+ model_text=result_model_text,
397
+ display_text=result_display_text,
398
+ is_error=result_is_error,
399
+ content=result_content if isinstance(result_content, list) else None,
400
+ )
401
+ )
402
+
403
+ tool_was_cancelled = result_model_text == "error: cancelled"
404
+ if tool_was_cancelled and self._cancel_event.is_set():
405
+ tool_result_message = build_message("user", tool_results)
406
+ self.messages.append(tool_result_message)
407
+ if on_persist:
408
+ await on_persist(tool_result_message)
409
+ return
410
+
411
+ tool_result_message = build_message("user", tool_results)
412
+ self.messages.append(tool_result_message)
413
+ if on_persist:
414
+ await on_persist(tool_result_message)
415
+
416
+ else:
417
+ # while loop exhausted max_turns without breaking
418
+ yield Event("error", {"message": "max_turns reached"})
419
+ return
420
+
421
+ # Turn completed normally (assistant stopped calling tools).
422
+ # Check whether context compaction is needed.
423
+ if not self._cancel_event.is_set():
424
+ async for event in self._compact_if_needed(adapter, on_persist):
425
+ yield event
426
+
427
+ # -----------------------------------------------------------------
428
+ # Context compaction
429
+ # -----------------------------------------------------------------
430
+
431
+ async def _compact_if_needed(
432
+ self,
433
+ adapter: ProviderAdapter,
434
+ on_persist: PersistCallback | None,
435
+ ) -> AsyncIterator[Event]:
436
+ """Check token usage and run compaction if above threshold."""
437
+
438
+ usage: dict[str, Any] | None = None
439
+ for message in reversed(self.messages):
440
+ if message.get("role") == "assistant":
441
+ usage = (message.get("meta") or {}).get("usage")
442
+ break
443
+
444
+ if not should_compact(usage, self.context_window, self.compact_threshold):
445
+ return
446
+
447
+ try:
448
+ async for event in self._compact(adapter, on_persist):
449
+ yield event
450
+ except (Exception, asyncio.CancelledError):
451
+ logger.warning("Context compaction failed, continuing without compaction", exc_info=True)
452
+
453
+ async def _compact(
454
+ self,
455
+ adapter: ProviderAdapter,
456
+ on_persist: PersistCallback | None,
457
+ ) -> AsyncIterator[Event]:
458
+ """Generate a conversation summary and replace in-memory messages."""
459
+ compacted_count = len(self.messages)
460
+
461
+ # Ask the same provider for a summary — no tools, just text generation.
462
+ compact_messages = list(self.messages) + [user_text_message(COMPACT_SUMMARY_PROMPT)]
463
+ request = ProviderRequest(
464
+ provider=self.provider,
465
+ model=self.model,
466
+ session_id=self.session_id,
467
+ messages=compact_messages,
468
+ system=self.system,
469
+ tools=[],
470
+ max_tokens=min(self.max_tokens, 8192),
471
+ api_key=self.api_key,
472
+ api_base=self.api_base,
473
+ supports_image_input=self.supports_image_input,
474
+ )
475
+
476
+ summary_message: ConversationMessage | None = None
477
+ async for provider_event in self._stream_provider_turn(adapter, request):
478
+ if provider_event.type == "message_done":
479
+ msg = provider_event.data.get("message")
480
+ if isinstance(msg, dict):
481
+ summary_message = msg
482
+
483
+ if not summary_message:
484
+ logger.warning("Compaction produced no response")
485
+ return
486
+
487
+ summary_text = flatten_message_text(summary_message, include_thinking=False)
488
+ if not summary_text:
489
+ logger.warning("Compaction produced empty summary")
490
+ return
491
+
492
+ summary_usage = (summary_message.get("meta") or {}).get("usage")
493
+ compact_event = build_compact_event(
494
+ summary_text,
495
+ provider=self.provider,
496
+ model=self.model,
497
+ compacted_count=compacted_count,
498
+ usage=summary_usage,
499
+ )
500
+
501
+ # Persist the compact event (append-only — original messages stay in JSONL).
502
+ if on_persist:
503
+ await on_persist(compact_event)
504
+
505
+ # Rebuild in-memory messages from the compact event.
506
+ self.messages.append(compact_event)
507
+ self.messages = apply_compact(self.messages)
508
+
509
+ yield Event(
510
+ "compact",
511
+ {
512
+ "message": f"Context compacted ({compacted_count} messages → summary)",
513
+ "compacted_count": compacted_count,
514
+ },
515
+ )