llms-py 3.0.6__tar.gz → 3.0.23__tar.gz

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 (227) hide show
  1. {llms_py-3.0.6/llms_py.egg-info → llms_py-3.0.23}/PKG-INFO +2 -2
  2. {llms_py-3.0.6 → llms_py-3.0.23}/README.md +1 -1
  3. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/analytics/ui/index.mjs +1 -1
  4. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/__init__.py +7 -3
  5. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/db.py +7 -3
  6. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/ui/Recents.mjs +1 -1
  7. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/ui/threadStore.mjs +14 -6
  8. llms_py-3.0.23/llms/extensions/computer/README.md +96 -0
  9. llms_py-3.0.23/llms/extensions/computer/__init__.py +67 -0
  10. llms_py-3.0.23/llms/extensions/computer/base.py +80 -0
  11. llms_py-3.0.23/llms/extensions/computer/bash.py +185 -0
  12. llms_py-3.0.23/llms/extensions/computer/computer.py +523 -0
  13. llms_py-3.0.23/llms/extensions/computer/edit.py +299 -0
  14. llms_py-3.0.23/llms/extensions/computer/filesystem.py +573 -0
  15. llms_py-3.0.23/llms/extensions/computer/platform.py +461 -0
  16. llms_py-3.0.23/llms/extensions/computer/run.py +37 -0
  17. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/__init__.py +13 -49
  18. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/gallery/ui/index.mjs +2 -1
  19. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/__init__.py +2 -0
  20. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/anthropic.py +29 -2
  21. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/cerebras.py +0 -1
  22. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/chutes.py +7 -9
  23. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/google.py +115 -37
  24. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/nvidia.py +9 -11
  25. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/openai.py +6 -3
  26. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/providers/openrouter.py +1 -1
  27. llms_py-3.0.23/llms/extensions/providers/zai.py +182 -0
  28. llms_py-3.0.23/llms/extensions/skills/LICENSE +202 -0
  29. llms_py-3.0.23/llms/extensions/skills/README.md +275 -0
  30. llms_py-3.0.23/llms/extensions/skills/__init__.py +489 -0
  31. llms_py-3.0.23/llms/extensions/skills/errors.py +25 -0
  32. llms_py-3.0.23/llms/extensions/skills/installer.py +415 -0
  33. llms_py-3.0.23/llms/extensions/skills/models.py +39 -0
  34. llms_py-3.0.23/llms/extensions/skills/parser.py +178 -0
  35. llms_py-3.0.23/llms/extensions/skills/ui/data/skills-top-5000.json +1 -0
  36. llms_py-3.0.23/llms/extensions/skills/ui/index.mjs +944 -0
  37. llms_py-3.0.23/llms/extensions/skills/ui/skills/create-plan/SKILL.md +74 -0
  38. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/LICENSE.txt +202 -0
  39. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/SKILL.md +356 -0
  40. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/references/output-patterns.md +82 -0
  41. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/references/workflows.md +28 -0
  42. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/scripts/init_skill.py +299 -0
  43. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/scripts/package_skill.py +111 -0
  44. llms_py-3.0.23/llms/extensions/skills/ui/skills/skill-creator/scripts/quick_validate.py +98 -0
  45. llms_py-3.0.23/llms/extensions/skills/validator.py +177 -0
  46. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/system_prompts/ui/index.mjs +6 -10
  47. llms_py-3.0.23/llms/extensions/tools/__init__.py +67 -0
  48. llms_py-3.0.23/llms/extensions/tools/ui/index.mjs +837 -0
  49. {llms_py-3.0.6 → llms_py-3.0.23}/llms/llms.json +45 -21
  50. {llms_py-3.0.6 → llms_py-3.0.23}/llms/main.py +967 -227
  51. {llms_py-3.0.6 → llms_py-3.0.23}/llms/providers-extra.json +38 -0
  52. llms_py-3.0.23/llms/providers.json +1 -0
  53. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/App.mjs +1 -1
  54. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/ai.mjs +1 -1
  55. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/app.css +432 -17
  56. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/ctx.mjs +91 -19
  57. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/index.mjs +2 -2
  58. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/servicestack-vue.mjs +3 -3
  59. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/markdown.mjs +2 -1
  60. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/chat/ChatBody.mjs +611 -141
  61. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/chat/index.mjs +125 -107
  62. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/tailwind.input.css +64 -0
  63. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/utils.mjs +105 -5
  64. {llms_py-3.0.6 → llms_py-3.0.23/llms_py.egg-info}/PKG-INFO +2 -2
  65. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/SOURCES.txt +31 -0
  66. {llms_py-3.0.6 → llms_py-3.0.23}/pyproject.toml +1 -1
  67. {llms_py-3.0.6 → llms_py-3.0.23}/setup.py +1 -1
  68. llms_py-3.0.23/tests/test_core_tools_direct.py +38 -0
  69. llms_py-3.0.23/tests/test_gemini_tool_calling.py +101 -0
  70. llms_py-3.0.23/tests/test_interleaved_thinking.py +60 -0
  71. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_provider_config.py +4 -1
  72. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_utils.py +66 -14
  73. llms_py-3.0.6/llms/extensions/tools/__init__.py +0 -5
  74. llms_py-3.0.6/llms/extensions/tools/ui/index.mjs +0 -204
  75. llms_py-3.0.6/llms/providers.json +0 -1
  76. {llms_py-3.0.6 → llms_py-3.0.23}/LICENSE +0 -0
  77. {llms_py-3.0.6 → llms_py-3.0.23}/MANIFEST.in +0 -0
  78. {llms_py-3.0.6 → llms_py-3.0.23}/llms/__init__.py +0 -0
  79. {llms_py-3.0.6 → llms_py-3.0.23}/llms/__main__.py +0 -0
  80. {llms_py-3.0.6 → llms_py-3.0.23}/llms/db.py +0 -0
  81. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/README.md +0 -0
  82. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/app/ui/index.mjs +0 -0
  83. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/CALCULATOR.md +0 -0
  84. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -0
  85. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -0
  86. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -0
  87. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -0
  88. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -0
  89. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -0
  90. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -0
  91. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -0
  92. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -0
  93. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/codemirror.css +0 -0
  94. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/codemirror.js +0 -0
  95. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -0
  96. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  97. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -0
  98. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -0
  99. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -0
  100. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -0
  101. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -0
  102. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -0
  103. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -0
  104. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/core_tools/ui/index.mjs +0 -0
  105. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/gallery/README.md +0 -0
  106. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/gallery/__init__.py +0 -0
  107. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/gallery/db.py +0 -0
  108. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/README.md +0 -0
  109. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/__init__.py +0 -0
  110. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/README.md +0 -0
  111. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/auto-render.js +0 -0
  112. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/auto-render.min.js +0 -0
  113. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/auto-render.mjs +0 -0
  114. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/copy-tex.js +0 -0
  115. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -0
  116. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -0
  117. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -0
  118. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -0
  119. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -0
  120. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mhchem.js +0 -0
  121. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mhchem.min.js +0 -0
  122. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/mhchem.mjs +0 -0
  123. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -0
  124. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -0
  125. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -0
  126. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  127. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  128. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  129. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  130. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  131. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  132. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  133. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  134. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  135. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  136. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  137. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  138. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  139. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  140. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  141. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  142. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  143. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  144. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  145. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  146. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  147. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  148. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  149. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  150. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  151. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  152. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  153. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  154. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  155. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  156. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  157. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  158. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  159. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  160. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  161. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  162. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  163. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  164. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  165. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  166. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  167. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  168. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  169. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  170. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  171. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  172. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  173. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  174. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  175. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  176. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  177. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  178. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  179. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  180. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  181. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  182. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  183. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  184. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  185. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  186. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/index.mjs +0 -0
  187. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex-swap.css +0 -0
  188. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex-swap.min.css +0 -0
  189. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.css +0 -0
  190. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.js +0 -0
  191. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.min.css +0 -0
  192. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.min.js +0 -0
  193. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.min.mjs +0 -0
  194. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/katex/ui/katex.mjs +0 -0
  195. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/system_prompts/README.md +0 -0
  196. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/system_prompts/__init__.py +0 -0
  197. {llms_py-3.0.6 → llms_py-3.0.23}/llms/extensions/system_prompts/ui/prompts.json +0 -0
  198. {llms_py-3.0.6 → llms_py-3.0.23}/llms/index.html +0 -0
  199. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/fav.svg +0 -0
  200. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/chart.js +0 -0
  201. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/charts.mjs +0 -0
  202. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/color.js +0 -0
  203. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/highlight.min.mjs +0 -0
  204. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/idb.min.mjs +0 -0
  205. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/marked.min.mjs +0 -0
  206. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/servicestack-client.mjs +0 -0
  207. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/vue-router.min.mjs +0 -0
  208. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/vue.min.mjs +0 -0
  209. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/lib/vue.mjs +0 -0
  210. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/chat/SettingsDialog.mjs +0 -0
  211. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/icons.mjs +0 -0
  212. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/layout.mjs +0 -0
  213. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/modules/model-selector.mjs +0 -0
  214. {llms_py-3.0.6 → llms_py-3.0.23}/llms/ui/typography.css +0 -0
  215. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/dependency_links.txt +0 -0
  216. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/entry_points.txt +0 -0
  217. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/not-zip-safe +0 -0
  218. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/requires.txt +0 -0
  219. {llms_py-3.0.6 → llms_py-3.0.23}/llms_py.egg-info/top_level.txt +0 -0
  220. {llms_py-3.0.6 → llms_py-3.0.23}/requirements.txt +0 -0
  221. {llms_py-3.0.6 → llms_py-3.0.23}/setup.cfg +0 -0
  222. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_async.py +0 -0
  223. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_config.py +0 -0
  224. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_extensions.py +0 -0
  225. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_gemini_upload.py +0 -0
  226. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_integration.py +0 -0
  227. {llms_py-3.0.6 → llms_py-3.0.23}/tests/test_provider_checks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.6
3
+ Version: 3.0.23
4
4
  Summary: A lightweight CLI tool and OpenAI-compatible server for querying multiple Large Language Model (LLM) providers
5
5
  Home-page: https://github.com/ServiceStack/llms
6
6
  Author: ServiceStack
@@ -44,6 +44,6 @@ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing mu
44
44
 
45
45
  [llmspy.org](https://llmspy.org)
46
46
 
47
- [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
47
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home-v3.webp?raw=true)](https://llmspy.org)
48
48
 
49
49
  GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -4,6 +4,6 @@ Lightweight CLI, API and ChatGPT-like alternative to Open WebUI for accessing mu
4
4
 
5
5
  [llmspy.org](https://llmspy.org)
6
6
 
7
- [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home.webp?raw=true)](https://llmspy.org)
7
+ [![](https://github.com/ServiceStack/llmspy.org/blob/main/public/img/llmspy-home-v3.webp?raw=true)](https://llmspy.org)
8
8
 
9
9
  GitHub: [llmspy.org](https://github.com/ServiceStack/llmspy.org)
@@ -370,7 +370,7 @@ export const Analytics = {
370
370
  </div>
371
371
  <div>
372
372
  <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Duration</div>
373
- <div v-if="request.duration" class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $fmt.humanifyMs(request.duration) }}</div>
373
+ <div v-if="request.duration" class="text-sm font-semibold text-gray-900 dark:text-gray-100">{{ $fmt.humanifyMs(request.duration * 1000) }}</div>
374
374
  </div>
375
375
  <div>
376
376
  <div class="text-xs text-gray-500 dark:text-gray-400 font-medium">Speed</div>
@@ -199,7 +199,6 @@ def install(ctx):
199
199
  "model": thread.get("model"),
200
200
  "messages": thread.get("messages"),
201
201
  "modalities": thread.get("modalities"),
202
- "systemPrompt": thread.get("systemPrompt"),
203
202
  "tools": thread.get("tools"), # tools request
204
203
  "metadata": metadata,
205
204
  }
@@ -224,7 +223,10 @@ def install(ctx):
224
223
  await ctx.chat_completion(chat_req, context=context_req)
225
224
  except Exception as ex:
226
225
  ctx.err("run_chat", ex)
227
- # not necessary to update thread in db with error as it's done in chat_error filter
226
+ # shouldn't be necessary to update thread in db with error as it's done in chat_error filter
227
+ thread = thread_dto(g_db.get_thread(id, user=ctx.get_username(request)))
228
+ if thread and not thread.get("error"):
229
+ await chat_error(ex, context)
228
230
 
229
231
  asyncio.create_task(run_chat(chat, context))
230
232
 
@@ -444,7 +446,9 @@ def install(ctx):
444
446
  input_tokens = usage.get("prompt_tokens", 0)
445
447
  output_tokens = usage.get("completion_tokens", 0)
446
448
  total_tokens = usage.get("total_tokens", input_tokens + output_tokens)
447
- cost = o.get("cost", ((input_price * input_tokens) + (output_price * output_tokens)) / 1000000)
449
+ cost = usage.get("cost") or o.get(
450
+ "cost", ((input_price * input_tokens) + (output_price * output_tokens)) / 1000000
451
+ )
448
452
 
449
453
  request = {
450
454
  "user": user,
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import os
3
+ import time
3
4
  from datetime import datetime, timedelta
4
5
  from typing import Any, Dict
5
6
 
@@ -344,9 +345,12 @@ class AppDB:
344
345
  else:
345
346
  thread["createdAt"] = now
346
347
  thread["updatedAt"] = now
348
+ initial_timestamp = int(time.time() * 1000) + 1
347
349
  if "messages" in thread:
348
- for m in thread["messages"]:
350
+ for idx, m in enumerate(thread["messages"]):
349
351
  self.ctx.cache_message_inline_data(m)
352
+ if "timestamp" not in m:
353
+ m["timestamp"] = initial_timestamp + idx
350
354
  return with_user(thread, user=user)
351
355
 
352
356
  def create_thread(self, thread: Dict[str, Any], user=None):
@@ -528,9 +532,9 @@ class AppDB:
528
532
  with self.db.create_writer_connection() as conn:
529
533
  conn.execute(
530
534
  "UPDATE thread SET completedAt = :completedAt, error = :error WHERE completedAt IS NULL",
531
- {"completedAt": datetime.now(), "error": "Server Shutdown"},
535
+ {"completedAt": datetime.now().isoformat(" "), "error": "Server Shutdown"},
532
536
  )
533
537
  conn.execute(
534
538
  "UPDATE request SET completedAt = :completedAt, error = :error WHERE completedAt IS NULL",
535
- {"completedAt": datetime.now(), "error": "Server Shutdown"},
539
+ {"completedAt": datetime.now().isoformat(" "), "error": "Server Shutdown"},
536
540
  )
@@ -67,7 +67,7 @@ const RecentResults = {
67
67
 
68
68
  const normalized = (s) => (s || '').toString().toLowerCase()
69
69
  const replaceChars = new Set('<>`*|#'.split(''))
70
- const clean = s => [...s].map(c => replaceChars.has(c) ? ' ' : c).join('')
70
+ const clean = s => [...(s || '')].map(c => replaceChars.has(c) ? ' ' : c).join('')
71
71
 
72
72
  const loadMore = async (reset = false) => {
73
73
  if (reset) {
@@ -139,10 +139,11 @@ async function updateThread(threadId, updates) {
139
139
  }
140
140
  }
141
141
 
142
- async function deleteMessageFromThread(threadId, messageId) {
142
+ async function deleteMessageFromThread(threadId, timestamp) {
143
143
  const thread = await getThread(threadId)
144
144
  if (!thread) throw new Error('Thread not found')
145
- const updatedMessages = thread.messages.filter(m => m.id !== messageId)
145
+ const updatedMessages = thread.messages.filter(m => m.timestamp !== timestamp)
146
+ console.log('deleteMessageFromThread', threadId, timestamp, updatedMessages)
146
147
  await updateThread(threadId, { messages: updatedMessages })
147
148
  }
148
149
 
@@ -150,7 +151,7 @@ async function updateMessageInThread(threadId, messageId, updates) {
150
151
  const thread = await getThread(threadId)
151
152
  if (!thread) throw new Error('Thread not found')
152
153
 
153
- const messageIndex = thread.messages.findIndex(m => m.id === messageId)
154
+ const messageIndex = thread.messages.findIndex(m => m.timestamp === messageId)
154
155
  if (messageIndex === -1) throw new Error('Message not found')
155
156
 
156
157
  const updatedMessages = [...thread.messages]
@@ -353,11 +354,11 @@ async function startNewThread({ title, model, tools, redirect }) {
353
354
  async function queueChat(ctxRequest, options = {}) {
354
355
  if (!ctxRequest.request) return ctx.createErrorResult({ message: 'No request provided' })
355
356
  if (!ctxRequest.thread) return ctx.createErrorResult({ message: 'No thread provided' })
356
- if (!ctxRequest.request.metadata) {
357
- ctxRequest.request.metadata = {}
358
- }
357
+ ctxRequest = ctx.createChatContext(ctxRequest)
359
358
  ctx.chatRequestFilters.forEach(f => f(ctxRequest))
360
359
  const { thread, request } = ctxRequest
360
+ ctx.completeChatContext(ctxRequest)
361
+
361
362
  const api = await ctx.postJson(`/ext/app/threads/${thread.id}/chat`, {
362
363
  ...options,
363
364
  body: typeof request == 'string'
@@ -380,6 +381,12 @@ async function loadThreadDetails(id, opt = null) {
380
381
  return threadDetails.value[id]
381
382
  }
382
383
 
384
+ function getCurrentThreadSystemPrompt() {
385
+ return currentThread.value?.systemPrompt
386
+ ?? currentThread.value?.messages?.find(m => m.role == 'system')?.content
387
+ ?? ''
388
+ }
389
+
383
390
  // Export the store
384
391
  export function useThreadStore() {
385
392
  return {
@@ -390,6 +397,7 @@ export function useThreadStore() {
390
397
  groupedThreads,
391
398
 
392
399
  // Actions
400
+ getCurrentThreadSystemPrompt,
393
401
  query,
394
402
  createThread,
395
403
  updateThread,
@@ -0,0 +1,96 @@
1
+ # Computer Use Tools
2
+
3
+ This extension provides a set of tools that allow an Agent to interact with a computer environment in a way similar to a human user. It includes capabilities for screen interaction (mouse/keyboard), shell execution, and file editing. Based on [Anthropic's computer use tools](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo).
4
+
5
+ ## Available Tools
6
+
7
+ ### 1. Computer Tool (`computer`)
8
+ Allows interaction with the screen, keyboard, and mouse.
9
+
10
+ **Capabilities:**
11
+ - **Mouse Interaction**: Move cursor, click (left, right, middle, double, triple), click & drag.
12
+ - **Keyboard Interaction**: Type text, press specific keys or key combinations.
13
+ - **Screen**: Take screenshots, get cursor position.
14
+ - **Zooming**: Zoom into specific regions of the screen (Action: `zoom`).
15
+
16
+ **Key Parameters:**
17
+ - `action`: The action to perform (e.g., `mouse_move`, `left_click`, `type`, `screenshot`, `zoom`).
18
+ - `coordinate`: `(x, y)` coordinates for mouse actions.
19
+ - `text`: Text to type.
20
+ - `key`: Key sequence to press (e.g., `Return`, `Control+c`).
21
+ - `region`: `(x0, y0, x1, y1)` region for zooming.
22
+
23
+ ### 2. Bash Tool (`bash`)
24
+ Provides a persistent shell session to execute command-line instructions.
25
+
26
+ **Capabilities:**
27
+ - **Execute Commands**: Run any bash command.
28
+ - **Persistent Session**: State (like environment variables, working directory) is preserved between calls within the same session.
29
+ - **Process Management**: Can restart the session if needed.
30
+ - **Open Files/URLs**: Helper function `open` allows opening files or URLs using the system's default handler (`xdg-open`, `open`, or `start`).
31
+
32
+ **Key Parameters:**
33
+ - `command`: The bash command to execute.
34
+ - `restart`: Boolean to restart the session.
35
+
36
+ ### 3. Edit Tool (`str_replace_editor`)
37
+ A filesystem editor for viewing and modifying files.
38
+
39
+ **Capabilities:**
40
+ - **View**: Read file contents or list directories.
41
+ - **Create**: Create new files with content.
42
+ - **String Replace**: Replace unique strings in a file (robust for LLM editing).
43
+ - **Insert**: Insert text at specific line numbers.
44
+ - **Undo**: Undo the last edit to a file.
45
+
46
+ **Key Parameters:**
47
+ - `command`: The edit command (`view`, `create`, `str_replace`, `insert`, `undo_edit`).
48
+ - `path`: Absolute path to the file or directory.
49
+ - `file_text`: Content for file creation.
50
+ - `old_str` / `new_str`: Strings for replacement.
51
+
52
+ ---
53
+
54
+ ## Capabilities & Workflows
55
+
56
+ These tools are designed to work together to enable complex end-to-end tasks. An Agent can act as a developer, tester, or general user.
57
+
58
+ ### Example: "Build a Tetris web app in a tetris folder, open it then take a screenshot"
59
+
60
+ To achieve this high-level task, the Agent would sequence the tools as follows:
61
+
62
+ 1. **Create the Project Structure**
63
+ * **Tool**: `bash`
64
+ * **Command**: `mkdir -p tetris`
65
+ * *Result*: Creates the folder.
66
+
67
+ 2. **Create the Application Files**
68
+ * **Tool**: `edit` (command: `create`)
69
+ * **Path**: `/path/to/tetris/index.html`
70
+ * **Content**: (HTML code for Tetris game)
71
+ * *Result*: Writes the HTML file.
72
+
73
+ 3. **Open the Application**
74
+ * **Tool**: `bash` (via helper `open`) or `bash` directly.
75
+ * **Command**: `xdg-open /path/to/tetris/index.html` (Linux) or just `python -m http.server` and open localhost.
76
+ * *Result*: Opens the file in the default web browser.
77
+
78
+ 4. **Wait & Verify**
79
+ * **Tool**: `computer`
80
+ * **Action**: `wait` or `screenshot` to see if it loaded.
81
+
82
+ 5. **Take a Screenshot**
83
+ * **Tool**: `computer`
84
+ * **Action**: `screenshot`
85
+ * *Result*: Captures the visual state of the running Tetris app for the user to see.
86
+
87
+ ### How it handles the "Build a Tetris..." request:
88
+ When a user gives the command:
89
+ > "Build a Tetris web app in a tetris folder, open it then take a screenshot"
90
+
91
+ The Agent decomposes this into:
92
+ 1. **"Build... in a tetris folder"** -> Uses `bash` to make the directory and `edit` to write the `index.html` / `style.css` / `script.js` files.
93
+ 2. **"Open it"** -> Uses `bash` to run a server or open the file in a browser.
94
+ 3. **"Take a screenshot"** -> Uses `computer` to verify the visual output.
95
+
96
+ This combination allows the Agent to not just generate code, but **verify** it visually and interactively, closing the loop on development tasks.
@@ -0,0 +1,67 @@
1
+ """
2
+ Anthropic's Computer Use Tools
3
+ https://github.com/anthropics/claude-quickstarts/tree/main/computer-use-demo
4
+ """
5
+
6
+ import os
7
+
8
+ from .bash import open, run_bash
9
+ from .edit import edit
10
+ from .filesystem import (
11
+ create_directory,
12
+ directory_tree,
13
+ edit_file,
14
+ filesystem_init,
15
+ get_file_info,
16
+ list_allowed_directories,
17
+ list_directory,
18
+ list_directory_with_sizes,
19
+ move_file,
20
+ read_media_file,
21
+ read_multiple_files,
22
+ read_text_file,
23
+ search_files,
24
+ write_file,
25
+ )
26
+
27
+ # Try to detect screen resolution - may fail in headless environments (Docker, etc.)
28
+ _has_display = False
29
+ try:
30
+ from .platform import get_display_num, get_screen_resolution
31
+ width, height = get_screen_resolution()
32
+ # set environment variables
33
+ os.environ["WIDTH"] = str(width)
34
+ os.environ["HEIGHT"] = str(height)
35
+ os.environ["DISPLAY_NUM"] = str(get_display_num())
36
+ _has_display = True
37
+ from .computer import computer
38
+ except RuntimeError:
39
+ # No display server available - computer tool will not be registered
40
+ computer = None
41
+
42
+
43
+ def install(ctx):
44
+ filesystem_init(ctx)
45
+
46
+ ctx.register_tool(run_bash, group="computer")
47
+ ctx.register_tool(open, group="computer")
48
+ ctx.register_tool(edit, group="computer")
49
+ if _has_display and computer is not None:
50
+ ctx.register_tool(computer, group="computer")
51
+
52
+ ctx.register_tool(read_text_file, group="filesystem")
53
+ ctx.register_tool(read_media_file, group="filesystem")
54
+ ctx.register_tool(read_multiple_files, group="filesystem")
55
+ ctx.register_tool(write_file, group="filesystem")
56
+ ctx.register_tool(edit_file, group="filesystem")
57
+ ctx.register_tool(create_directory, group="filesystem")
58
+ ctx.register_tool(list_directory, group="filesystem")
59
+ ctx.register_tool(list_directory_with_sizes, group="filesystem")
60
+ ctx.register_tool(directory_tree, group="filesystem")
61
+ ctx.register_tool(move_file, group="filesystem")
62
+ ctx.register_tool(search_files, group="filesystem")
63
+ ctx.register_tool(get_file_info, group="filesystem")
64
+ ctx.register_tool(list_allowed_directories, group="filesystem")
65
+
66
+
67
+ __install__ = install
@@ -0,0 +1,80 @@
1
+ from abc import ABCMeta, abstractmethod
2
+ from dataclasses import dataclass, fields, replace
3
+ from typing import Any
4
+
5
+
6
+ class BaseTool(metaclass=ABCMeta):
7
+ """Abstract base class"""
8
+
9
+ @abstractmethod
10
+ def __call__(self, **kwargs) -> Any:
11
+ """Executes the tool with the given arguments."""
12
+ ...
13
+
14
+ @abstractmethod
15
+ def to_params(
16
+ self,
17
+ ) -> Any:
18
+ raise NotImplementedError
19
+
20
+
21
+ @dataclass(kw_only=True, frozen=True)
22
+ class ToolResult:
23
+ """Represents the result of a tool execution."""
24
+
25
+ output: str | None = None
26
+ error: str | None = None
27
+ base64_image: str | None = None
28
+ system: str | None = None
29
+
30
+ def __bool__(self):
31
+ return any(getattr(self, field.name) for field in fields(self))
32
+
33
+ def __add__(self, other: "ToolResult"):
34
+ def combine_fields(field: str | None, other_field: str | None, concatenate: bool = True):
35
+ if field and other_field:
36
+ if concatenate:
37
+ return field + other_field
38
+ raise ValueError("Cannot combine tool results")
39
+ return field or other_field
40
+
41
+ return ToolResult(
42
+ output=combine_fields(self.output, other.output),
43
+ error=combine_fields(self.error, other.error),
44
+ base64_image=combine_fields(self.base64_image, other.base64_image, False),
45
+ system=combine_fields(self.system, other.system),
46
+ )
47
+
48
+ def replace(self, **kwargs):
49
+ """Returns a new ToolResult with the given fields replaced."""
50
+ return replace(self, **kwargs)
51
+
52
+ def to_tool_results(self) -> list[dict[str, Any]]:
53
+ text = ""
54
+ if self.output:
55
+ text += f"{self.output}\n"
56
+ if self.error:
57
+ text += f"stderr: {self.error}\n"
58
+ if self.system:
59
+ text += f"system: {self.system}\n"
60
+ ret = []
61
+ if text:
62
+ ret.append({"type": "text", "text": text})
63
+ if self.base64_image:
64
+ ret.append({"type": "image", "format": "png", "data": self.base64_image})
65
+ return ret
66
+
67
+
68
+ class CLIResult(ToolResult):
69
+ """A ToolResult that can be rendered as a CLI output."""
70
+
71
+
72
+ class ToolFailure(ToolResult):
73
+ """A ToolResult that represents a failure."""
74
+
75
+
76
+ class ToolError(Exception):
77
+ """Raised when a tool encounters an error."""
78
+
79
+ def __init__(self, message):
80
+ self.message = message
@@ -0,0 +1,185 @@
1
+ import asyncio
2
+ import os
3
+ import sys
4
+ from typing import Annotated, Any, Literal
5
+
6
+ from .base import BaseTool, CLIResult, ToolError, ToolResult
7
+
8
+
9
+ class _BashSession:
10
+ """A session of a bash shell."""
11
+
12
+ _started: bool
13
+ _process: asyncio.subprocess.Process
14
+
15
+ command: str = "/bin/bash"
16
+ _output_delay: float = 0.2 # seconds
17
+ _timeout: float = 120.0 # seconds
18
+ _sentinel: str = "<<exit>>"
19
+
20
+ def __init__(self):
21
+ self._started = False
22
+ self._timed_out = False
23
+
24
+ async def start(self):
25
+ if self._started:
26
+ return
27
+
28
+ self._process = await asyncio.create_subprocess_shell(
29
+ self.command,
30
+ preexec_fn=os.setsid,
31
+ shell=True,
32
+ bufsize=0,
33
+ stdin=asyncio.subprocess.PIPE,
34
+ stdout=asyncio.subprocess.PIPE,
35
+ stderr=asyncio.subprocess.PIPE,
36
+ )
37
+
38
+ self._started = True
39
+
40
+ def stop(self):
41
+ """Terminate the bash shell."""
42
+ if not self._started:
43
+ raise ToolError("Session has not started.")
44
+ if self._process.returncode is not None:
45
+ return
46
+ self._process.terminate()
47
+
48
+ async def run(self, command: str):
49
+ """Execute a command in the bash shell."""
50
+ if not self._started:
51
+ raise ToolError("Session has not started.")
52
+ if self._process.returncode is not None:
53
+ return ToolResult(
54
+ system="tool must be restarted",
55
+ error=f"bash has exited with returncode {self._process.returncode}",
56
+ )
57
+ if self._timed_out:
58
+ raise ToolError(
59
+ f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
60
+ )
61
+
62
+ # we know these are not None because we created the process with PIPEs
63
+ assert self._process.stdin
64
+ assert self._process.stdout
65
+ assert self._process.stderr
66
+
67
+ # send command to the process
68
+ self._process.stdin.write(command.encode() + f"; echo '{self._sentinel}'\n".encode())
69
+ await self._process.stdin.drain()
70
+
71
+ # read output from the process, until the sentinel is found
72
+ try:
73
+ async with asyncio.timeout(self._timeout):
74
+ while True:
75
+ await asyncio.sleep(self._output_delay)
76
+ # if we read directly from stdout/stderr, it will wait forever for
77
+ # EOF. use the StreamReader buffer directly instead.
78
+ output = self._process.stdout._buffer.decode() # pyright: ignore[reportAttributeAccessIssue]
79
+ if self._sentinel in output:
80
+ # strip the sentinel and break
81
+ output = output[: output.index(self._sentinel)]
82
+ break
83
+ except asyncio.TimeoutError:
84
+ self._timed_out = True
85
+ raise ToolError(
86
+ f"timed out: bash has not returned in {self._timeout} seconds and must be restarted",
87
+ ) from None
88
+
89
+ if output.endswith("\n"):
90
+ output = output[:-1]
91
+
92
+ error = self._process.stderr._buffer.decode() # pyright: ignore[reportAttributeAccessIssue]
93
+ if error.endswith("\n"):
94
+ error = error[:-1]
95
+
96
+ # clear the buffers so that the next output can be read correctly
97
+ self._process.stdout._buffer.clear() # pyright: ignore[reportAttributeAccessIssue]
98
+ self._process.stderr._buffer.clear() # pyright: ignore[reportAttributeAccessIssue]
99
+
100
+ return CLIResult(output=output, error=error)
101
+
102
+
103
+ class BashTool20250124(BaseTool):
104
+ """
105
+ A tool that allows the agent to run bash commands.
106
+ The tool parameters are defined by Anthropic and are not editable.
107
+ """
108
+
109
+ _session: _BashSession | None
110
+
111
+ api_type: Literal["bash_20250124"] = "bash_20250124"
112
+ name: Literal["bash"] = "bash"
113
+
114
+ def __init__(self):
115
+ self._session = None
116
+ super().__init__()
117
+
118
+ def to_params(self) -> Any:
119
+ return {
120
+ "type": self.api_type,
121
+ "name": self.name,
122
+ }
123
+
124
+ async def __call__(self, command: str | None = None, restart: bool = False, **kwargs):
125
+ if restart:
126
+ if self._session:
127
+ self._session.stop()
128
+ self._session = _BashSession()
129
+ await self._session.start()
130
+
131
+ return ToolResult(system="tool has been restarted.")
132
+
133
+ if self._session is None:
134
+ self._session = _BashSession()
135
+ await self._session.start()
136
+
137
+ if command is not None:
138
+ return await self._session.run(command)
139
+
140
+ raise ToolError("no command provided.")
141
+
142
+
143
+ class BashTool20241022(BashTool20250124):
144
+ api_type: Literal["bash_20250124"] = "bash_20250124" # pyright: ignore[reportIncompatibleVariableOverride]
145
+
146
+
147
+ g_tool = None
148
+
149
+
150
+ async def run_bash(
151
+ command: Annotated[str | None, "Command to run"],
152
+ restart: Annotated[bool, "Restart the bash session"] = False,
153
+ ) -> list[dict[str, Any]]:
154
+ """
155
+ A tool that allows the agent to run bash commands.
156
+ """
157
+ global g_tool
158
+ if g_tool is None:
159
+ g_tool = BashTool20241022()
160
+
161
+ result = await g_tool(command=command, restart=restart)
162
+ if isinstance(result, Exception):
163
+ raise result
164
+ else:
165
+ return result.to_tool_results()
166
+
167
+
168
+ async def open(target: Annotated[str, "URL or file path to open"]) -> list[dict[str, Any]]:
169
+ """
170
+ Open a URL or file using the appropriate system opener, uses `xdg-open` on Linux, `open` on macOS, and `start` on Windows.
171
+ """
172
+ target = target.strip()
173
+ if not target:
174
+ raise ValueError("No target specified")
175
+
176
+ platform = sys.platform
177
+
178
+ if platform == "darwin":
179
+ cmd = ["open", target]
180
+ elif platform == "win32":
181
+ cmd = ["cmd", "/c", "start", "", target]
182
+ else: # Linux and other Unix-like
183
+ cmd = ["xdg-open", target]
184
+
185
+ return await run_bash(command=" ".join(cmd))