pygpt-net 2.7.6__py3-none-any.whl → 2.7.7__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 (57) hide show
  1. pygpt_net/CHANGELOG.txt +6 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/controller/chat/remote_tools.py +3 -9
  4. pygpt_net/controller/chat/stream.py +2 -2
  5. pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
  6. pygpt_net/core/debug/models.py +2 -2
  7. pygpt_net/data/config/config.json +14 -4
  8. pygpt_net/data/config/models.json +192 -4
  9. pygpt_net/data/config/settings.json +125 -35
  10. pygpt_net/data/locale/locale.de.ini +2 -0
  11. pygpt_net/data/locale/locale.en.ini +32 -8
  12. pygpt_net/data/locale/locale.es.ini +2 -0
  13. pygpt_net/data/locale/locale.fr.ini +2 -0
  14. pygpt_net/data/locale/locale.it.ini +2 -0
  15. pygpt_net/data/locale/locale.pl.ini +3 -1
  16. pygpt_net/data/locale/locale.uk.ini +2 -0
  17. pygpt_net/data/locale/locale.zh.ini +2 -0
  18. pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
  19. pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
  20. pygpt_net/provider/api/anthropic/__init__.py +8 -3
  21. pygpt_net/provider/api/anthropic/chat.py +259 -11
  22. pygpt_net/provider/api/anthropic/computer.py +844 -0
  23. pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
  24. pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +24 -10
  25. pygpt_net/provider/api/anthropic/tools.py +32 -77
  26. pygpt_net/provider/api/anthropic/utils.py +30 -0
  27. pygpt_net/provider/api/google/chat.py +3 -7
  28. pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +1 -1
  29. pygpt_net/provider/api/google/utils.py +185 -0
  30. pygpt_net/{controller/chat/handler → provider/api/langchain}/__init__.py +0 -0
  31. pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
  32. pygpt_net/provider/api/llama_index/__init__.py +0 -0
  33. pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
  34. pygpt_net/provider/api/openai/image.py +2 -2
  35. pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
  36. pygpt_net/provider/api/openai/utils.py +69 -3
  37. pygpt_net/provider/api/x_ai/__init__.py +109 -10
  38. pygpt_net/provider/api/x_ai/chat.py +0 -0
  39. pygpt_net/provider/api/x_ai/image.py +149 -47
  40. pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +165 -70
  41. pygpt_net/provider/api/x_ai/responses.py +507 -0
  42. pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +12 -1
  43. pygpt_net/provider/api/x_ai/tools.py +59 -8
  44. pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
  45. pygpt_net/provider/api/x_ai/vision.py +1 -4
  46. pygpt_net/provider/core/config/patch.py +22 -1
  47. pygpt_net/provider/core/model/patch.py +26 -1
  48. pygpt_net/tools/image_viewer/ui/dialogs.py +3 -2
  49. pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
  50. pygpt_net/tools/text_editor/ui/widgets.py +0 -0
  51. pygpt_net/ui/widget/dialog/base.py +16 -5
  52. pygpt_net/ui/widget/textarea/editor.py +0 -0
  53. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.7.dist-info}/METADATA +8 -2
  54. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.7.dist-info}/RECORD +54 -48
  55. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.7.dist-info}/LICENSE +0 -0
  56. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.7.dist-info}/WHEEL +0 -0
  57. {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.7.dist-info}/entry_points.txt +0 -0
@@ -6,27 +6,176 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.17 20:00:00 #
9
+ # Updated Date: 2026.01.04 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from __future__ import annotations
13
+
14
+ import json
13
15
  from typing import Optional, Dict, Any, List
14
16
 
15
17
  from pygpt_net.item.model import ModelItem
16
18
 
19
+ # xAI server-side tools
20
+ try:
21
+ from xai_sdk.tools import web_search as x_web_search
22
+ from xai_sdk.tools import x_search as x_x_search
23
+ from xai_sdk.tools import code_execution as x_code_execution
24
+ from xai_sdk.tools import mcp as x_mcp
25
+ except Exception:
26
+ x_web_search = None
27
+ x_x_search = None
28
+ x_code_execution = None
29
+ x_mcp = None
30
+
17
31
 
18
32
  class Remote:
19
33
  def __init__(self, window=None):
20
34
  """
21
- Live Search builder for xAI:
35
+ Live Search builder for xAI (old, grok-3):
22
36
  - Returns a dict with 'sdk' (X-only toggle) and 'http' (full search_parameters).
23
37
  - SDK path: native streaming (xai_sdk.chat.stream()) works only for basic X search (no advanced filters).
24
38
  - HTTP path: full Live Search (web/news/rss/X with filters), streaming via SSE.
25
39
 
40
+ Server-side Tools builder for xAI (newer SDKs, Responses API):
41
+ - Builds xAI SDK tool objects for web_search, x_search, code_execution, MCP.
42
+ - Returns include flags, max_turns and use_encrypted_content settings.
43
+
26
44
  :param window: Window instance
27
45
  """
28
46
  self.window = window
29
47
 
48
+ def build_for_chat(self, model: ModelItem = None, stream: bool = False) -> Dict[str, Any]:
49
+ """
50
+ Build server-side tools and options for Chat Responses.
51
+
52
+ Returns:
53
+ {
54
+ "tools": [ ... xai_sdk.tools.* ... ],
55
+ "include": [ ... ],
56
+ "use_encrypted_content": bool,
57
+ "max_turns": Optional[int],
58
+ }
59
+ """
60
+ cfg = self.window.core.config
61
+ include: List[str] = []
62
+ tools: List[object] = []
63
+
64
+ # global remote tools switch
65
+ is_web_enabled = self.window.controller.chat.remote_tools.enabled(model, "web_search")
66
+ is_x_enabled = bool(cfg.get("remote_tools.xai.x_search", False))
67
+ is_code_enabled = bool(cfg.get("remote_tools.xai.code_execution", False))
68
+ is_mcp_enabled = bool(cfg.get("remote_tools.xai.mcp", False))
69
+
70
+ # include flags
71
+ if stream:
72
+ include.append("verbose_streaming")
73
+ if bool(cfg.get("remote_tools.xai.inline_citations", True)):
74
+ include.append("inline_citations")
75
+ if bool(cfg.get("remote_tools.xai.include_code_output", True)):
76
+ include.append("code_execution_call_output")
77
+
78
+ # use_encrypted_content
79
+ use_encrypted = bool(cfg.get("remote_tools.xai.use_encrypted_content", False))
80
+
81
+ # optional max_turns
82
+ max_turns = None
83
+ try:
84
+ mt = cfg.get("remote_tools.xai.max_turns")
85
+ if isinstance(mt, int) and mt > 0:
86
+ max_turns = int(mt)
87
+ except Exception:
88
+ pass
89
+
90
+ # WEB SEARCH
91
+ if is_web_enabled and x_web_search is not None:
92
+ kwargs: Dict[str, Any] = {}
93
+ enable_img = bool(cfg.get("remote_tools.xai.web.enable_image_understanding", False))
94
+ '''
95
+ allowed = self._as_list(cfg.get("remote_tools.xai.web.allowed_websites"), 5)
96
+ excluded = self._as_list(cfg.get("remote_tools.xai.web.excluded_websites"), 5)
97
+ if allowed and not excluded:
98
+ kwargs["allowed_domains"] = allowed
99
+ elif excluded and not allowed:
100
+ kwargs["excluded_domains"] = excluded
101
+ '''
102
+ if enable_img:
103
+ kwargs["enable_image_understanding"] = True
104
+ try:
105
+ tools.append(x_web_search(**kwargs))
106
+ except Exception:
107
+ tools.append(x_web_search())
108
+
109
+ # X SEARCH
110
+ if is_x_enabled and x_x_search is not None:
111
+ kwargs: Dict[str, Any] = {}
112
+ '''
113
+ inc = self._as_list(cfg.get("remote_tools.xai.x.included_handles"), 10)
114
+ exc = self._as_list(cfg.get("remote_tools.xai.x.excluded_handles"), 10)
115
+ if inc and not exc:
116
+ kwargs["allowed_x_handles"] = inc
117
+ elif exc and not inc:
118
+ kwargs["excluded_x_handles"] = exc
119
+ '''
120
+ # optional date range filters (YYYY-MM-DD)
121
+ for k_in, k_out in (("remote_tools.xai.from_date", "from_date"),
122
+ ("remote_tools.xai.to_date", "to_date")):
123
+ v = cfg.get(k_in)
124
+ if isinstance(v, str) and v.strip():
125
+ kwargs[k_out] = v.strip()
126
+
127
+ if bool(cfg.get("remote_tools.xai.x.enable_image_understanding", False)):
128
+ kwargs["enable_image_understanding"] = True
129
+ if bool(cfg.get("remote_tools.xai.x.enable_video_understanding", False)):
130
+ kwargs["enable_video_understanding"] = True
131
+
132
+ # optional favorites/views filters (supported by live search)
133
+ try:
134
+ favs = cfg.get("remote_tools.xai.x.min_favs")
135
+ if isinstance(favs, int) and favs > 0:
136
+ kwargs["post_favorite_count"] = int(favs)
137
+ except Exception:
138
+ pass
139
+ try:
140
+ views = cfg.get("remote_tools.xai.x.min_views")
141
+ if isinstance(views, int) and views > 0:
142
+ kwargs["post_view_count"] = int(views)
143
+ except Exception:
144
+ pass
145
+
146
+ try:
147
+ tools.append(x_x_search(**kwargs))
148
+ except Exception:
149
+ tools.append(x_x_search())
150
+
151
+ # CODE EXECUTION
152
+ if is_code_enabled and x_code_execution is not None:
153
+ try:
154
+ tools.append(x_code_execution())
155
+ except Exception:
156
+ pass
157
+
158
+ # MCP
159
+ if is_mcp_enabled and x_mcp is not None:
160
+ kwargs = {}
161
+ mcp_config = cfg.get("remote_tools.xai.mcp.args", "")
162
+ if mcp_config:
163
+ try:
164
+ kwargs = json.loads(mcp_config)
165
+ except Exception:
166
+ pass
167
+ try:
168
+ tools.append(x_mcp(**kwargs))
169
+ except Exception:
170
+ pass
171
+
172
+ return {
173
+ "tools": tools,
174
+ "include": include,
175
+ "use_encrypted_content": use_encrypted,
176
+ "max_turns": max_turns,
177
+ }
178
+
30
179
  def build_remote_tools(self, model: ModelItem = None) -> Dict[str, Any]:
31
180
  """
32
181
  Return live-search config for xAI:
@@ -58,38 +207,17 @@ class Remote:
58
207
  """
59
208
  cfg = self.window.core.config
60
209
  is_web = self.window.controller.chat.remote_tools.enabled(model, "web_search") # get global config
61
-
62
- mode = str(cfg.get("remote_tools.xai.mode") or "auto").lower()
63
- if mode not in ("auto", "on", "off"):
64
- mode = "auto"
65
-
66
- if mode == "off":
67
- if is_web:
68
- mode = "on" # override off if global web_search enabled
210
+ mode = "on" if is_web else "off"
69
211
 
70
212
  # sources toggles
71
- s_web = bool(cfg.get("remote_tools.xai.sources.web", True))
72
- s_x = bool(cfg.get("remote_tools.xai.sources.x", True))
73
- s_news = bool(cfg.get("remote_tools.xai.sources.news", False))
74
- s_rss = bool(cfg.get("remote_tools.xai.sources.rss", False))
75
-
76
- # advanced flags
77
- adv_web_allowed = self._has_list(cfg.get("remote_tools.xai.web.allowed_websites"))
78
- adv_web_excluded = self._has_list(cfg.get("remote_tools.xai.web.excluded_websites"))
79
- adv_web_country = self._has_str(cfg.get("remote_tools.xai.web.country"))
80
- adv_web_safe = cfg.get("remote_tools.xai.web.safe_search", None)
81
-
82
- adv_news_excl = self._has_list(cfg.get("remote_tools.xai.news.excluded_websites"))
83
- adv_news_country = self._has_str(cfg.get("remote_tools.xai.news.country"))
84
- adv_news_safe = cfg.get("remote_tools.xai.news.safe_search", None)
213
+ s_web = bool(cfg.get("remote_tools.xai.web_search", False))
214
+ s_x = bool(cfg.get("remote_tools.xai.x_search", False))
85
215
 
86
216
  adv_x_incl = self._has_list(cfg.get("remote_tools.xai.x.included_handles"))
87
217
  adv_x_excl = self._has_list(cfg.get("remote_tools.xai.x.excluded_handles"))
88
218
  adv_x_favs = self._has_int(cfg.get("remote_tools.xai.x.min_favs"))
89
219
  adv_x_views = self._has_int(cfg.get("remote_tools.xai.x.min_views"))
90
220
 
91
- adv_rss_link = self._has_str(cfg.get("remote_tools.xai.rss.link"))
92
-
93
221
  adv_from = self._has_str(cfg.get("remote_tools.xai.from_date"))
94
222
  adv_to = self._has_str(cfg.get("remote_tools.xai.to_date"))
95
223
 
@@ -97,7 +225,7 @@ class Remote:
97
225
  adv_return_cits = cfg.get("remote_tools.xai.return_citations", True) is not True # different than default?
98
226
 
99
227
  # SDK-capable if: mode!=off and ONLY X is enabled and no X filters/date/max_results customizations
100
- x_only = s_x and not s_web and not s_news and not s_rss
228
+ x_only = s_x and not s_web
101
229
  x_filters = any([adv_x_incl, adv_x_excl, adv_x_favs, adv_x_views])
102
230
  sdk_enabled = (mode != "off") and x_only and not any([
103
231
  x_filters, adv_from, adv_to, adv_max_results, adv_return_cits
@@ -110,10 +238,10 @@ class Remote:
110
238
 
111
239
  need_http = (mode != "off") and (
112
240
  not sdk_enabled or # advanced X filters or other sources/date/results/citations
113
- s_web or s_news or s_rss
241
+ s_web
114
242
  )
115
243
  if need_http:
116
- http_params = self._build_http_params(cfg, mode, s_web, s_x, s_news, s_rss)
244
+ http_params = self._build_http_params(cfg, mode, s_web, s_x)
117
245
  http_reason = "advanced_sources_or_filters"
118
246
 
119
247
  return {
@@ -131,8 +259,6 @@ class Remote:
131
259
  mode: str,
132
260
  s_web: bool,
133
261
  s_x: bool,
134
- s_news: bool,
135
- s_rss: bool
136
262
  ) -> dict:
137
263
  """
138
264
  Build search_parameters for Chat Completions (HTTP).
@@ -141,8 +267,6 @@ class Remote:
141
267
  :param mode: "auto"|"on"|"off"
142
268
  :param s_web: Include web search
143
269
  :param s_x: Include X search
144
- :param s_news: Include news search
145
- :param s_rss: Include RSS search
146
270
  :return: search_parameters dict
147
271
  """
148
272
  params: Dict[str, Any] = {"mode": mode}
@@ -196,41 +320,12 @@ class Remote:
196
320
  xsrc["post_view_count"] = int(views)
197
321
  sources.append(xsrc)
198
322
 
199
- if s_news:
200
- news: Dict[str, Any] = {"type": "news"}
201
- country = cfg.get("remote_tools.xai.news.country")
202
- if isinstance(country, str) and len(country.strip()) == 2:
203
- news["country"] = country.strip().upper()
204
- excluded = self._as_list(cfg.get("remote_tools.xai.news.excluded_websites"), 5)
205
- if excluded:
206
- news["excluded_websites"] = excluded
207
- safe = cfg.get("remote_tools.xai.news.safe_search")
208
- if safe is not None:
209
- news["safe_search"] = bool(safe)
210
- sources.append(news)
211
-
212
- if s_rss:
213
- link = cfg.get("remote_tools.xai.rss.link")
214
- if isinstance(link, str) and link.strip():
215
- sources.append({"type": "rss", "links": [link.strip()]})
216
-
217
323
  if sources:
218
324
  params["sources"] = sources
219
325
 
220
326
  return params
221
327
 
222
- def _has_list(self, v) -> bool:
223
- """
224
- Return True if v is a non-empty list/tuple or a non-empty comma-separated string.
225
-
226
- :param v: Any
227
- :return: bool
228
- """
229
- if v is None:
230
- return False
231
- if isinstance(v, (list, tuple)):
232
- return len([x for x in v if str(x).strip()]) > 0
233
- return len([x for x in str(v).split(",") if x.strip()]) > 0
328
+ # ---------- helpers ----------
234
329
 
235
330
  def _has_str(self, v) -> bool:
236
331
  """
@@ -250,14 +345,14 @@ class Remote:
250
345
  """
251
346
  return isinstance(v, int) and v > 0
252
347
 
253
- def _as_list(self, v, limit: int) -> List[str]:
254
- """
255
- Convert v to a list of strings, limited to 'limit' items.
348
+ def _has_list(self, v) -> bool:
349
+ if v is None:
350
+ return False
351
+ if isinstance(v, (list, tuple)):
352
+ return len([x for x in v if str(x).strip()]) > 0
353
+ return len([x for x in str(v).split(",") if x.strip()]) > 0
256
354
 
257
- :param v: Any
258
- :param limit: limit number of items
259
- :return: List of strings
260
- """
355
+ def _as_list(self, v, limit: int) -> List[str]:
261
356
  if v is None:
262
357
  return []
263
358
  if isinstance(v, (list, tuple)):