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
@@ -0,0 +1,351 @@
1
+ """Shared provider adapter interfaces.
2
+
3
+ The agent loop talks to providers through a small normalized contract:
4
+
5
+ - input: `ProviderRequest`
6
+ - output: streamed `ProviderStreamEvent` objects
7
+
8
+ Concrete adapters are free to use the official SDK or protocol that best matches
9
+ their upstream provider. Each adapter is also responsible for projecting the
10
+ canonical session transcript into a provider-safe replay history before a new
11
+ request is sent upstream.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from abc import ABC, abstractmethod
18
+ from collections.abc import AsyncIterator
19
+ from dataclasses import dataclass, field
20
+ from typing import Any
21
+
22
+ from mycode.core.messages import ConversationMessage, build_message, text_block, tool_result_block
23
+
24
+ DEFAULT_REQUEST_TIMEOUT = 300.0
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ProviderRequest:
29
+ provider: str
30
+ model: str
31
+ session_id: str | None
32
+ messages: list[ConversationMessage]
33
+ system: str
34
+ tools: list[dict[str, Any]]
35
+ max_tokens: int
36
+ api_key: str | None
37
+ api_base: str | None
38
+ reasoning_effort: str | None = None
39
+ supports_image_input: bool = True
40
+
41
+
42
+ @dataclass
43
+ class ProviderStreamEvent:
44
+ type: str
45
+ data: dict[str, Any] = field(default_factory=dict)
46
+
47
+
48
+ def dump_model(value: Any) -> Any:
49
+ """Convert SDK model objects into plain Python data."""
50
+
51
+ if value is None:
52
+ return None
53
+ if hasattr(value, "model_dump"):
54
+ return value.model_dump()
55
+ if isinstance(value, list):
56
+ return [dump_model(item) for item in value]
57
+ return value
58
+
59
+
60
+ def get_native_meta(block: dict[str, Any]) -> dict[str, Any]:
61
+ """Return block.meta.native as a dict, or {} if absent."""
62
+
63
+ raw_meta = block.get("meta")
64
+ if isinstance(raw_meta, dict):
65
+ candidate = raw_meta.get("native")
66
+ if isinstance(candidate, dict):
67
+ return candidate
68
+ return {}
69
+
70
+
71
+ class ProviderAdapter(ABC):
72
+ """Base class for provider adapters.
73
+
74
+ New adapters usually only need to implement `stream_turn()` and optionally
75
+ override tool-call ID projection.
76
+ """
77
+
78
+ provider_id: str
79
+ label: str
80
+ default_base_url: str | None = None
81
+ env_api_key_names: tuple[str, ...] = ()
82
+ # Used only as lightweight defaults during config resolution.
83
+ default_models: tuple[str, ...] = ()
84
+ # Auto-discovery is intentionally limited to first-party built-ins that can
85
+ # run from environment variables alone.
86
+ auto_discoverable: bool = True
87
+ # Whether this adapter accepts the shared `reasoning_effort` knob. Providers
88
+ # that do not support it keep their upstream default behavior unchanged.
89
+ supports_reasoning_effort: bool = False
90
+
91
+ @abstractmethod
92
+ def stream_turn(self, request: ProviderRequest) -> AsyncIterator[ProviderStreamEvent]:
93
+ """Stream exactly one assistant turn."""
94
+
95
+ def prepare_messages(self, request: ProviderRequest) -> list[ConversationMessage]:
96
+ """Repair canonical history, then project tool IDs for provider replay."""
97
+
98
+ supports_image_input = getattr(request, "supports_image_input", True)
99
+ repaired_messages = repair_messages_for_replay(request.messages, supports_image_input=supports_image_input)
100
+ prepared_messages: list[ConversationMessage] = []
101
+ tool_id_map: dict[str, str] = {}
102
+ used_tool_call_ids: set[str] = set()
103
+
104
+ for message in repaired_messages:
105
+ projected_blocks: list[dict[str, Any]] = []
106
+ for raw_block in message.get("content") or []:
107
+ if not isinstance(raw_block, dict):
108
+ continue
109
+
110
+ block = dict(raw_block)
111
+ if block.get("type") == "tool_use":
112
+ tool_use_id = str(block.get("id") or "")
113
+ if tool_use_id and tool_use_id not in tool_id_map:
114
+ tool_id_map[tool_use_id] = self.project_tool_call_id(tool_use_id, used_tool_call_ids)
115
+ used_tool_call_ids.add(tool_id_map[tool_use_id])
116
+ if tool_use_id:
117
+ block["id"] = tool_id_map[tool_use_id]
118
+ elif block.get("type") == "tool_result":
119
+ tool_use_id = str(block.get("tool_use_id") or "")
120
+ if tool_use_id in tool_id_map:
121
+ block["tool_use_id"] = tool_id_map[tool_use_id]
122
+
123
+ projected_blocks.append(block)
124
+
125
+ projected_message = dict(message)
126
+ projected_message["content"] = projected_blocks
127
+ prepared_messages.append(projected_message)
128
+
129
+ return prepared_messages
130
+
131
+ def project_tool_call_id(self, tool_call_id: str, used_tool_call_ids: set[str]) -> str:
132
+ """Project one canonical tool call ID into a provider-safe ID.
133
+
134
+ Most providers accept canonical tool IDs as-is. Adapters can override
135
+ this when the upstream protocol restricts character sets or length, as
136
+ long as the returned ID stays unique within the projected request.
137
+ """
138
+
139
+ return tool_call_id
140
+
141
+ def api_key_from_env(self) -> str | None:
142
+ for env_name in self.env_api_key_names:
143
+ value = os.environ.get(env_name)
144
+ if value:
145
+ return value
146
+ return None
147
+
148
+ def require_api_key(self, api_key: str | None) -> str:
149
+ resolved = (api_key or "").strip() or self.api_key_from_env() or ""
150
+ if resolved:
151
+ return resolved
152
+
153
+ checked = ", ".join(self.env_api_key_names) or "<api key env>"
154
+ raise ValueError(f"missing API key for provider {self.provider_id}; checked: {checked}")
155
+
156
+ def resolve_base_url(self, api_base: str | None) -> str | None:
157
+ base = (api_base or self.default_base_url or "").strip()
158
+ return base.rstrip("/") or None
159
+
160
+
161
+ def repair_messages_for_replay(
162
+ source_messages: list[ConversationMessage],
163
+ *,
164
+ supports_image_input: bool,
165
+ ) -> list[ConversationMessage]:
166
+ """Return a minimal replay-safe transcript from canonical session history.
167
+
168
+ This keeps only replayable blocks, removes duplicate or orphaned tool
169
+ records, and inserts synthetic error tool results when a tool call was left
170
+ open by an interrupted turn.
171
+ """
172
+
173
+ replay_messages: list[ConversationMessage] = []
174
+ emitted_tool_use_ids: set[str] = set()
175
+ emitted_tool_result_ids: set[str] = set()
176
+ open_tool_use_ids: list[str] = []
177
+
178
+ for message in source_messages:
179
+ role = str(message.get("role") or "")
180
+
181
+ if role == "assistant":
182
+ if open_tool_use_ids:
183
+ replay_messages.append(_interrupted_tool_result_message(open_tool_use_ids))
184
+ emitted_tool_result_ids.update(open_tool_use_ids)
185
+ open_tool_use_ids = []
186
+
187
+ raw_meta = message.get("meta")
188
+ stop_reason = str((raw_meta or {}).get("stop_reason") or "") if isinstance(raw_meta, dict) else ""
189
+ if stop_reason in {"error", "aborted", "cancelled"}:
190
+ continue
191
+
192
+ content: list[dict[str, Any]] = []
193
+ current_tool_use_ids: list[str] = []
194
+ for raw_block in message.get("content") or []:
195
+ if not isinstance(raw_block, dict):
196
+ continue
197
+ block_type = raw_block.get("type")
198
+ if block_type in {"text", "thinking"}:
199
+ text = str(raw_block.get("text") or "")
200
+ if text:
201
+ content.append(dict(raw_block))
202
+ continue
203
+
204
+ if block_type != "tool_use":
205
+ continue
206
+
207
+ tool_use_id = str(raw_block.get("id") or "")
208
+ if not tool_use_id or tool_use_id in emitted_tool_use_ids:
209
+ continue
210
+
211
+ emitted_tool_use_ids.add(tool_use_id)
212
+ current_tool_use_ids.append(tool_use_id)
213
+ content.append(dict(raw_block))
214
+
215
+ if not content:
216
+ continue
217
+
218
+ replay_message = dict(message)
219
+ replay_message["content"] = content
220
+ if isinstance(raw_meta, dict):
221
+ replay_message["meta"] = dict(raw_meta)
222
+ replay_messages.append(replay_message)
223
+ open_tool_use_ids = current_tool_use_ids
224
+ continue
225
+
226
+ if role != "user":
227
+ continue
228
+
229
+ content = []
230
+ resolved_tool_use_ids: set[str] = set()
231
+ has_user_input = False
232
+
233
+ for raw_block in message.get("content") or []:
234
+ if not isinstance(raw_block, dict):
235
+ continue
236
+
237
+ block_type = raw_block.get("type")
238
+ if block_type == "text":
239
+ text = str(raw_block.get("text") or "")
240
+ if text:
241
+ has_user_input = True
242
+ content.append(dict(raw_block))
243
+ continue
244
+
245
+ if block_type == "image":
246
+ if supports_image_input:
247
+ has_user_input = True
248
+ content.append(dict(raw_block))
249
+ continue
250
+
251
+ if block_type != "tool_result":
252
+ continue
253
+
254
+ tool_use_id = str(raw_block.get("tool_use_id") or "")
255
+ if not tool_use_id or tool_use_id not in emitted_tool_use_ids or tool_use_id in emitted_tool_result_ids:
256
+ continue
257
+
258
+ block = dict(raw_block)
259
+ raw_content = block.get("content")
260
+ if not supports_image_input and isinstance(raw_content, list):
261
+ filtered_content = [
262
+ dict(item) for item in raw_content if isinstance(item, dict) and item.get("type") != "image"
263
+ ]
264
+ if filtered_content:
265
+ block["content"] = filtered_content
266
+ else:
267
+ block.pop("content", None)
268
+
269
+ content.append(block)
270
+ resolved_tool_use_ids.add(tool_use_id)
271
+ emitted_tool_result_ids.add(tool_use_id)
272
+
273
+ if has_user_input and open_tool_use_ids:
274
+ missing_tool_use_ids = [
275
+ tool_use_id for tool_use_id in open_tool_use_ids if tool_use_id not in resolved_tool_use_ids
276
+ ]
277
+ if missing_tool_use_ids:
278
+ replay_messages.append(_interrupted_tool_result_message(missing_tool_use_ids))
279
+ emitted_tool_result_ids.update(missing_tool_use_ids)
280
+ open_tool_use_ids = []
281
+
282
+ elif open_tool_use_ids:
283
+ open_tool_use_ids = [
284
+ tool_use_id for tool_use_id in open_tool_use_ids if tool_use_id not in resolved_tool_use_ids
285
+ ]
286
+
287
+ if not content:
288
+ if replay_messages and replay_messages[-1].get("role") == "assistant":
289
+ # Keep a valid replay transcript when a corrupted user turn is
290
+ # reduced to nothing after cleanup.
291
+ replay_messages.append(
292
+ build_message(
293
+ "user",
294
+ [text_block("[User turn omitted during replay]")],
295
+ meta={"synthetic": True},
296
+ )
297
+ )
298
+ continue
299
+
300
+ replay_message = dict(message)
301
+ replay_message["content"] = content
302
+ if isinstance(message.get("meta"), dict):
303
+ replay_message["meta"] = dict(message["meta"])
304
+ replay_messages.append(replay_message)
305
+
306
+ if open_tool_use_ids:
307
+ replay_messages.append(_interrupted_tool_result_message(open_tool_use_ids))
308
+
309
+ return replay_messages
310
+
311
+
312
+ def _interrupted_tool_result_message(tool_use_ids: list[str]) -> ConversationMessage:
313
+ """Return one synthetic user message that closes interrupted tool calls."""
314
+
315
+ return build_message(
316
+ "user",
317
+ [
318
+ tool_result_block(
319
+ tool_use_id=tool_use_id,
320
+ model_text="error: tool call was interrupted",
321
+ display_text="Tool call was interrupted",
322
+ is_error=True,
323
+ )
324
+ for tool_use_id in tool_use_ids
325
+ ],
326
+ )
327
+
328
+
329
+ def load_image_block_payload(block: dict[str, Any]) -> tuple[str, str]:
330
+ """Return (mime_type, base64_data) for one canonical image block."""
331
+
332
+ mime_type = block.get("mime_type")
333
+ if not isinstance(mime_type, str) or not mime_type:
334
+ raise ValueError("image block is missing mime_type")
335
+
336
+ data = block.get("data")
337
+ if not isinstance(data, str) or not data:
338
+ raise ValueError("image block is missing data")
339
+
340
+ return mime_type, data
341
+
342
+
343
+ def tool_result_content_blocks(block: dict[str, Any]) -> list[dict[str, Any]]:
344
+ """Return structured tool-result content, falling back to one text block."""
345
+
346
+ raw_content = block.get("content")
347
+ if isinstance(raw_content, list):
348
+ structured = [dict(item) for item in raw_content if isinstance(item, dict)]
349
+ if structured:
350
+ return structured
351
+ return [text_block(str(block.get("model_text") or ""))]
@@ -0,0 +1,321 @@
1
+ """Google Gemini adapter built on the official google-genai Python SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import AsyncIterator
6
+ from typing import Any
7
+ from urllib.parse import urlparse
8
+
9
+ from google import genai
10
+ from google.genai import types
11
+ from google.genai.errors import APIError
12
+
13
+ from mycode.core.messages import assistant_message, text_block, thinking_block, tool_use_block
14
+ from mycode.core.providers.base import (
15
+ DEFAULT_REQUEST_TIMEOUT,
16
+ ProviderAdapter,
17
+ ProviderRequest,
18
+ ProviderStreamEvent,
19
+ get_native_meta,
20
+ load_image_block_payload,
21
+ )
22
+
23
+ _DUMMY_THOUGHT_SIGNATURE = "skip_thought_signature_validator"
24
+
25
+
26
+ def _to_json(value: Any) -> Any:
27
+ """Convert SDK objects into JSON-safe plain data."""
28
+
29
+ if value is None:
30
+ return None
31
+ if hasattr(value, "model_dump"):
32
+ try:
33
+ return value.model_dump(mode="json", exclude_none=True)
34
+ except TypeError:
35
+ return _to_json(value.model_dump())
36
+ if isinstance(value, list):
37
+ return [_to_json(item) for item in value]
38
+ if isinstance(value, dict):
39
+ return {key: normalized for key, item in value.items() if (normalized := _to_json(item)) is not None}
40
+ return value
41
+
42
+
43
+ class GoogleGeminiAdapter(ProviderAdapter):
44
+ """Adapter for the Gemini Developer API."""
45
+
46
+ provider_id = "google"
47
+ label = "Google Gemini"
48
+ default_base_url = "https://generativelanguage.googleapis.com"
49
+ env_api_key_names = ("GEMINI_API_KEY", "GOOGLE_API_KEY")
50
+ default_models = ("gemini-3.1-pro-preview", "gemini-3-flash-preview")
51
+ supports_reasoning_effort = True
52
+
53
+ async def stream_turn(self, request: ProviderRequest) -> AsyncIterator[ProviderStreamEvent]:
54
+ api_key = self.require_api_key(request.api_key)
55
+ client = genai.Client(api_key=api_key, http_options=self._http_options(request.api_base))
56
+
57
+ blocks: list[dict[str, Any]] = []
58
+ response_id: str | None = None
59
+ response_model: str | None = None
60
+ finish_reason: str | None = None
61
+ finish_message: str | None = None
62
+ usage: dict[str, Any] | None = None
63
+
64
+ try:
65
+ stream = await client.aio.models.generate_content_stream(
66
+ model=request.model,
67
+ contents=self._build_contents(request),
68
+ config=self._build_config(request),
69
+ )
70
+ async for chunk in stream:
71
+ response_id = response_id or getattr(chunk, "response_id", None)
72
+ response_model = response_model or getattr(chunk, "model_version", None)
73
+ usage = _to_json(getattr(chunk, "usage_metadata", None)) or usage
74
+
75
+ candidates = getattr(chunk, "candidates", None) or []
76
+ if not candidates:
77
+ continue
78
+ candidate = candidates[0]
79
+
80
+ finish_reason = _to_json(getattr(candidate, "finish_reason", None)) or finish_reason
81
+ finish_message = getattr(candidate, "finish_message", None) or finish_message
82
+
83
+ for part in getattr(getattr(candidate, "content", None), "parts", None) or []:
84
+ for event in self._consume_part(blocks, part):
85
+ yield event
86
+ except APIError as exc:
87
+ raise ValueError(str(exc)) from exc
88
+ finally:
89
+ try:
90
+ await client.aio.aclose()
91
+ except Exception:
92
+ pass
93
+
94
+ yield ProviderStreamEvent(
95
+ "message_done",
96
+ {
97
+ "message": assistant_message(
98
+ blocks,
99
+ provider=self.provider_id,
100
+ model=response_model or request.model,
101
+ provider_message_id=response_id,
102
+ stop_reason=str(finish_reason) if finish_reason else None,
103
+ usage=usage,
104
+ native_meta={"finish_message": str(finish_message)} if finish_message else None,
105
+ )
106
+ },
107
+ )
108
+
109
+ def _http_options(self, api_base: str | None) -> types.HttpOptions:
110
+ base_url = self.resolve_base_url(api_base)
111
+ api_version = "v1beta"
112
+ if base_url and urlparse(base_url).path.rstrip("/").lower().endswith(("/v1", "/v1beta")):
113
+ api_version = None
114
+ return types.HttpOptions(base_url=base_url, api_version=api_version, timeout=int(DEFAULT_REQUEST_TIMEOUT))
115
+
116
+ def _build_contents(self, request: ProviderRequest) -> list[dict[str, Any]]:
117
+ """Convert canonical replay messages into Gemini contents."""
118
+
119
+ contents: list[dict[str, Any]] = []
120
+ tool_names: dict[str, str] = {}
121
+
122
+ for message in self.prepare_messages(request):
123
+ role = str(message.get("role") or "")
124
+ blocks = [block for block in message.get("content") or [] if isinstance(block, dict)]
125
+
126
+ if role == "assistant":
127
+ parts: list[dict[str, Any]] = []
128
+ needs_dummy_signature = True
129
+
130
+ for block in blocks:
131
+ if block.get("type") == "tool_use":
132
+ tool_id = str(block.get("id") or "")
133
+ tool_name = str(block.get("name") or "")
134
+ if tool_id and tool_name:
135
+ tool_names[tool_id] = tool_name
136
+
137
+ native_part = get_native_meta(block).get("part")
138
+ if isinstance(native_part, dict):
139
+ parts.append(dict(native_part))
140
+ if native_part.get("function_call") and native_part.get("thought_signature"):
141
+ needs_dummy_signature = False
142
+ continue
143
+
144
+ block_type = block.get("type")
145
+ if block_type == "thinking":
146
+ parts.append({"text": str(block.get("text") or ""), "thought": True})
147
+ continue
148
+
149
+ if block_type == "text":
150
+ parts.append({"text": str(block.get("text") or "")})
151
+ continue
152
+
153
+ if block_type != "tool_use":
154
+ continue
155
+
156
+ part: dict[str, Any] = {
157
+ "function_call": {
158
+ "id": block.get("id") or "",
159
+ "name": block.get("name") or "",
160
+ "args": block.get("input") if isinstance(block.get("input"), dict) else {},
161
+ }
162
+ }
163
+ # Gemini 3 validates the first function call in each step of
164
+ # the current turn. Cross-provider replay has no real thought
165
+ # signature, so we attach the documented dummy signature once.
166
+ if needs_dummy_signature:
167
+ part["thought_signature"] = _DUMMY_THOUGHT_SIGNATURE
168
+ needs_dummy_signature = False
169
+ parts.append(part)
170
+
171
+ if parts:
172
+ contents.append({"role": "model", "parts": parts})
173
+ continue
174
+
175
+ if role != "user":
176
+ continue
177
+
178
+ parts: list[dict[str, Any]] = []
179
+ for block in blocks:
180
+ block_type = block.get("type")
181
+ if block_type == "text":
182
+ parts.append({"text": str(block.get("text") or "")})
183
+ continue
184
+
185
+ if block_type == "image":
186
+ mime_type, data = load_image_block_payload(block)
187
+ parts.append({"inline_data": {"mime_type": mime_type, "data": data}})
188
+ continue
189
+
190
+ if block_type != "tool_result":
191
+ continue
192
+
193
+ tool_id = str(block.get("tool_use_id") or "")
194
+ response: dict[str, Any] = {"result": str(block.get("model_text") or "")}
195
+ if block.get("is_error"):
196
+ response["is_error"] = True
197
+
198
+ # Gemini requires the exact id and name from the matching
199
+ # function_call in the previous model turn.
200
+ parts.append(
201
+ {
202
+ "function_response": {
203
+ "id": tool_id,
204
+ "name": tool_names.get(tool_id, ""),
205
+ "response": response,
206
+ }
207
+ }
208
+ )
209
+
210
+ if parts:
211
+ contents.append({"role": "user", "parts": parts})
212
+
213
+ return contents
214
+
215
+ def _build_config(self, request: ProviderRequest) -> types.GenerateContentConfig:
216
+ tools = None
217
+ tool_config = None
218
+ automatic_function_calling = None
219
+ if request.tools:
220
+ tools = [
221
+ types.Tool(
222
+ function_declarations=[
223
+ types.FunctionDeclaration(
224
+ name=str(tool.get("name") or ""),
225
+ description=str(tool.get("description") or ""),
226
+ parameters_json_schema=tool.get("input_schema") or {"type": "object", "properties": {}},
227
+ )
228
+ for tool in request.tools
229
+ ]
230
+ )
231
+ ]
232
+ automatic_function_calling = types.AutomaticFunctionCallingConfig(disable=True)
233
+ tool_config = types.ToolConfig(
234
+ function_calling_config=types.FunctionCallingConfig(stream_function_call_arguments=False)
235
+ )
236
+
237
+ thinking_config = types.ThinkingConfig(include_thoughts=True)
238
+ if request.reasoning_effort and request.model.lower().startswith("gemini-3"):
239
+ # Official OpenAI-compat mapping:
240
+ # Gemini 3.1 Pro: minimal -> low
241
+ # Gemini 3 Flash: minimal -> minimal
242
+ effort = request.reasoning_effort
243
+ if effort in {"none", "low"}:
244
+ thinking_config.thinking_level = (
245
+ types.ThinkingLevel.LOW
246
+ if request.model.lower().startswith("gemini-3.1-pro")
247
+ else types.ThinkingLevel.MINIMAL
248
+ )
249
+ elif effort == "medium":
250
+ thinking_config.thinking_level = types.ThinkingLevel.MEDIUM
251
+ else:
252
+ thinking_config.thinking_level = types.ThinkingLevel.HIGH
253
+
254
+ return types.GenerateContentConfig(
255
+ system_instruction=request.system or None,
256
+ max_output_tokens=request.max_tokens,
257
+ tools=tools,
258
+ tool_config=tool_config,
259
+ automatic_function_calling=automatic_function_calling,
260
+ thinking_config=thinking_config,
261
+ )
262
+
263
+ def _consume_part(self, blocks: list[dict[str, Any]], part: Any) -> list[ProviderStreamEvent]:
264
+ native_part = _to_json(part) or {}
265
+ if native_part.get("thought") is False:
266
+ native_part.pop("thought", None)
267
+
268
+ function_call = getattr(part, "function_call", None)
269
+ if function_call is not None:
270
+ tool_input = getattr(function_call, "args", None)
271
+ blocks.append(
272
+ tool_use_block(
273
+ tool_id=str(getattr(function_call, "id", None) or f"tool_call_{len(blocks)}"),
274
+ name=str(getattr(function_call, "name", None) or ""),
275
+ input=tool_input if isinstance(tool_input, dict) else {},
276
+ meta={"native": {"part": native_part}},
277
+ )
278
+ )
279
+ return []
280
+
281
+ text = getattr(part, "text", None)
282
+ if text is None or text == "":
283
+ if not native_part.get("thought_signature"):
284
+ return []
285
+
286
+ # Gemini may put the final thought signature into an empty-text part.
287
+ # Keep it as a separate empty block so replay preserves the original
288
+ # part boundary instead of merging the signature into another block.
289
+ blocks.append(
290
+ thinking_block("", meta={"native": {"part": native_part}})
291
+ if bool(getattr(part, "thought", False))
292
+ else text_block("", meta={"native": {"part": native_part}})
293
+ )
294
+ return []
295
+
296
+ is_thought = bool(getattr(part, "thought", False))
297
+ event = ProviderStreamEvent("thinking_delta" if is_thought else "text_delta", {"text": str(text)})
298
+ block_type = "thinking" if is_thought else "text"
299
+
300
+ # Gemini may stream one logical thought/text across many chunks.
301
+ # Merge only when the block kind matches and we are not combining
302
+ # distinct thought signatures.
303
+ if blocks and blocks[-1].get("type") == block_type:
304
+ last_part = get_native_meta(blocks[-1]).get("part")
305
+ if isinstance(last_part, dict):
306
+ last_signature = last_part.get("thought_signature")
307
+ current_signature = native_part.get("thought_signature")
308
+ if not (last_signature and current_signature and last_signature != current_signature):
309
+ blocks[-1]["text"] = f"{blocks[-1].get('text') or ''}{text}"
310
+ last_part["text"] = f"{last_part.get('text') or ''}{text}"
311
+ if current_signature and not last_signature:
312
+ last_part["thought_signature"] = current_signature
313
+ return [event]
314
+
315
+ block = (
316
+ thinking_block(str(text), meta={"native": {"part": native_part}})
317
+ if is_thought
318
+ else text_block(str(text), meta={"native": {"part": native_part}})
319
+ )
320
+ blocks.append(block)
321
+ return [event]