llms-py 3.0.0b6__py3-none-any.whl → 3.0.0b8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. llms/__pycache__/main.cpython-314.pyc +0 -0
  2. llms/{ui/modules/analytics.mjs → extensions/analytics/ui/index.mjs} +55 -164
  3. llms/extensions/app/__init__.py +519 -0
  4. llms/extensions/app/__pycache__/__init__.cpython-314.pyc +0 -0
  5. llms/extensions/app/__pycache__/db.cpython-314.pyc +0 -0
  6. llms/extensions/app/__pycache__/db_manager.cpython-314.pyc +0 -0
  7. llms/extensions/app/db.py +641 -0
  8. llms/extensions/app/db_manager.py +195 -0
  9. llms/extensions/app/requests.json +9073 -0
  10. llms/extensions/app/threads.json +15290 -0
  11. llms/{ui/modules/threads → extensions/app/ui}/Recents.mjs +82 -55
  12. llms/{ui/modules/threads → extensions/app/ui}/index.mjs +83 -20
  13. llms/extensions/app/ui/threadStore.mjs +407 -0
  14. llms/extensions/core_tools/__init__.py +598 -0
  15. llms/extensions/core_tools/__pycache__/__init__.cpython-314.pyc +0 -0
  16. llms/extensions/core_tools/ui/codemirror/addon/edit/closebrackets.js +201 -0
  17. llms/extensions/core_tools/ui/codemirror/addon/edit/closetag.js +185 -0
  18. llms/extensions/core_tools/ui/codemirror/addon/edit/continuelist.js +101 -0
  19. llms/extensions/core_tools/ui/codemirror/addon/edit/matchbrackets.js +160 -0
  20. llms/extensions/core_tools/ui/codemirror/addon/edit/matchtags.js +66 -0
  21. llms/extensions/core_tools/ui/codemirror/addon/edit/trailingspace.js +27 -0
  22. llms/extensions/core_tools/ui/codemirror/addon/selection/active-line.js +72 -0
  23. llms/extensions/core_tools/ui/codemirror/addon/selection/mark-selection.js +119 -0
  24. llms/extensions/core_tools/ui/codemirror/addon/selection/selection-pointer.js +98 -0
  25. llms/extensions/core_tools/ui/codemirror/doc/docs.css +225 -0
  26. llms/extensions/core_tools/ui/codemirror/doc/source_sans.woff +0 -0
  27. llms/extensions/core_tools/ui/codemirror/lib/codemirror.css +344 -0
  28. llms/extensions/core_tools/ui/codemirror/lib/codemirror.js +9884 -0
  29. llms/extensions/core_tools/ui/codemirror/mode/clike/clike.js +942 -0
  30. llms/extensions/core_tools/ui/codemirror/mode/javascript/index.html +118 -0
  31. llms/extensions/core_tools/ui/codemirror/mode/javascript/javascript.js +962 -0
  32. llms/extensions/core_tools/ui/codemirror/mode/javascript/typescript.html +62 -0
  33. llms/extensions/core_tools/ui/codemirror/mode/python/python.js +402 -0
  34. llms/extensions/core_tools/ui/codemirror/theme/dracula.css +40 -0
  35. llms/extensions/core_tools/ui/codemirror/theme/mocha.css +135 -0
  36. llms/extensions/core_tools/ui/index.mjs +650 -0
  37. llms/extensions/gallery/__init__.py +61 -0
  38. llms/extensions/gallery/__pycache__/__init__.cpython-314.pyc +0 -0
  39. llms/extensions/gallery/__pycache__/db.cpython-314.pyc +0 -0
  40. llms/extensions/gallery/db.py +298 -0
  41. llms/extensions/gallery/ui/index.mjs +481 -0
  42. llms/extensions/katex/__init__.py +6 -0
  43. llms/extensions/katex/__pycache__/__init__.cpython-314.pyc +0 -0
  44. llms/extensions/katex/ui/README.md +125 -0
  45. llms/extensions/katex/ui/contrib/auto-render.js +338 -0
  46. llms/extensions/katex/ui/contrib/auto-render.min.js +1 -0
  47. llms/extensions/katex/ui/contrib/auto-render.mjs +244 -0
  48. llms/extensions/katex/ui/contrib/copy-tex.js +127 -0
  49. llms/extensions/katex/ui/contrib/copy-tex.min.js +1 -0
  50. llms/extensions/katex/ui/contrib/copy-tex.mjs +105 -0
  51. llms/extensions/katex/ui/contrib/mathtex-script-type.js +109 -0
  52. llms/extensions/katex/ui/contrib/mathtex-script-type.min.js +1 -0
  53. llms/extensions/katex/ui/contrib/mathtex-script-type.mjs +24 -0
  54. llms/extensions/katex/ui/contrib/mhchem.js +3213 -0
  55. llms/extensions/katex/ui/contrib/mhchem.min.js +1 -0
  56. llms/extensions/katex/ui/contrib/mhchem.mjs +3109 -0
  57. llms/extensions/katex/ui/contrib/render-a11y-string.js +887 -0
  58. llms/extensions/katex/ui/contrib/render-a11y-string.min.js +1 -0
  59. llms/extensions/katex/ui/contrib/render-a11y-string.mjs +800 -0
  60. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.ttf +0 -0
  61. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff +0 -0
  62. llms/extensions/katex/ui/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  63. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  64. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  65. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  66. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  67. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  68. llms/extensions/katex/ui/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  69. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  70. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  71. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  72. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  73. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  74. llms/extensions/katex/ui/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  75. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.ttf +0 -0
  76. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff +0 -0
  77. llms/extensions/katex/ui/fonts/KaTeX_Main-Bold.woff2 +0 -0
  78. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  79. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  80. llms/extensions/katex/ui/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  81. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.ttf +0 -0
  82. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff +0 -0
  83. llms/extensions/katex/ui/fonts/KaTeX_Main-Italic.woff2 +0 -0
  84. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.ttf +0 -0
  85. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff +0 -0
  86. llms/extensions/katex/ui/fonts/KaTeX_Main-Regular.woff2 +0 -0
  87. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  88. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  89. llms/extensions/katex/ui/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  90. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.ttf +0 -0
  91. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff +0 -0
  92. llms/extensions/katex/ui/fonts/KaTeX_Math-Italic.woff2 +0 -0
  93. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  94. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  95. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  96. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  97. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  98. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  99. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  100. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  101. llms/extensions/katex/ui/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  102. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.ttf +0 -0
  103. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff +0 -0
  104. llms/extensions/katex/ui/fonts/KaTeX_Script-Regular.woff2 +0 -0
  105. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.ttf +0 -0
  106. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff +0 -0
  107. llms/extensions/katex/ui/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  108. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.ttf +0 -0
  109. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff +0 -0
  110. llms/extensions/katex/ui/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  111. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.ttf +0 -0
  112. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff +0 -0
  113. llms/extensions/katex/ui/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  114. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.ttf +0 -0
  115. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff +0 -0
  116. llms/extensions/katex/ui/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  117. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  118. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  119. llms/extensions/katex/ui/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  120. llms/extensions/katex/ui/index.mjs +92 -0
  121. llms/extensions/katex/ui/katex-swap.css +1230 -0
  122. llms/extensions/katex/ui/katex-swap.min.css +1 -0
  123. llms/extensions/katex/ui/katex.css +1230 -0
  124. llms/extensions/katex/ui/katex.js +19080 -0
  125. llms/extensions/katex/ui/katex.min.css +1 -0
  126. llms/extensions/katex/ui/katex.min.js +1 -0
  127. llms/extensions/katex/ui/katex.min.mjs +1 -0
  128. llms/extensions/katex/ui/katex.mjs +18547 -0
  129. llms/extensions/providers/__init__.py +18 -0
  130. llms/extensions/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  131. llms/extensions/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  132. llms/extensions/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  133. llms/extensions/providers/__pycache__/google.cpython-314.pyc +0 -0
  134. llms/{providers → extensions/providers}/__pycache__/nvidia.cpython-314.pyc +0 -0
  135. llms/{providers → extensions/providers}/__pycache__/openai.cpython-314.pyc +0 -0
  136. llms/extensions/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  137. llms/{providers → extensions/providers}/anthropic.py +45 -5
  138. llms/{providers → extensions/providers}/chutes.py +21 -18
  139. llms/{providers → extensions/providers}/google.py +99 -27
  140. llms/{providers → extensions/providers}/nvidia.py +6 -8
  141. llms/{providers → extensions/providers}/openai.py +3 -6
  142. llms/{providers → extensions/providers}/openrouter.py +12 -10
  143. llms/extensions/system_prompts/__init__.py +45 -0
  144. llms/extensions/system_prompts/__pycache__/__init__.cpython-314.pyc +0 -0
  145. llms/extensions/system_prompts/ui/index.mjs +285 -0
  146. llms/extensions/system_prompts/ui/prompts.json +1067 -0
  147. llms/extensions/tools/__init__.py +5 -0
  148. llms/extensions/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  149. llms/{ui/modules/tools.mjs → extensions/tools/ui/index.mjs} +12 -10
  150. llms/index.html +26 -38
  151. llms/llms.json +20 -1
  152. llms/main.py +845 -245
  153. llms/providers-extra.json +0 -32
  154. llms/ui/App.mjs +18 -20
  155. llms/ui/ai.mjs +38 -15
  156. llms/ui/app.css +1440 -59
  157. llms/ui/ctx.mjs +154 -18
  158. llms/ui/index.mjs +17 -14
  159. llms/ui/lib/vue.min.mjs +10 -9
  160. llms/ui/lib/vue.mjs +1796 -1635
  161. llms/ui/markdown.mjs +4 -2
  162. llms/ui/modules/chat/ChatBody.mjs +101 -334
  163. llms/ui/modules/chat/HomeTools.mjs +12 -0
  164. llms/ui/modules/chat/SettingsDialog.mjs +1 -1
  165. llms/ui/modules/chat/index.mjs +351 -314
  166. llms/ui/modules/layout.mjs +2 -26
  167. llms/ui/modules/model-selector.mjs +3 -3
  168. llms/ui/tailwind.input.css +35 -1
  169. llms/ui/utils.mjs +33 -3
  170. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/METADATA +1 -1
  171. llms_py-3.0.0b8.dist-info/RECORD +198 -0
  172. llms/providers/__pycache__/anthropic.cpython-314.pyc +0 -0
  173. llms/providers/__pycache__/chutes.cpython-314.pyc +0 -0
  174. llms/providers/__pycache__/google.cpython-314.pyc +0 -0
  175. llms/providers/__pycache__/openrouter.cpython-314.pyc +0 -0
  176. llms/ui/modules/threads/threadStore.mjs +0 -586
  177. llms_py-3.0.0b6.dist-info/RECORD +0 -66
  178. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/WHEEL +0 -0
  179. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/entry_points.txt +0 -0
  180. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/licenses/LICENSE +0 -0
  181. {llms_py-3.0.0b6.dist-info → llms_py-3.0.0b8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,18 @@
1
+ from .anthropic import install_anthropic
2
+ from .chutes import install_chutes
3
+ from .google import install_google
4
+ from .nvidia import install_nvidia
5
+ from .openai import install_openai
6
+ from .openrouter import install_openrouter
7
+
8
+
9
+ def install(ctx):
10
+ install_anthropic(ctx)
11
+ install_chutes(ctx)
12
+ install_google(ctx)
13
+ install_openai(ctx)
14
+ install_openrouter(ctx)
15
+ install_nvidia(ctx)
16
+
17
+
18
+ __install__ = install
@@ -4,7 +4,7 @@ import time
4
4
  import aiohttp
5
5
 
6
6
 
7
- def install(ctx):
7
+ def install_anthropic(ctx):
8
8
  from llms.main import OpenAiCompatible
9
9
 
10
10
  class AnthropicProvider(OpenAiCompatible):
@@ -58,6 +58,23 @@ def install(ctx):
58
58
  if message.get("role") == "system":
59
59
  continue
60
60
 
61
+ if message.get("role") == "tool":
62
+ # Convert OpenAI tool response to Anthropic tool_result
63
+ tool_call_id = message.get("tool_call_id")
64
+ content = ctx.to_content(message.get("content", ""))
65
+ if not isinstance(content, (str, list)):
66
+ content = str(content)
67
+
68
+ tool_result = {"type": "tool_result", "tool_use_id": tool_call_id, "content": content}
69
+
70
+ # Anthropic requires tool results to be in a user message
71
+ # Check if the last message was a user message, if so append to it
72
+ if anthropic_request["messages"] and anthropic_request["messages"][-1]["role"] == "user":
73
+ anthropic_request["messages"][-1]["content"].append(tool_result)
74
+ else:
75
+ anthropic_request["messages"].append({"role": "user", "content": [tool_result]})
76
+ continue
77
+
61
78
  anthropic_message = {"role": message.get("role"), "content": []}
62
79
 
63
80
  content = message.get("content", "")
@@ -106,7 +123,18 @@ def install(ctx):
106
123
  if "stream" in chat:
107
124
  anthropic_request["stream"] = chat["stream"]
108
125
  if "tools" in chat:
109
- anthropic_request["tools"] = chat["tools"]
126
+ anthropic_tools = []
127
+ for tool in chat["tools"]:
128
+ if tool.get("type") == "function":
129
+ function = tool.get("function", {})
130
+ anthropic_tool = {
131
+ "name": function.get("name"),
132
+ "description": function.get("description"),
133
+ "input_schema": function.get("parameters"),
134
+ }
135
+ anthropic_tools.append(anthropic_tool)
136
+ if anthropic_tools:
137
+ anthropic_request["tools"] = anthropic_tools
110
138
  if "tool_choice" in chat:
111
139
  anthropic_request["tool_choice"] = chat["tool_choice"]
112
140
 
@@ -138,6 +166,7 @@ def install(ctx):
138
166
  # Transform content blocks to message content
139
167
  content_parts = []
140
168
  thinking_parts = []
169
+ tool_calls = []
141
170
 
142
171
  for block in response.get("content", []):
143
172
  if block.get("type") == "text":
@@ -145,6 +174,16 @@ def install(ctx):
145
174
  elif block.get("type") == "thinking":
146
175
  # Store thinking blocks separately (some models include reasoning)
147
176
  thinking_parts.append(block.get("thinking", ""))
177
+ elif block.get("type") == "tool_use":
178
+ tool_call = {
179
+ "id": block.get("id"),
180
+ "type": "function",
181
+ "function": {
182
+ "name": block.get("name"),
183
+ "arguments": json.dumps(block.get("input", {})),
184
+ },
185
+ }
186
+ tool_calls.append(tool_call)
148
187
 
149
188
  # Combine all text content
150
189
  message_content = "\n".join(content_parts) if content_parts else ""
@@ -160,6 +199,10 @@ def install(ctx):
160
199
  if thinking_parts:
161
200
  choice["message"]["thinking"] = "\n".join(thinking_parts)
162
201
 
202
+ # Add tool_calls if present
203
+ if tool_calls:
204
+ choice["message"]["tool_calls"] = tool_calls
205
+
163
206
  ret["choices"].append(choice)
164
207
 
165
208
  # Transform usage
@@ -184,6 +227,3 @@ def install(ctx):
184
227
  return ret
185
228
 
186
229
  ctx.add_provider(AnthropicProvider)
187
-
188
-
189
- __install__ = install
@@ -5,7 +5,7 @@ import time
5
5
  import aiohttp
6
6
 
7
7
 
8
- def install(ctx):
8
+ def install_chutes(ctx):
9
9
  from llms.main import GeneratorBase
10
10
 
11
11
  class ChutesImage(GeneratorBase):
@@ -50,15 +50,18 @@ def install(ctx):
50
50
  if "messages" in chat and len(chat["messages"]) > 0:
51
51
  aspect_ratio = chat["messages"][0].get("aspect_ratio", "1:1")
52
52
  cfg_scale = self.cfg_scale
53
+ steps = self.steps
54
+ width = self.width
55
+ height = self.height
53
56
  if chat["model"] == "chutes-z-image-turbo":
54
57
  cfg_scale = min(self.cfg_scale, 5)
55
58
  payload = {
56
59
  "model": chat["model"],
57
60
  "prompt": ctx.last_user_prompt(chat),
58
61
  "guidance_scale": cfg_scale,
59
- "width": self.width,
60
- "height": self.height,
61
- "num_inference_steps": self.steps,
62
+ "width": width,
63
+ "height": height,
64
+ "num_inference_steps": steps,
62
65
  }
63
66
  if chat["model"] in self.model_negative_prompt:
64
67
  payload["negative_prompt"] = self.negative_prompt
@@ -68,9 +71,10 @@ def install(ctx):
68
71
  if aspect_ratio:
69
72
  dimension = ctx.app.aspect_ratios.get(aspect_ratio)
70
73
  if dimension:
71
- width, height = dimension.split("×")
72
- payload["width"] = int(width)
73
- payload["height"] = int(height)
74
+ w, h = dimension.split("×")
75
+ width, height = int(w), int(h)
76
+ payload["width"] = width
77
+ payload["height"] = height
74
78
 
75
79
  if chat["model"] in self.model_resolutions:
76
80
  # if models use resolution, remove width and height
@@ -107,14 +111,16 @@ def install(ctx):
107
111
  relative_url, info = ctx.save_image_to_cache(
108
112
  image_data,
109
113
  f"{chat['model']}.{ext}",
110
- {
111
- "model": chat["model"],
112
- "prompt": ctx.last_user_prompt(chat),
113
- "width": self.width,
114
- "height": self.height,
115
- "cfg_scale": self.cfg_scale,
116
- "steps": self.steps,
117
- },
114
+ ctx.to_file_info(
115
+ chat,
116
+ {
117
+ "aspect_ratio": aspect_ratio,
118
+ "width": width,
119
+ "height": height,
120
+ "cfg_scale": cfg_scale,
121
+ "steps": steps,
122
+ },
123
+ ),
118
124
  )
119
125
  return {
120
126
  "choices": [
@@ -147,6 +153,3 @@ def install(ctx):
147
153
  raise Exception(f"Failed to generate image {response.status}")
148
154
 
149
155
  ctx.add_provider(ChutesImage)
150
-
151
-
152
- __install__ = install
@@ -1,5 +1,8 @@
1
+ import base64
2
+ import io
1
3
  import json
2
4
  import time
5
+ import wave
3
6
 
4
7
  import aiohttp
5
8
 
@@ -11,7 +14,7 @@ import aiohttp
11
14
  # self.chat_url = "https://generativelanguage.googleapis.com/v1beta/chat/completions"
12
15
 
13
16
 
14
- def install(ctx):
17
+ def install_google(ctx):
15
18
  from llms.main import OpenAiCompatible
16
19
 
17
20
  def gemini_chat_summary(gemini_chat):
@@ -68,6 +71,7 @@ def install(ctx):
68
71
  super().__init__(**new_kwargs)
69
72
  self.safety_settings = kwargs.get("safety_settings")
70
73
  self.thinking_config = kwargs.get("thinking_config")
74
+ self.speech_config = kwargs.get("speech_config")
71
75
  self.tools = kwargs.get("tools")
72
76
  self.curl = kwargs.get("curl")
73
77
  self.headers = kwargs.get("headers", {"Content-Type": "application/json"})
@@ -189,13 +193,15 @@ def install(ctx):
189
193
  gemini_chat["generationConfig"] = generation_config
190
194
 
191
195
  if "tools" in chat:
192
- gemini_chat["tools"] = chat["tools"]
196
+ # gemini_chat["tools"] = chat["tools"]
197
+ ctx.log("Error: tools not supported in Gemini")
193
198
  elif self.tools:
194
- gemini_chat["tools"] = self.tools.copy()
199
+ # gemini_chat["tools"] = self.tools.copy()
200
+ ctx.log("Error: tools not supported in Gemini")
195
201
 
196
202
  if "modalities" in chat:
197
203
  generation_config["responseModalities"] = [modality.upper() for modality in chat["modalities"]]
198
- if "image_config" in chat:
204
+ if "image" in chat["modalities"] and "image_config" in chat:
199
205
  # delete thinkingConfig
200
206
  del generation_config["thinkingConfig"]
201
207
  config_map = {
@@ -205,6 +211,11 @@ def install(ctx):
205
211
  generation_config["imageConfig"] = {
206
212
  config_map[k]: v for k, v in chat["image_config"].items() if k in config_map
207
213
  }
214
+ if "audio" in chat["modalities"] and self.speech_config:
215
+ del generation_config["thinkingConfig"]
216
+ generation_config["speechConfig"] = self.speech_config.copy()
217
+ # Currently Google Audio Models only accept AUDIO
218
+ generation_config["responseModalities"] = ["AUDIO"]
208
219
 
209
220
  started_at = int(time.time() * 1000)
210
221
  gemini_chat_url = f"https://generativelanguage.googleapis.com/v1beta/models/{chat['model']}:generateContent?key={self.api_key}"
@@ -218,13 +229,22 @@ def install(ctx):
218
229
  with open(f"{ctx.MOCK_DIR}/gemini-image.json") as f:
219
230
  obj = json.load(f)
220
231
  else:
221
- async with session.post(
222
- gemini_chat_url,
223
- headers=self.headers,
224
- data=json.dumps(gemini_chat),
225
- timeout=aiohttp.ClientTimeout(total=120),
226
- ) as res:
227
- obj = await self.response_json(res)
232
+ try:
233
+ async with session.post(
234
+ gemini_chat_url,
235
+ headers=self.headers,
236
+ data=json.dumps(gemini_chat),
237
+ timeout=aiohttp.ClientTimeout(total=120),
238
+ ) as res:
239
+ obj = await self.response_json(res)
240
+ except Exception as e:
241
+ ctx.log(f"Error: {res.status} {res.reason}: {e}")
242
+ text = await res.text()
243
+ try:
244
+ obj = json.loads(text)
245
+ except:
246
+ ctx.log(text)
247
+ raise e
228
248
 
229
249
  if "error" in obj:
230
250
  ctx.log(f"Error: {obj['error']}")
@@ -233,6 +253,18 @@ def install(ctx):
233
253
  if ctx.debug:
234
254
  ctx.dbg(json.dumps(gemini_response_summary(obj), indent=2))
235
255
 
256
+ # calculate cost per generation
257
+ cost = None
258
+ token_costs = obj.get("metadata", {}).get("pricing", "")
259
+ if token_costs:
260
+ input_price, output_price = token_costs.split("/")
261
+ input_per_token = float(input_price) / 1000000
262
+ output_per_token = float(output_price) / 1000000
263
+ if "usageMetadata" in obj:
264
+ input_tokens = obj["usageMetadata"].get("promptTokenCount", 0)
265
+ output_tokens = obj["usageMetadata"].get("candidatesTokenCount", 0)
266
+ cost = (input_per_token * input_tokens) + (output_per_token * output_tokens)
267
+
236
268
  response = {
237
269
  "id": f"chatcmpl-{started_at}",
238
270
  "created": started_at,
@@ -248,6 +280,7 @@ def install(ctx):
248
280
  content = ""
249
281
  reasoning = ""
250
282
  images = []
283
+ audios = []
251
284
  if "content" in candidate and "parts" in candidate["content"]:
252
285
  text_parts = []
253
286
  reasoning_parts = []
@@ -260,19 +293,59 @@ def install(ctx):
260
293
  if "inlineData" in part:
261
294
  inline_data = part["inlineData"]
262
295
  mime_type = inline_data.get("mimeType", "image/png")
263
- ext = mime_type.split("/")[1]
264
- base64_data = inline_data["data"]
265
- filename = f"{chat['model'].split('/')[-1]}-{len(images)}.{ext}"
266
- relative_url, info = ctx.save_image_to_cache(base64_data, filename, {})
267
- images.append(
268
- {
269
- "type": "image_url",
270
- "index": len(images),
271
- "image_url": {
272
- "url": relative_url,
273
- },
274
- }
275
- )
296
+ if mime_type.startswith("image"):
297
+ ext = mime_type.split("/")[1]
298
+ base64_data = inline_data["data"]
299
+ filename = f"{chat['model'].split('/')[-1]}-{len(images)}.{ext}"
300
+ ctx.log(f"inlineData {len(base64_data)} {mime_type} {filename}")
301
+ relative_url, info = ctx.save_image_to_cache(
302
+ base64_data,
303
+ filename,
304
+ ctx.to_file_info(chat, {"cost": cost}),
305
+ )
306
+ images.append(
307
+ {
308
+ "type": "image_url",
309
+ "index": len(images),
310
+ "image_url": {
311
+ "url": relative_url,
312
+ },
313
+ }
314
+ )
315
+ elif mime_type.startswith("audio"):
316
+ # mime_type audio/L16;codec=pcm;rate=24000
317
+ base64_data = inline_data["data"]
318
+
319
+ pcm = base64.b64decode(base64_data)
320
+ # Convert PCM to WAV
321
+ wav_io = io.BytesIO()
322
+ with wave.open(wav_io, "wb") as wf:
323
+ wf.setnchannels(1)
324
+ wf.setsampwidth(2)
325
+ wf.setframerate(24000)
326
+ wf.writeframes(pcm)
327
+ wav_data = wav_io.getvalue()
328
+
329
+ ext = mime_type.split("/")[1].split(";")[0]
330
+ pcm_filename = f"{chat['model'].split('/')[-1]}-{len(audios)}.{ext}"
331
+ filename = pcm_filename.replace(f".{ext}", ".wav")
332
+ ctx.log(f"inlineData {len(base64_data)} {mime_type} {filename}")
333
+
334
+ relative_url, info = ctx.save_bytes_to_cache(
335
+ wav_data,
336
+ filename,
337
+ ctx.to_file_info(chat, {"cost": cost}),
338
+ )
339
+
340
+ audios.append(
341
+ {
342
+ "type": "audio_url",
343
+ "index": len(audios),
344
+ "audio_url": {
345
+ "url": relative_url,
346
+ },
347
+ }
348
+ )
276
349
  content = " ".join(text_parts)
277
350
  reasoning = " ".join(reasoning_parts)
278
351
 
@@ -288,6 +361,8 @@ def install(ctx):
288
361
  choice["message"]["reasoning"] = reasoning
289
362
  if len(images) > 0:
290
363
  choice["message"]["images"] = images
364
+ if len(audios) > 0:
365
+ choice["message"]["audios"] = audios
291
366
  choices.append(choice)
292
367
  response["choices"] = choices
293
368
  if "usageMetadata" in obj:
@@ -301,6 +376,3 @@ def install(ctx):
301
376
  return ctx.log_json(self.to_response(response, chat, started_at))
302
377
 
303
378
  ctx.add_provider(GoogleProvider)
304
-
305
-
306
- __install__ = install
@@ -4,7 +4,7 @@ import time
4
4
  import aiohttp
5
5
 
6
6
 
7
- def install(ctx):
7
+ def install_nvidia(ctx):
8
8
  from llms.main import GeneratorBase
9
9
 
10
10
  class NvidiaGenAi(GeneratorBase):
@@ -29,10 +29,11 @@ def install(ctx):
29
29
  last_model = "/" in chat["model"] and chat["model"].split("/")[-1] or chat["model"]
30
30
  filename = f"{last_model}_{seed}.png"
31
31
 
32
- image_info = {
33
- "seed": seed,
34
- }
35
- relative_url, info = ctx.save_image_to_cache(base64, filename, image_info)
32
+ relative_url, info = ctx.save_image_to_cache(
33
+ base64,
34
+ filename,
35
+ ctx.to_file_info(chat, {"seed": seed}),
36
+ )
36
37
  return {
37
38
  "choices": [
38
39
  {
@@ -102,6 +103,3 @@ def install(ctx):
102
103
  return self.to_response(await self.response_json(response), chat, started_at)
103
104
 
104
105
  ctx.add_provider(NvidiaGenAi)
105
-
106
-
107
- __install__ = install
@@ -6,7 +6,7 @@ import time
6
6
  import aiohttp
7
7
 
8
8
 
9
- def install(ctx):
9
+ def install_openai(ctx):
10
10
  from llms.main import GeneratorBase, OpenAiCompatible
11
11
 
12
12
  class OpenAiProvider(OpenAiCompatible):
@@ -83,10 +83,7 @@ def install(ctx):
83
83
  relative_url, info = ctx.save_image_to_cache(
84
84
  image_data,
85
85
  f"{chat['model']}-{i}.{ext}",
86
- {
87
- "model": chat["model"],
88
- "prompt": ctx.last_user_prompt(chat),
89
- },
86
+ ctx.to_file_info(chat),
90
87
  )
91
88
  images.append(
92
89
  {
@@ -156,4 +153,4 @@ def install(ctx):
156
153
  ctx.add_provider(OpenAiGenerator)
157
154
 
158
155
 
159
- __install__ = install
156
+ __install__ = install_openai
@@ -4,7 +4,7 @@ import time
4
4
  import aiohttp
5
5
 
6
6
 
7
- def install(ctx):
7
+ def install_openrouter(ctx):
8
8
  from llms.main import GeneratorBase
9
9
 
10
10
  # https://openrouter.ai/docs/guides/overview/multimodal/image-generation
@@ -16,6 +16,9 @@ def install(ctx):
16
16
 
17
17
  def to_response(self, response, chat, started_at):
18
18
  # go through all image responses and save them to cache
19
+ cost = None
20
+ if "usage" in response and "cost" in response["usage"]:
21
+ cost = response["usage"]["cost"]
19
22
  for choice in response["choices"]:
20
23
  if "message" in choice and "images" in choice["message"]:
21
24
  for image in choice["message"]["images"]:
@@ -29,11 +32,9 @@ def install(ctx):
29
32
  base64_data = parts[1]
30
33
  model = chat["model"].split("/")[-1]
31
34
  filename = f"{model}-{choice['index']}.{ext}"
32
- info = {
33
- "model": model,
34
- "prompt": ctx.last_user_prompt(chat),
35
- }
36
- relative_url, info = ctx.save_image_to_cache(base64_data, filename, info)
35
+ relative_url, info = ctx.save_image_to_cache(
36
+ base64_data, filename, ctx.to_file_info(chat, {"cost": cost})
37
+ )
37
38
  image["image_url"]["url"] = relative_url
38
39
 
39
40
  return response
@@ -50,11 +51,13 @@ def install(ctx):
50
51
  return ctx.log_json(self.to_response(json.loads(text), chat, started_at))
51
52
  else:
52
53
  chat_url = provider.chat_url
54
+ # remove tools
55
+ chat.pop("tools", None)
53
56
  chat = await self.process_chat(chat, provider_id=self.id)
54
57
  ctx.log(f"POST {chat_url}")
55
58
  ctx.log(provider.chat_summary(chat))
56
59
  # remove metadata if any (conflicts with some providers, e.g. Z.ai)
57
- chat.pop("metadata", None)
60
+ metadata = chat.pop("metadata", None)
58
61
 
59
62
  async with aiohttp.ClientSession() as session, session.post(
60
63
  chat_url,
@@ -62,9 +65,8 @@ def install(ctx):
62
65
  data=json.dumps(chat),
63
66
  timeout=aiohttp.ClientTimeout(total=300),
64
67
  ) as response:
68
+ if metadata:
69
+ chat["metadata"] = metadata
65
70
  return ctx.log_json(self.to_response(await self.response_json(response), chat, started_at))
66
71
 
67
72
  ctx.add_provider(OpenRouterGenerator)
68
-
69
-
70
- __install__ = install
@@ -0,0 +1,45 @@
1
+ import json
2
+ import os
3
+
4
+ from aiohttp import web
5
+
6
+ default_prompts = [
7
+ {"name": "Helpful Assistant", "prompt": "You are a helpful assistant."},
8
+ ]
9
+
10
+
11
+ # runs after providers are configured but before server is run
12
+ def install(ctx):
13
+ # helper to get user or default prompts
14
+ def get_user_prompts(request):
15
+ candidate_paths = []
16
+ # check if user is signed in
17
+ username = ctx.get_username(request)
18
+ if username:
19
+ # if signed in (Github OAuth), return the prompts for this user if exists
20
+ candidate_paths.append(os.path.join(ctx.get_user_path(username), "system_prompts", "prompts.json"))
21
+ # return default prompts for all users if exists
22
+ candidate_paths.append(os.path.join(ctx.get_user_path(), "system_prompts", "prompts.json"))
23
+ # otherwise return the default prompts from this repo
24
+ candidate_paths.append(os.path.join(ctx.path, "ui", "prompts.json"))
25
+
26
+ # iterate all candidate paths and when exists return its json
27
+ for path in candidate_paths:
28
+ if os.path.exists(path):
29
+ with open(path, encoding="utf-8") as f:
30
+ txt = f.read()
31
+ return json.loads(txt)
32
+ return default_prompts
33
+
34
+ # API Handler to get prompts
35
+ async def get_prompts(request):
36
+ prompts_json = get_user_prompts(request)
37
+ return web.json_response(prompts_json)
38
+
39
+ ctx.add_get("prompts.json", get_prompts)
40
+
41
+
42
+ # register install extension handler
43
+ __install__ = install
44
+
45
+ __order__ = -10