llms-py 3.0.0b10__tar.gz → 3.0.2__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 (223) hide show
  1. {llms_py-3.0.0b10 → llms_py-3.0.2}/PKG-INFO +1 -1
  2. llms_py-3.0.0b10/llms/extensions/app/db_manager.py → llms_py-3.0.2/llms/db.py +170 -15
  3. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/__init__.py +95 -39
  4. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/db.py +16 -124
  5. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/ui/threadStore.mjs +20 -2
  6. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/__init__.py +37 -0
  7. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/gallery/__init__.py +15 -13
  8. llms_py-3.0.2/llms/extensions/gallery/db.py +243 -0
  9. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/gallery/ui/index.mjs +1 -1
  10. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/__init__.py +3 -1
  11. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/anthropic.py +7 -3
  12. llms_py-3.0.2/llms/extensions/providers/cerebras.py +37 -0
  13. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/chutes.py +1 -1
  14. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/google.py +131 -28
  15. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/nvidia.py +2 -2
  16. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/openai.py +2 -2
  17. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/providers/openrouter.py +4 -2
  18. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/system_prompts/ui/index.mjs +21 -26
  19. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/system_prompts/ui/prompts.json +5 -5
  20. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/llms.json +3 -0
  21. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/main.py +81 -34
  22. llms_py-3.0.2/llms/providers.json +1 -0
  23. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/ai.mjs +1 -1
  24. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/app.css +96 -3
  25. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/ctx.mjs +24 -1
  26. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/index.mjs +2 -0
  27. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/modules/chat/ChatBody.mjs +1 -0
  28. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/modules/chat/index.mjs +19 -1
  29. llms_py-3.0.2/llms/ui/modules/icons.mjs +46 -0
  30. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/modules/layout.mjs +28 -0
  31. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/modules/model-selector.mjs +0 -40
  32. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/utils.mjs +9 -1
  33. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/PKG-INFO +1 -1
  34. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/SOURCES.txt +4 -31
  35. {llms_py-3.0.0b10 → llms_py-3.0.2}/pyproject.toml +1 -1
  36. {llms_py-3.0.0b10 → llms_py-3.0.2}/setup.py +1 -1
  37. llms_py-3.0.2/tests/test_gemini_upload.py +71 -0
  38. llms_py-3.0.0b10/llms/__pycache__/__init__.cpython-312.pyc +0 -0
  39. llms_py-3.0.0b10/llms/__pycache__/__init__.cpython-313.pyc +0 -0
  40. llms_py-3.0.0b10/llms/__pycache__/__init__.cpython-314.pyc +0 -0
  41. llms_py-3.0.0b10/llms/__pycache__/__main__.cpython-312.pyc +0 -0
  42. llms_py-3.0.0b10/llms/__pycache__/__main__.cpython-314.pyc +0 -0
  43. llms_py-3.0.0b10/llms/__pycache__/llms.cpython-312.pyc +0 -0
  44. llms_py-3.0.0b10/llms/__pycache__/main.cpython-312.pyc +0 -0
  45. llms_py-3.0.0b10/llms/__pycache__/main.cpython-313.pyc +0 -0
  46. llms_py-3.0.0b10/llms/__pycache__/main.cpython-314.pyc +0 -0
  47. llms_py-3.0.0b10/llms/__pycache__/plugins.cpython-314.pyc +0 -0
  48. llms_py-3.0.0b10/llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  49. llms_py-3.0.0b10/llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  50. llms_py-3.0.0b10/llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  51. llms_py-3.0.0b10/llms/extensions/app/requests.json +0 -9073
  52. llms_py-3.0.0b10/llms/extensions/app/threads.json +0 -15290
  53. llms_py-3.0.0b10/llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  54. llms_py-3.0.0b10/llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +0 -344
  55. llms_py-3.0.0b10/llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +0 -9884
  56. llms_py-3.0.0b10/llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  57. llms_py-3.0.0b10/llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  58. llms_py-3.0.0b10/llms/extensions/gallery/db.py +0 -298
  59. llms_py-3.0.0b10/llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  60. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  61. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  62. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  63. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  64. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/nvidia.cpython-314.pyc +0 -0
  65. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/openai.cpython-314.pyc +0 -0
  66. llms_py-3.0.0b10/llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  67. llms_py-3.0.0b10/llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  68. llms_py-3.0.0b10/llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  69. llms_py-3.0.0b10/llms/providers.json +0 -1
  70. {llms_py-3.0.0b10 → llms_py-3.0.2}/LICENSE +0 -0
  71. {llms_py-3.0.0b10 → llms_py-3.0.2}/MANIFEST.in +0 -0
  72. {llms_py-3.0.0b10 → llms_py-3.0.2}/README.md +0 -0
  73. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/__init__.py +0 -0
  74. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/__main__.py +0 -0
  75. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/analytics/ui/index.mjs +0 -0
  76. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/README.md +0 -0
  77. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/ui/Recents.mjs +0 -0
  78. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/app/ui/index.mjs +0 -0
  79. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/CALCULATOR.md +0 -0
  80. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +0 -0
  81. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +0 -0
  82. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +0 -0
  83. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +0 -0
  84. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +0 -0
  85. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +0 -0
  86. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +0 -0
  87. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +0 -0
  88. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +0 -0
  89. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/doc/docs.css +0 -0
  90. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  91. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +0 -0
  92. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +0 -0
  93. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +0 -0
  94. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +0 -0
  95. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/mode/python/python.js +0 -0
  96. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/theme/dracula.css +0 -0
  97. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/codemirror/theme/mocha.css +0 -0
  98. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/core_tools/ui/index.mjs +0 -0
  99. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/gallery/README.md +0 -0
  100. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/README.md +0 -0
  101. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/__init__.py +0 -0
  102. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/README.md +0 -0
  103. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/auto-render.js +0 -0
  104. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/auto-render.min.js +0 -0
  105. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/auto-render.mjs +0 -0
  106. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/copy-tex.js +0 -0
  107. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/copy-tex.min.js +0 -0
  108. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/copy-tex.mjs +0 -0
  109. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mathtex-script-type.js +0 -0
  110. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +0 -0
  111. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +0 -0
  112. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mhchem.js +0 -0
  113. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mhchem.min.js +0 -0
  114. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/mhchem.mjs +0 -0
  115. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/render-a11y-string.js +0 -0
  116. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/render-a11y-string.min.js +0 -0
  117. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/contrib/render-a11y-string.mjs +0 -0
  118. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  119. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  120. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  121. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  122. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  123. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  124. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  125. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  126. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  127. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  128. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  129. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  130. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  131. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  132. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  133. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  134. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  135. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  136. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  137. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  138. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  139. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  140. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  141. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  142. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  143. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  144. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  145. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  146. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  147. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  148. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  149. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  150. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  151. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  152. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  153. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  154. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  155. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  156. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  157. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  158. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  159. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  160. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  161. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  162. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  163. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  164. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  165. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  166. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  167. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  168. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  169. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  170. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  171. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  172. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  173. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  174. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  175. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  176. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  177. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  178. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/index.mjs +0 -0
  179. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex-swap.css +0 -0
  180. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex-swap.min.css +0 -0
  181. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.css +0 -0
  182. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.js +0 -0
  183. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.min.css +0 -0
  184. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.min.js +0 -0
  185. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.min.mjs +0 -0
  186. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/katex/ui/katex.mjs +0 -0
  187. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/system_prompts/README.md +0 -0
  188. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/system_prompts/__init__.py +0 -0
  189. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/tools/__init__.py +0 -0
  190. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/extensions/tools/ui/index.mjs +0 -0
  191. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/index.html +0 -0
  192. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/providers-extra.json +0 -0
  193. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/App.mjs +0 -0
  194. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/fav.svg +0 -0
  195. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/chart.js +0 -0
  196. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/charts.mjs +0 -0
  197. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/color.js +0 -0
  198. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/highlight.min.mjs +0 -0
  199. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/idb.min.mjs +0 -0
  200. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/marked.min.mjs +0 -0
  201. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/servicestack-client.mjs +0 -0
  202. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/servicestack-vue.mjs +0 -0
  203. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/vue-router.min.mjs +0 -0
  204. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/vue.min.mjs +0 -0
  205. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/lib/vue.mjs +0 -0
  206. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/markdown.mjs +0 -0
  207. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/modules/chat/SettingsDialog.mjs +0 -0
  208. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/tailwind.input.css +0 -0
  209. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms/ui/typography.css +0 -0
  210. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/dependency_links.txt +0 -0
  211. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/entry_points.txt +0 -0
  212. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/not-zip-safe +0 -0
  213. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/requires.txt +0 -0
  214. {llms_py-3.0.0b10 → llms_py-3.0.2}/llms_py.egg-info/top_level.txt +0 -0
  215. {llms_py-3.0.0b10 → llms_py-3.0.2}/requirements.txt +0 -0
  216. {llms_py-3.0.0b10 → llms_py-3.0.2}/setup.cfg +0 -0
  217. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_async.py +0 -0
  218. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_config.py +0 -0
  219. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_extensions.py +0 -0
  220. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_integration.py +0 -0
  221. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_provider_checks.py +0 -0
  222. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_provider_config.py +0 -0
  223. {llms_py-3.0.0b10 → llms_py-3.0.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: llms-py
3
- Version: 3.0.0b10
3
+ Version: 3.0.2
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
@@ -1,11 +1,17 @@
1
1
  import json
2
2
  import sqlite3
3
+ import threading
3
4
  from queue import Empty, Queue
4
5
  from threading import Event, Thread
5
6
 
7
+ POOL = True
8
+
6
9
 
7
10
  def create_reader_connection(db_path):
8
- conn = sqlite3.connect(db_path, timeout=1.0) # Lower - reads should be fast
11
+ # isolation_level=None leaves the connection in autocommit mode
12
+ conn = sqlite3.connect(
13
+ db_path, timeout=1.0, check_same_thread=False, isolation_level=None
14
+ ) # Lower - reads should be fast
9
15
  conn.execute("PRAGMA query_only=1") # Read-only optimization
10
16
  return conn
11
17
 
@@ -33,7 +39,7 @@ def writer_thread(ctx, db_path, task_queue, stop_event):
33
39
  sql, args, callback = task # Optional callback for results
34
40
 
35
41
  try:
36
- ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql)
42
+ ctx.dbg("SQL>" + ("\n" if "\n" in sql else " ") + sql + ("\n" if args else "") + str(args))
37
43
  cursor = conn.execute(sql, args)
38
44
  conn.commit()
39
45
  ctx.dbg(f"lastrowid {cursor.lastrowid}, rowcount {cursor.rowcount}")
@@ -53,17 +59,79 @@ def writer_thread(ctx, db_path, task_queue, stop_event):
53
59
  conn.close()
54
60
 
55
61
 
62
+ def to_dto(ctx, row, json_columns):
63
+ # as=column -> [0,1,2]
64
+ if not isinstance(row, dict):
65
+ return row
66
+
67
+ to = {}
68
+ for k, v in row.items():
69
+ if k in json_columns and v is not None and isinstance(v, str):
70
+ try:
71
+ to[k] = json.loads(v)
72
+ except Exception as e:
73
+ print(f"Failed to parse JSON for {k}: {v} ({type(v)})", e)
74
+ to[k] = v
75
+ else:
76
+ to[k] = v
77
+ return to
78
+
79
+
80
+ def valid_columns(all_columns, fields):
81
+ if fields:
82
+ if not isinstance(fields, list):
83
+ fields = fields.split(",")
84
+ cols = []
85
+ for k in fields:
86
+ k = k.strip()
87
+ if k in all_columns:
88
+ cols.append(k)
89
+ return cols
90
+ return []
91
+
92
+
93
+ def table_columns(all_columns, fields):
94
+ cols = valid_columns(all_columns, fields)
95
+ return ", ".join(cols) if len(cols) > 0 else ", ".join(all_columns)
96
+
97
+
98
+ def select_columns(all_columns, fields, select=None):
99
+ columns = table_columns(all_columns, fields)
100
+ if select == "distinct":
101
+ return f"SELECT DISTINCT {columns}"
102
+ return f"SELECT {columns}"
103
+
104
+
105
+ def order_by(all_columns, sort):
106
+ cols = []
107
+ for k in sort.split(","):
108
+ k = k.strip()
109
+ by = ""
110
+ if k[0] == "-":
111
+ by = " DESC"
112
+ k = k[1:]
113
+ if k in all_columns:
114
+ cols.append(f"{k}{by}")
115
+ return f"ORDER BY {', '.join(cols)} " if len(cols) > 0 else ""
116
+
117
+
56
118
  class DbManager:
57
- def __init__(self, ctx, db_path):
119
+ def __init__(self, ctx, db_path, clone=None):
58
120
  if db_path is None:
59
121
  raise ValueError("db_path is required")
60
122
  self.ctx = ctx
61
123
  self.db_path = db_path
62
- self.task_queue = Queue()
63
- self.stop_event = Event()
64
- self.writer_thread = Thread(target=writer_thread, args=(ctx, db_path, self.task_queue, self.stop_event))
65
- self.writer_thread.start()
66
124
  self.read_only_pool = Queue()
125
+ if not clone:
126
+ self.task_queue = Queue()
127
+ self.stop_event = Event()
128
+ self.writer_thread = Thread(target=writer_thread, args=(ctx, db_path, self.task_queue, self.stop_event))
129
+ self.writer_thread.start()
130
+ else:
131
+ # share singleton writer thread in clones
132
+ self.task_queue = clone.task_queue
133
+ self.stop_event = clone.stop_event
134
+ self.writer_thread = clone.writer_thread
67
135
 
68
136
  def create_reader_connection(self):
69
137
  return create_reader_connection(self.db_path)
@@ -72,11 +140,21 @@ class DbManager:
72
140
  return create_writer_connection(self.db_path)
73
141
 
74
142
  def resolve_connection(self):
75
- try:
76
- return self.read_only_pool.get_nowait()
77
- except Empty:
143
+ if POOL:
144
+ try:
145
+ return self.read_only_pool.get_nowait()
146
+ except Empty:
147
+ return self.create_reader_connection()
148
+ else:
78
149
  return self.create_reader_connection()
79
150
 
151
+ def release_connection(self, conn):
152
+ if POOL:
153
+ conn.rollback()
154
+ self.read_only_pool.put(conn)
155
+ else:
156
+ conn.close()
157
+
80
158
  def write(self, query, args=None, callback=None):
81
159
  """
82
160
  Execute a write operation asynchronously.
@@ -101,6 +179,9 @@ class DbManager:
101
179
  return connection.execute(sql, parameters or ())
102
180
 
103
181
  def all(self, sql, parameters=None, connection=None):
182
+ """
183
+ Execute a query and return all rows as a list of dictionaries.
184
+ """
104
185
  conn = self.resolve_connection() if connection is None else connection
105
186
 
106
187
  try:
@@ -112,9 +193,12 @@ class DbManager:
112
193
  finally:
113
194
  if connection is None:
114
195
  conn.row_factory = None
115
- self.read_only_pool.put(conn)
196
+ self.release_connection(conn)
116
197
 
117
198
  def one(self, sql, parameters=None, connection=None):
199
+ """
200
+ Execute a query and return the first row as a dictionary.
201
+ """
118
202
  conn = self.resolve_connection() if connection is None else connection
119
203
 
120
204
  try:
@@ -126,9 +210,12 @@ class DbManager:
126
210
  finally:
127
211
  if connection is None:
128
212
  conn.row_factory = None
129
- self.read_only_pool.put(conn)
213
+ self.release_connection(conn)
130
214
 
131
215
  def scalar(self, sql, parameters=None, connection=None):
216
+ """
217
+ Execute a scalar query and return the first column of the first row.
218
+ """
132
219
  conn = self.resolve_connection() if connection is None else connection
133
220
 
134
221
  try:
@@ -140,7 +227,7 @@ class DbManager:
140
227
  finally:
141
228
  if connection is None:
142
229
  conn.row_factory = None
143
- self.read_only_pool.put(conn)
230
+ self.release_connection(conn)
144
231
 
145
232
  def column(self, sql, parameters=None, connection=None):
146
233
  """
@@ -154,7 +241,7 @@ class DbManager:
154
241
  return [row[0] for row in cursor.fetchall()]
155
242
  finally:
156
243
  if connection is None:
157
- self.read_only_pool.put(conn)
244
+ self.release_connection(conn)
158
245
 
159
246
  def dict(self, sql, parameters=None, connection=None):
160
247
  """
@@ -171,7 +258,7 @@ class DbManager:
171
258
  finally:
172
259
  if connection is None:
173
260
  conn.row_factory = None
174
- self.read_only_pool.put(conn)
261
+ self.release_connection(conn)
175
262
 
176
263
  # Helper to safely dump JSON if value exists
177
264
  def value(self, val):
@@ -181,6 +268,74 @@ class DbManager:
181
268
  return json.dumps(val)
182
269
  return val
183
270
 
271
+ def insert(self, table, columns, info, callback=None):
272
+ if not info:
273
+ raise Exception("info is required")
274
+
275
+ args = {}
276
+ known_columns = columns.keys()
277
+ for k, val in info.items():
278
+ if k in known_columns and k != "id":
279
+ args[k] = self.value(val)
280
+
281
+ insert_keys = list(args.keys())
282
+ insert_body = ", ".join(insert_keys)
283
+ insert_values = ", ".join(["?" for _ in insert_keys])
284
+
285
+ sql = f"INSERT INTO {table} ({insert_body}) VALUES ({insert_values})"
286
+
287
+ self.write(sql, tuple(args[k] for k in insert_keys), callback)
288
+
289
+ async def insert_async(self, table, columns, info):
290
+ event = threading.Event()
291
+
292
+ ret = [None]
293
+
294
+ def cb(lastrowid, rowcount, error=None):
295
+ nonlocal ret
296
+ if error:
297
+ raise error
298
+ ret[0] = lastrowid
299
+ event.set()
300
+
301
+ self.insert(table, columns, info, cb)
302
+ event.wait()
303
+ return ret[0]
304
+
305
+ def update(self, table, columns, info, callback=None):
306
+ if not info:
307
+ raise Exception("info is required")
308
+
309
+ args = {}
310
+ known_columns = columns.keys()
311
+ for k, val in info.items():
312
+ if k in known_columns and k != "id":
313
+ args[k] = self.value(val)
314
+
315
+ update_keys = list(args.keys())
316
+ update_body = ", ".join([f"{k} = :{k}" for k in update_keys])
317
+
318
+ args["id"] = info["id"]
319
+ sql = f"UPDATE {table} SET {update_body} WHERE id = :id"
320
+
321
+ self.write(sql, args, callback)
322
+
323
+ async def update_async(self, table, columns, info):
324
+ event = threading.Event()
325
+
326
+ ret = [None]
327
+
328
+ def cb(lastrowid, rowcount, error=None):
329
+ nonlocal ret
330
+ if error:
331
+ raise error
332
+ ret[0] = rowcount
333
+ event.set()
334
+
335
+ self.update(table, columns, info, cb)
336
+ event.wait()
337
+ return ret[0]
338
+
184
339
  def close(self):
185
340
  self.ctx.dbg("Closing database")
186
341
  self.stop_event.set()
@@ -7,13 +7,9 @@ from typing import Any
7
7
 
8
8
  from aiohttp import web
9
9
 
10
- g_db = None
10
+ from .db import AppDB
11
11
 
12
- try:
13
- from llms.extensions.app.db import AppDB
14
- except ImportError as e:
15
- print(f"Failed to import AppDB: {e}")
16
- AppDB = None
12
+ g_db = None
17
13
 
18
14
 
19
15
  def install(ctx):
@@ -25,16 +21,6 @@ def install(ctx):
25
21
  g_db = AppDB(ctx, db_path)
26
22
  ctx.register_shutdown_handler(g_db.close)
27
23
 
28
- threads_json = "/home/mythz/src/ServiceStack/llms/llms/extensions/app/threads.json"
29
- requests_json = "/home/mythz/src/ServiceStack/llms/llms/extensions/app/requests.json"
30
- with open(threads_json) as f:
31
- threads = json.load(f)["threads"]
32
- threads.reverse()
33
- with open(requests_json) as f:
34
- requests = json.load(f)["requests"]
35
- requests.reverse()
36
- # g_db.import_db(threads, requests)
37
-
38
24
  except Exception as e:
39
25
  ctx.err("Failed to init AppDB", e)
40
26
  return g_db
@@ -42,28 +28,49 @@ def install(ctx):
42
28
  if not get_db():
43
29
  return
44
30
 
45
- def to_dto(row, json_columns):
46
- # as=column -> [0,1,2]
47
- if not isinstance(row, dict):
48
- return row
49
-
50
- to = {}
51
- for k, v in row.items():
52
- if k in json_columns and v is not None and isinstance(v, str):
53
- try:
54
- to[k] = json.loads(v)
55
- except Exception as e:
56
- ctx.err(f"Failed to parse JSON for {k}: {v} ({type(v)})", e)
57
- to[k] = v
58
- else:
59
- to[k] = v
60
- return to
31
+ thread_fields = [
32
+ "id",
33
+ "threadId",
34
+ "createdAt",
35
+ "updatedAt",
36
+ "title",
37
+ "model",
38
+ "modelInfo",
39
+ "modalities",
40
+ "messages",
41
+ "args",
42
+ "cost",
43
+ "inputTokens",
44
+ "outputTokens",
45
+ "stats",
46
+ "provider",
47
+ "providerModel",
48
+ "publishedAt",
49
+ "startedAt",
50
+ "completedAt",
51
+ "metadata",
52
+ "error",
53
+ "ref",
54
+ ]
61
55
 
62
56
  def thread_dto(row):
63
- return row and to_dto(row, ["messages", "modalities", "args", "modelInfo", "stats", "metadata"])
57
+ return row and g_db.to_dto(
58
+ row,
59
+ [
60
+ "messages",
61
+ "tools",
62
+ "toolHistory",
63
+ "modalities",
64
+ "args",
65
+ "modelInfo",
66
+ "stats",
67
+ "metadata",
68
+ "providerResponse",
69
+ ],
70
+ )
64
71
 
65
72
  def request_dto(row):
66
- return row and to_dto(row, ["usage"])
73
+ return row and g_db.to_dto(row, ["usage"])
67
74
 
68
75
  def prompt_to_title(prompt):
69
76
  return prompt[:100] + ("..." if len(prompt) > 100 else "") if prompt else None
@@ -77,7 +84,10 @@ def install(ctx):
77
84
  return messages
78
85
 
79
86
  async def query_threads(request):
80
- rows = g_db.query_threads(request.query, user=ctx.get_username(request))
87
+ query = request.query.copy()
88
+ if "fields" not in query:
89
+ query["fields"] = thread_fields
90
+ rows = g_db.query_threads(query, user=ctx.get_username(request))
81
91
  dtos = [thread_dto(row) for row in rows]
82
92
  return web.json_response(dtos)
83
93
 
@@ -91,6 +101,13 @@ def install(ctx):
91
101
 
92
102
  ctx.add_post("threads", create_thread)
93
103
 
104
+ async def get_thread(request):
105
+ id = request.match_info["id"]
106
+ row = g_db.get_thread(id, user=ctx.get_username(request))
107
+ return web.json_response(thread_dto(row) if row else "")
108
+
109
+ ctx.add_get("threads/{id}", get_thread)
110
+
94
111
  async def update_thread(request):
95
112
  thread = await request.json()
96
113
  id = request.match_info["id"]
@@ -129,8 +146,10 @@ def install(ctx):
129
146
  if not thread:
130
147
  raise Exception("Thread not found")
131
148
 
149
+ tools = chat.get("tools", thread.get("tools", []))
132
150
  update_thread = {
133
151
  "messages": messages,
152
+ "tools": tools,
134
153
  "startedAt": datetime.now(),
135
154
  "completedAt": None,
136
155
  "error": None,
@@ -174,24 +193,28 @@ def install(ctx):
174
193
  if not thread:
175
194
  raise Exception("Thread not found")
176
195
 
177
- metadata = thread.get("metadata", {})
196
+ metadata = thread.get("metadata") or {}
178
197
  chat = {
179
198
  "model": thread.get("model"),
180
199
  "messages": thread.get("messages"),
181
200
  "modalities": thread.get("modalities"),
182
201
  "systemPrompt": thread.get("systemPrompt"),
202
+ "tools": thread.get("tools"), # tools request
183
203
  "metadata": metadata,
184
204
  }
185
- for k, v in thread.get("args", {}).items():
205
+ args = thread.get("args") or {}
206
+ for k, v in args.items():
186
207
  if k in ctx.request_args:
187
208
  chat[k] = v
188
209
 
210
+ ctx.dbg("CHAT\n" + json.dumps(chat, indent=2))
211
+
189
212
  context = {
190
213
  "chat": chat,
191
214
  "user": user,
192
215
  "threadId": id,
193
216
  "metadata": metadata,
194
- "tools": metadata.get("tools", "all"),
217
+ "tools": metadata.get("tools", "all"), # only tools: all|none|<tool1>,<tool2>,...
195
218
  }
196
219
 
197
220
  # execute chat in background thread
@@ -305,6 +328,7 @@ def install(ctx):
305
328
  metadata = chat.get("metadata", {})
306
329
  model = chat.get("model", None)
307
330
  messages = timestamp_messages(chat.get("messages", []))
331
+ tools = chat.get("tools", [])
308
332
  title = context.get("title") or prompt_to_title(ctx.last_user_prompt(chat) if chat else None)
309
333
  started_at = context.get("startedAt")
310
334
  if not started_at:
@@ -317,6 +341,7 @@ def install(ctx):
317
341
  "modelInfo": model_info,
318
342
  "title": title,
319
343
  "messages": messages,
344
+ "tools": tools,
320
345
  "systemPrompt": ctx.chat_to_system_prompt(chat),
321
346
  "modalities": chat.get("modalities", ["text"]),
322
347
  "startedAt": started_at,
@@ -331,6 +356,7 @@ def install(ctx):
331
356
  "modelInfo": model_info,
332
357
  "startedAt": started_at,
333
358
  "messages": messages,
359
+ "tools": tools,
334
360
  "completedAt": None,
335
361
  "error": None,
336
362
  "metadata": metadata,
@@ -365,6 +391,29 @@ def install(ctx):
365
391
 
366
392
  ctx.register_chat_tool_filter(tool_request)
367
393
 
394
+ def truncate_long_strings(obj, max_length=10000):
395
+ """
396
+ Recursively traverse a dictionary/list structure and replace
397
+ string values longer than max_length with their length indicator.
398
+
399
+ Args:
400
+ obj: The object to process (dict, list, or other value)
401
+ max_length: Maximum string length before truncation (default 10000)
402
+
403
+ Returns:
404
+ A new object with long strings replaced by "({length})"
405
+ """
406
+ if isinstance(obj, dict):
407
+ return {key: truncate_long_strings(value, max_length) for key, value in obj.items()}
408
+ elif isinstance(obj, list):
409
+ return [truncate_long_strings(item, max_length) for item in obj]
410
+ elif isinstance(obj, str):
411
+ if len(obj) > max_length:
412
+ return f"({len(obj)})"
413
+ return obj
414
+ else:
415
+ return obj
416
+
368
417
  async def chat_response(openai_response, context):
369
418
  ctx.dbg("create_response")
370
419
  o = openai_response
@@ -416,7 +465,6 @@ def install(ctx):
416
465
  "totalTokens": total_tokens,
417
466
  "usage": usage,
418
467
  "completedAt": completed_at,
419
- "toolHistory": o.get("tool_history", None),
420
468
  "ref": o.get("id", None),
421
469
  }
422
470
  tasks.append(g_db.create_request_async(request, user=user))
@@ -446,15 +494,23 @@ def install(ctx):
446
494
  }
447
495
  messages.append(assistant_message)
448
496
 
497
+ tools = chat.get("tools", [])
449
498
  update_thread = {
450
499
  "model": model,
451
500
  "providerModel": o.get("model"),
452
501
  "modelInfo": model_info,
453
502
  "messages": messages,
503
+ "tools": tools,
454
504
  "completedAt": completed_at,
455
505
  }
506
+ tool_history = o.get("tool_history", None)
507
+ if tool_history:
508
+ update_thread["toolHistory"] = tool_history
456
509
  if "error" in metadata:
457
510
  update_thread["error"] = metadata["error"]
511
+ provider_response = context.get("providerResponse", None)
512
+ if provider_response:
513
+ update_thread["providerResponse"] = truncate_long_strings(provider_response)
458
514
  tasks.append(g_db.update_thread_async(thread_id, update_thread, user=user))
459
515
  else:
460
516
  ctx.dbg("Missing thread_id")