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.
- mycode/__init__.py +1 -0
- mycode/cli/__init__.py +1 -0
- mycode/cli/chat.py +650 -0
- mycode/cli/main.py +245 -0
- mycode/cli/render.py +693 -0
- mycode/cli/runtime.py +271 -0
- mycode/cli/theme.py +109 -0
- mycode/core/__init__.py +30 -0
- mycode/core/agent.py +515 -0
- mycode/core/config.py +551 -0
- mycode/core/messages.py +166 -0
- mycode/core/models.py +144 -0
- mycode/core/models_catalog.json +2090 -0
- mycode/core/providers/__init__.py +86 -0
- mycode/core/providers/anthropic_like.py +366 -0
- mycode/core/providers/base.py +351 -0
- mycode/core/providers/gemini.py +321 -0
- mycode/core/providers/openai_chat.py +356 -0
- mycode/core/providers/openai_responses.py +326 -0
- mycode/core/session.py +537 -0
- mycode/core/system_prompt.md +10 -0
- mycode/core/system_prompt.py +319 -0
- mycode/core/tools.py +898 -0
- mycode/server/__init__.py +1 -0
- mycode/server/app.py +52 -0
- mycode/server/deps.py +29 -0
- mycode/server/routers/__init__.py +7 -0
- mycode/server/routers/chat.py +320 -0
- mycode/server/routers/sessions.py +77 -0
- mycode/server/routers/workspaces.py +92 -0
- mycode/server/run_manager.py +195 -0
- mycode/server/schemas.py +66 -0
- mycode/server/static/assets/EditDiff-C1ql7kft.js +12 -0
- mycode/server/static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- mycode/server/static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- mycode/server/static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- mycode/server/static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- mycode/server/static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- mycode/server/static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- mycode/server/static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- mycode/server/static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- mycode/server/static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- mycode/server/static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- mycode/server/static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- mycode/server/static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- mycode/server/static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- mycode/server/static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- mycode/server/static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- mycode/server/static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- mycode/server/static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- mycode/server/static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- mycode/server/static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- mycode/server/static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- mycode/server/static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- mycode/server/static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- mycode/server/static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- mycode/server/static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- mycode/server/static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- mycode/server/static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- mycode/server/static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- mycode/server/static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- mycode/server/static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- mycode/server/static/assets/abap-BdImnpbu.js +1 -0
- mycode/server/static/assets/actionscript-3-CoDkCxhg.js +1 -0
- mycode/server/static/assets/ada-bCR0ucgS.js +1 -0
- mycode/server/static/assets/andromeeda-C4gqWexZ.js +1 -0
- mycode/server/static/assets/angular-html-DA-rfuFy.js +1 -0
- mycode/server/static/assets/angular-ts-BrjP3tb8.js +1 -0
- mycode/server/static/assets/apache-Pmp26Uib.js +1 -0
- mycode/server/static/assets/apex-D8_7TLub.js +1 -0
- mycode/server/static/assets/apl-CORt7UWP.js +1 -0
- mycode/server/static/assets/applescript-Co6uUVPk.js +1 -0
- mycode/server/static/assets/ara-BRHolxvo.js +1 -0
- mycode/server/static/assets/asciidoc-Ve4PFQV2.js +1 -0
- mycode/server/static/assets/asm-D_Q5rh1f.js +1 -0
- mycode/server/static/assets/astro-HNnZUWAn.js +1 -0
- mycode/server/static/assets/aurora-x-D-2ljcwZ.js +1 -0
- mycode/server/static/assets/auto-render-xntwXHOX.js +261 -0
- mycode/server/static/assets/awk-DMzUqQB5.js +1 -0
- mycode/server/static/assets/ayu-dark-DYE7WIF3.js +1 -0
- mycode/server/static/assets/ayu-light-BA47KaF1.js +1 -0
- mycode/server/static/assets/ayu-mirage-32ctXXKs.js +1 -0
- mycode/server/static/assets/ballerina-BFfxhgS-.js +1 -0
- mycode/server/static/assets/bat-BkioyH1T.js +1 -0
- mycode/server/static/assets/beancount-k_qm7-4y.js +1 -0
- mycode/server/static/assets/berry-uYugtg8r.js +1 -0
- mycode/server/static/assets/bibtex-CHM0blh-.js +1 -0
- mycode/server/static/assets/bicep-Bmn6On1c.js +1 -0
- mycode/server/static/assets/bird2-BIv1doCn.js +1 -0
- mycode/server/static/assets/blade-BjGOyj-B.js +1 -0
- mycode/server/static/assets/bsl-BO_Y6i37.js +1 -0
- mycode/server/static/assets/c-BIGW1oBm.js +1 -0
- mycode/server/static/assets/c3-eo99z4R2.js +1 -0
- mycode/server/static/assets/cadence-Bv_4Rxtq.js +1 -0
- mycode/server/static/assets/cairo-KRGpt6FW.js +1 -0
- mycode/server/static/assets/catppuccin-frappe-DFWUc33u.js +1 -0
- mycode/server/static/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
- mycode/server/static/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
- mycode/server/static/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
- mycode/server/static/assets/clarity-D53aC0YG.js +1 -0
- mycode/server/static/assets/clojure-P80f7IUj.js +1 -0
- mycode/server/static/assets/cmake-D1j8_8rp.js +1 -0
- mycode/server/static/assets/cobol-nBiQ_Alo.js +1 -0
- mycode/server/static/assets/codeowners-Bp6g37R7.js +1 -0
- mycode/server/static/assets/codeql-DsOJ9woJ.js +1 -0
- mycode/server/static/assets/coffee-Ch7k5sss.js +1 -0
- mycode/server/static/assets/common-lisp-Cg-RD9OK.js +1 -0
- mycode/server/static/assets/coq-DkFqJrB1.js +1 -0
- mycode/server/static/assets/cpp-CofmeUqb.js +1 -0
- mycode/server/static/assets/crystal-DNxU26gB.js +1 -0
- mycode/server/static/assets/csharp-COcwbKMJ.js +1 -0
- mycode/server/static/assets/css-CLj8gQPS.js +1 -0
- mycode/server/static/assets/csv-fuZLfV_i.js +1 -0
- mycode/server/static/assets/cue-D82EKSYY.js +1 -0
- mycode/server/static/assets/cypher-COkxafJQ.js +1 -0
- mycode/server/static/assets/d-85-TOEBH.js +1 -0
- mycode/server/static/assets/dark-plus-C3mMm8J8.js +1 -0
- mycode/server/static/assets/dart-bE4Kk8sk.js +1 -0
- mycode/server/static/assets/dax-CEL-wOlO.js +1 -0
- mycode/server/static/assets/desktop-BmXAJ9_W.js +1 -0
- mycode/server/static/assets/diff-D97Zzqfu.js +1 -0
- mycode/server/static/assets/docker-BcOcwvcX.js +1 -0
- mycode/server/static/assets/dotenv-Da5cRb03.js +1 -0
- mycode/server/static/assets/dracula-BzJJZx-M.js +1 -0
- mycode/server/static/assets/dracula-soft-BXkSAIEj.js +1 -0
- mycode/server/static/assets/dream-maker-BtqSS_iP.js +1 -0
- mycode/server/static/assets/edge-FbVlp4U3.js +1 -0
- mycode/server/static/assets/elixir-CkH2-t6x.js +1 -0
- mycode/server/static/assets/elm-DbKCFpqz.js +1 -0
- mycode/server/static/assets/emacs-lisp-CXvaQtF9.js +1 -0
- mycode/server/static/assets/erb-BYCe7drp.js +1 -0
- mycode/server/static/assets/erlang-DsQrWhSR.js +1 -0
- mycode/server/static/assets/everforest-dark-BgDCqdQA.js +1 -0
- mycode/server/static/assets/everforest-light-C8M2exoo.js +1 -0
- mycode/server/static/assets/fennel-BYunw83y.js +1 -0
- mycode/server/static/assets/fish-BvzEVeQv.js +1 -0
- mycode/server/static/assets/fluent-C4IJs8-o.js +1 -0
- mycode/server/static/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
- mycode/server/static/assets/fortran-free-form-BxgE0vQu.js +1 -0
- mycode/server/static/assets/fsharp-CXgrBDvD.js +1 -0
- mycode/server/static/assets/gdresource-BOOCDP_w.js +1 -0
- mycode/server/static/assets/gdscript-C5YyOfLZ.js +1 -0
- mycode/server/static/assets/gdshader-DkwncUOv.js +1 -0
- mycode/server/static/assets/genie-D0YGMca9.js +1 -0
- mycode/server/static/assets/gherkin-DyxjwDmM.js +1 -0
- mycode/server/static/assets/git-commit-F4YmCXRG.js +1 -0
- mycode/server/static/assets/git-rebase-r7XF79zn.js +1 -0
- mycode/server/static/assets/github-dark-DHJKELXO.js +1 -0
- mycode/server/static/assets/github-dark-default-Cuk6v7N8.js +1 -0
- mycode/server/static/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
- mycode/server/static/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
- mycode/server/static/assets/github-light-DAi9KRSo.js +1 -0
- mycode/server/static/assets/github-light-default-D7oLnXFd.js +1 -0
- mycode/server/static/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
- mycode/server/static/assets/gleam-BspZqrRM.js +1 -0
- mycode/server/static/assets/glimmer-js-ByusRIyA.js +1 -0
- mycode/server/static/assets/glimmer-ts-BfAWNZQY.js +1 -0
- mycode/server/static/assets/glsl-DplSGwfg.js +1 -0
- mycode/server/static/assets/gn-n2N0HUVH.js +1 -0
- mycode/server/static/assets/gnuplot-DdkO51Og.js +1 -0
- mycode/server/static/assets/go-C27-OAKa.js +1 -0
- mycode/server/static/assets/graphql-ChdNCCLP.js +1 -0
- mycode/server/static/assets/groovy-gcz8RCvz.js +1 -0
- mycode/server/static/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
- mycode/server/static/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
- mycode/server/static/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
- mycode/server/static/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
- mycode/server/static/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
- mycode/server/static/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
- mycode/server/static/assets/hack-i7_Ulhet.js +1 -0
- mycode/server/static/assets/haml-D5jkg6IW.js +1 -0
- mycode/server/static/assets/handlebars-BpdQsYii.js +1 -0
- mycode/server/static/assets/haskell-Df6bDoY_.js +1 -0
- mycode/server/static/assets/haxe-CzTSHFRz.js +1 -0
- mycode/server/static/assets/hcl-BWvSN4gD.js +1 -0
- mycode/server/static/assets/hjson-D5-asLiD.js +1 -0
- mycode/server/static/assets/hlsl-D3lLCCz7.js +1 -0
- mycode/server/static/assets/horizon-BUw7H-hv.js +1 -0
- mycode/server/static/assets/horizon-bright-CUuTKBJd.js +1 -0
- mycode/server/static/assets/houston-DnULxvSX.js +1 -0
- mycode/server/static/assets/html-derivative-DlHx6ybY.js +1 -0
- mycode/server/static/assets/html-pp8916En.js +1 -0
- mycode/server/static/assets/http-jrhK8wxY.js +1 -0
- mycode/server/static/assets/hurl-irOxFIW8.js +1 -0
- mycode/server/static/assets/hxml-Bvhsp5Yf.js +1 -0
- mycode/server/static/assets/hy-DFXneXwc.js +1 -0
- mycode/server/static/assets/imba-DGztddWO.js +1 -0
- mycode/server/static/assets/index-B4e4WQPq.css +1 -0
- mycode/server/static/assets/index-C2xTNJGd.js +203 -0
- mycode/server/static/assets/ini-BEwlwnbL.js +1 -0
- mycode/server/static/assets/java-CylS5w8V.js +1 -0
- mycode/server/static/assets/javascript-wDzz0qaB.js +1 -0
- mycode/server/static/assets/jinja-f2NsQr07.js +1 -0
- mycode/server/static/assets/jison-wvAkD_A8.js +1 -0
- mycode/server/static/assets/json-Cp-IABpG.js +1 -0
- mycode/server/static/assets/json5-C9tS-k6U.js +1 -0
- mycode/server/static/assets/jsonc-Des-eS-w.js +1 -0
- mycode/server/static/assets/jsonl-DcaNXYhu.js +1 -0
- mycode/server/static/assets/jsonnet-DFQXde-d.js +1 -0
- mycode/server/static/assets/jssm-C2t-YnRu.js +1 -0
- mycode/server/static/assets/jsx-g9-lgVsj.js +1 -0
- mycode/server/static/assets/julia-CxzCAyBv.js +1 -0
- mycode/server/static/assets/just-VxiPbLrw.js +1 -0
- mycode/server/static/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
- mycode/server/static/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
- mycode/server/static/assets/kanagawa-wave-DWedfzmr.js +1 -0
- mycode/server/static/assets/katex-DnJR2-55.css +1 -0
- mycode/server/static/assets/kdl-DV7GczEv.js +1 -0
- mycode/server/static/assets/kotlin-BdnUsdx6.js +1 -0
- mycode/server/static/assets/kusto-wEQ09or8.js +1 -0
- mycode/server/static/assets/laserwave-DUszq2jm.js +1 -0
- mycode/server/static/assets/latex-CWtU0Tv5.js +1 -0
- mycode/server/static/assets/lean-BZvkOJ9d.js +1 -0
- mycode/server/static/assets/less-B1dDrJ26.js +1 -0
- mycode/server/static/assets/light-plus-B7mTdjB0.js +1 -0
- mycode/server/static/assets/liquid-C0sCDyMI.js +1 -0
- mycode/server/static/assets/llvm-DjAJT7YJ.js +1 -0
- mycode/server/static/assets/log-2UxHyX5q.js +1 -0
- mycode/server/static/assets/logo-BtOb2qkB.js +1 -0
- mycode/server/static/assets/lua-BaeVxFsk.js +1 -0
- mycode/server/static/assets/luau-C-HG3fhB.js +1 -0
- mycode/server/static/assets/make-CHLpvVh8.js +1 -0
- mycode/server/static/assets/markdown-Cvjx9yec.js +1 -0
- mycode/server/static/assets/marko-DjSrsDqO.js +1 -0
- mycode/server/static/assets/material-theme-D5KoaKCx.js +1 -0
- mycode/server/static/assets/material-theme-darker-BfHTSMKl.js +1 -0
- mycode/server/static/assets/material-theme-lighter-B0m2ddpp.js +1 -0
- mycode/server/static/assets/material-theme-ocean-CyktbL80.js +1 -0
- mycode/server/static/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
- mycode/server/static/assets/matlab-D7o27uSR.js +1 -0
- mycode/server/static/assets/mdc-DTYItulj.js +1 -0
- mycode/server/static/assets/mdx-Cmh6b_Ma.js +1 -0
- mycode/server/static/assets/mermaid-mWjccvbQ.js +1 -0
- mycode/server/static/assets/min-dark-CafNBF8u.js +1 -0
- mycode/server/static/assets/min-light-CTRr51gU.js +1 -0
- mycode/server/static/assets/mipsasm-CKIfxQSi.js +1 -0
- mycode/server/static/assets/mojo-rZm6bMo-.js +1 -0
- mycode/server/static/assets/monokai-D4h5O-jR.js +1 -0
- mycode/server/static/assets/moonbit-_H4v1dQx.js +1 -0
- mycode/server/static/assets/move-IF9eRakj.js +1 -0
- mycode/server/static/assets/narrat-DRg8JJMk.js +1 -0
- mycode/server/static/assets/nextflow-C-mBbutL.js +1 -0
- mycode/server/static/assets/nextflow-groovy-vE_lwT2v.js +1 -0
- mycode/server/static/assets/nginx-BpAMiNFr.js +1 -0
- mycode/server/static/assets/night-owl-C39BiMTA.js +1 -0
- mycode/server/static/assets/night-owl-light-CMTm3GFP.js +1 -0
- mycode/server/static/assets/nim-BIad80T-.js +1 -0
- mycode/server/static/assets/nix-CwoSXNpI.js +1 -0
- mycode/server/static/assets/nord-Ddv68eIx.js +1 -0
- mycode/server/static/assets/nushell-Cz2AlsmD.js +1 -0
- mycode/server/static/assets/objective-c-DXmwc3jG.js +1 -0
- mycode/server/static/assets/objective-cpp-CLxacb5B.js +1 -0
- mycode/server/static/assets/ocaml-C0hk2d4L.js +1 -0
- mycode/server/static/assets/odin-BBf5iR-q.js +1 -0
- mycode/server/static/assets/one-dark-pro-DVMEJ2y_.js +1 -0
- mycode/server/static/assets/one-light-C3Wv6jpd.js +1 -0
- mycode/server/static/assets/openscad-C4EeE6gA.js +1 -0
- mycode/server/static/assets/pascal-D93ZcfNL.js +1 -0
- mycode/server/static/assets/perl-NvoQZIq0.js +1 -0
- mycode/server/static/assets/php-R6g_5hLQ.js +1 -0
- mycode/server/static/assets/pkl-u5AG7uiY.js +1 -0
- mycode/server/static/assets/plastic-3e1v2bzS.js +1 -0
- mycode/server/static/assets/plsql-ChMvpjG-.js +1 -0
- mycode/server/static/assets/po-BTJTHyun.js +1 -0
- mycode/server/static/assets/poimandres-CS3Unz2-.js +1 -0
- mycode/server/static/assets/polar-C0HS_06l.js +1 -0
- mycode/server/static/assets/postcss-CXtECtnM.js +1 -0
- mycode/server/static/assets/powerquery-CEu0bR-o.js +1 -0
- mycode/server/static/assets/powershell-Dpen1YoG.js +1 -0
- mycode/server/static/assets/prisma-Dd19v3D-.js +1 -0
- mycode/server/static/assets/prolog-CbFg5uaA.js +1 -0
- mycode/server/static/assets/proto-C7zT0LnQ.js +1 -0
- mycode/server/static/assets/pug-DKIMFp6K.js +1 -0
- mycode/server/static/assets/puppet-BMWR74SV.js +1 -0
- mycode/server/static/assets/purescript-CklMAg4u.js +1 -0
- mycode/server/static/assets/python-B6aJPvgy.js +1 -0
- mycode/server/static/assets/qml-3beO22l8.js +1 -0
- mycode/server/static/assets/qmldir-C8lEn-DE.js +1 -0
- mycode/server/static/assets/qss-IeuSbFQv.js +1 -0
- mycode/server/static/assets/r-Dspwwk_N.js +1 -0
- mycode/server/static/assets/racket-BqYA7rlc.js +1 -0
- mycode/server/static/assets/raku-DXvB9xmW.js +1 -0
- mycode/server/static/assets/razor-BDqjjVU7.js +1 -0
- mycode/server/static/assets/red-bN70gL4F.js +1 -0
- mycode/server/static/assets/reg-C-SQnVFl.js +1 -0
- mycode/server/static/assets/regexp-CDVJQ6XC.js +1 -0
- mycode/server/static/assets/rel-C3B-1QV4.js +1 -0
- mycode/server/static/assets/riscv-BM1_JUlF.js +1 -0
- mycode/server/static/assets/ron-D8l8udqQ.js +1 -0
- mycode/server/static/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
- mycode/server/static/assets/rose-pine-moon-D4_iv3hh.js +1 -0
- mycode/server/static/assets/rose-pine-qdsjHGoJ.js +1 -0
- mycode/server/static/assets/rosmsg-BJDFO7_C.js +1 -0
- mycode/server/static/assets/rst-CRjBmOyv.js +1 -0
- mycode/server/static/assets/ruby-Wjq7vjNf.js +1 -0
- mycode/server/static/assets/rust-B1yitclQ.js +1 -0
- mycode/server/static/assets/sas-cz2c8ADy.js +1 -0
- mycode/server/static/assets/sass-Cj5Yp3dK.js +1 -0
- mycode/server/static/assets/scala-C151Ov-r.js +1 -0
- mycode/server/static/assets/scheme-C98Dy4si.js +1 -0
- mycode/server/static/assets/scss-D5BDwBP9.js +1 -0
- mycode/server/static/assets/sdbl-DVxCFoDh.js +1 -0
- mycode/server/static/assets/shaderlab-Dg9Lc6iA.js +1 -0
- mycode/server/static/assets/shellscript-Yzrsuije.js +1 -0
- mycode/server/static/assets/shellsession-BADoaaVG.js +1 -0
- mycode/server/static/assets/slack-dark-BthQWCQV.js +1 -0
- mycode/server/static/assets/slack-ochin-DqwNpetd.js +1 -0
- mycode/server/static/assets/smalltalk-BERRCDM3.js +1 -0
- mycode/server/static/assets/snazzy-light-Bw305WKR.js +1 -0
- mycode/server/static/assets/solarized-dark-DXbdFlpD.js +1 -0
- mycode/server/static/assets/solarized-light-L9t79GZl.js +1 -0
- mycode/server/static/assets/solidity-rGO070M0.js +1 -0
- mycode/server/static/assets/soy-8wufbnw4.js +1 -0
- mycode/server/static/assets/sparql-rVzFXLq3.js +1 -0
- mycode/server/static/assets/splunk-BtCnVYZw.js +1 -0
- mycode/server/static/assets/sql-BLtJtn59.js +1 -0
- mycode/server/static/assets/ssh-config-_ykCGR6B.js +1 -0
- mycode/server/static/assets/stata-BH5u7GGu.js +1 -0
- mycode/server/static/assets/stylus-BEDo0Tqx.js +1 -0
- mycode/server/static/assets/surrealql-Bq5Q-fJD.js +1 -0
- mycode/server/static/assets/svelte-Cy7k_4gC.js +1 -0
- mycode/server/static/assets/swift-D82vCrfD.js +1 -0
- mycode/server/static/assets/synthwave-84-CbfX1IO0.js +1 -0
- mycode/server/static/assets/system-verilog-CnnmHF94.js +1 -0
- mycode/server/static/assets/systemd-4A_iFExJ.js +1 -0
- mycode/server/static/assets/talonscript-CkByrt1z.js +1 -0
- mycode/server/static/assets/tasl-QIJgUcNo.js +1 -0
- mycode/server/static/assets/tcl-dwOrl1Do.js +1 -0
- mycode/server/static/assets/templ-DhtptRzy.js +1 -0
- mycode/server/static/assets/terraform-BETggiCN.js +1 -0
- mycode/server/static/assets/tex-idrVyKtj.js +1 -0
- mycode/server/static/assets/tokyo-night-hegEt444.js +1 -0
- mycode/server/static/assets/toml-vGWfd6FD.js +1 -0
- mycode/server/static/assets/ts-tags-DQrlYJgV.js +1 -0
- mycode/server/static/assets/tsv-B_m7g4N7.js +1 -0
- mycode/server/static/assets/tsx-COt5Ahok.js +1 -0
- mycode/server/static/assets/turtle-BsS91CYL.js +1 -0
- mycode/server/static/assets/twig-xg9kU7Mw.js +1 -0
- mycode/server/static/assets/typescript-BPQ3VLAy.js +1 -0
- mycode/server/static/assets/typespec-CAFt9gP4.js +1 -0
- mycode/server/static/assets/typst-DHCkPAjA.js +1 -0
- mycode/server/static/assets/v-BcVCzyr7.js +1 -0
- mycode/server/static/assets/vala-CsfeWuGM.js +1 -0
- mycode/server/static/assets/vb-D17OF-Vu.js +1 -0
- mycode/server/static/assets/verilog-BQ8w6xss.js +1 -0
- mycode/server/static/assets/vesper-DU1UobuO.js +1 -0
- mycode/server/static/assets/vhdl-CeAyd5Ju.js +1 -0
- mycode/server/static/assets/viml-CJc9bBzg.js +1 -0
- mycode/server/static/assets/vitesse-black-Bkuqu6BP.js +1 -0
- mycode/server/static/assets/vitesse-dark-D0r3Knsf.js +1 -0
- mycode/server/static/assets/vitesse-light-CVO1_9PV.js +1 -0
- mycode/server/static/assets/vue-D2xRrEX4.js +1 -0
- mycode/server/static/assets/vue-html-AaS7Mt5G.js +1 -0
- mycode/server/static/assets/vue-vine-BoDAl6tE.js +1 -0
- mycode/server/static/assets/vyper-CDx5xZoG.js +1 -0
- mycode/server/static/assets/wasm-CG6Dc4jp.js +1 -0
- mycode/server/static/assets/wasm-MzD3tlZU.js +1 -0
- mycode/server/static/assets/wenyan-BV7otONQ.js +1 -0
- mycode/server/static/assets/wgsl-Dx-B1_4e.js +1 -0
- mycode/server/static/assets/wikitext-BhOHFoWU.js +1 -0
- mycode/server/static/assets/wit-5i3qLPDT.js +1 -0
- mycode/server/static/assets/wolfram-lXgVvXCa.js +1 -0
- mycode/server/static/assets/xml-sdJ4AIDG.js +1 -0
- mycode/server/static/assets/xsl-CtQFsRM5.js +1 -0
- mycode/server/static/assets/yaml-Buea-lGh.js +1 -0
- mycode/server/static/assets/zenscript-DVFEvuxE.js +1 -0
- mycode/server/static/assets/zig-VOosw3JB.js +1 -0
- mycode/server/static/favicon_slashes.svg +12 -0
- mycode/server/static/index.html +35 -0
- mycode_cli-0.1.0.dist-info/METADATA +186 -0
- mycode_cli-0.1.0.dist-info/RECORD +404 -0
- mycode_cli-0.1.0.dist-info/WHEEL +4 -0
- mycode_cli-0.1.0.dist-info/entry_points.txt +2 -0
- 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]
|