pygpt-net 2.6.52__py3-none-any.whl → 2.6.54__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 (61) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +4 -0
  4. pygpt_net/controller/audio/audio.py +22 -1
  5. pygpt_net/controller/chat/chat.py +5 -1
  6. pygpt_net/controller/chat/remote_tools.py +116 -0
  7. pygpt_net/controller/lang/mapping.py +2 -1
  8. pygpt_net/controller/mode/mode.py +5 -2
  9. pygpt_net/controller/plugins/plugins.py +29 -3
  10. pygpt_net/controller/realtime/realtime.py +8 -3
  11. pygpt_net/controller/ui/mode.py +17 -5
  12. pygpt_net/core/agents/provider.py +16 -9
  13. pygpt_net/core/models/models.py +25 -1
  14. pygpt_net/core/render/web/renderer.py +3 -1
  15. pygpt_net/data/config/config.json +5 -4
  16. pygpt_net/data/config/models.json +3 -3
  17. pygpt_net/data/icons/web_off.svg +1 -0
  18. pygpt_net/data/icons/web_on.svg +1 -0
  19. pygpt_net/data/js/app.js +19 -0
  20. pygpt_net/data/locale/locale.de.ini +1 -0
  21. pygpt_net/data/locale/locale.en.ini +3 -2
  22. pygpt_net/data/locale/locale.es.ini +1 -0
  23. pygpt_net/data/locale/locale.fr.ini +1 -0
  24. pygpt_net/data/locale/locale.it.ini +1 -0
  25. pygpt_net/data/locale/locale.pl.ini +1 -4
  26. pygpt_net/data/locale/locale.uk.ini +1 -0
  27. pygpt_net/data/locale/locale.zh.ini +1 -0
  28. pygpt_net/data/locale/plugin.mcp.en.ini +4 -4
  29. pygpt_net/data/locale/plugin.osm.en.ini +35 -0
  30. pygpt_net/data/locale/plugin.wolfram.en.ini +24 -0
  31. pygpt_net/icons.qrc +2 -0
  32. pygpt_net/icons_rc.py +232 -147
  33. pygpt_net/js_rc.py +10490 -10432
  34. pygpt_net/plugin/base/worker.py +7 -1
  35. pygpt_net/plugin/osm/__init__.py +12 -0
  36. pygpt_net/plugin/osm/config.py +267 -0
  37. pygpt_net/plugin/osm/plugin.py +87 -0
  38. pygpt_net/plugin/osm/worker.py +719 -0
  39. pygpt_net/plugin/wolfram/__init__.py +12 -0
  40. pygpt_net/plugin/wolfram/config.py +214 -0
  41. pygpt_net/plugin/wolfram/plugin.py +115 -0
  42. pygpt_net/plugin/wolfram/worker.py +551 -0
  43. pygpt_net/provider/api/anthropic/tools.py +4 -2
  44. pygpt_net/provider/api/google/__init__.py +3 -2
  45. pygpt_net/provider/api/google/video.py +0 -0
  46. pygpt_net/provider/api/openai/agents/experts.py +1 -1
  47. pygpt_net/provider/api/openai/agents/remote_tools.py +14 -4
  48. pygpt_net/provider/api/openai/chat.py +7 -2
  49. pygpt_net/provider/api/openai/remote_tools.py +5 -2
  50. pygpt_net/provider/api/x_ai/remote.py +6 -1
  51. pygpt_net/provider/core/config/patch.py +8 -1
  52. pygpt_net/provider/llms/anthropic.py +29 -1
  53. pygpt_net/provider/llms/google.py +30 -1
  54. pygpt_net/provider/llms/open_router.py +3 -1
  55. pygpt_net/provider/llms/x_ai.py +21 -1
  56. pygpt_net/ui/layout/chat/output.py +7 -2
  57. {pygpt_net-2.6.52.dist-info → pygpt_net-2.6.54.dist-info}/METADATA +37 -2
  58. {pygpt_net-2.6.52.dist-info → pygpt_net-2.6.54.dist-info}/RECORD +60 -47
  59. {pygpt_net-2.6.52.dist-info → pygpt_net-2.6.54.dist-info}/LICENSE +0 -0
  60. {pygpt_net-2.6.52.dist-info → pygpt_net-2.6.54.dist-info}/WHEEL +0 -0
  61. {pygpt_net-2.6.52.dist-info → pygpt_net-2.6.54.dist-info}/entry_points.txt +0 -0
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,14 @@
1
+ 2.6.54 (2025-09-18)
2
+
3
+ - Added: Remote tools (like web search) are now also available in the Chat with Files and Agents (LlamaIndex) modes.
4
+ - Added: Two new plugins: Wolfram Alpha and OpenStreetMap.
5
+ - Fixed: Enabled local file-like schemes in links/images in the markdown-it parser.
6
+
7
+ 2.6.53 (2025-09-17)
8
+
9
+ - Added: An icon to enable/disable the web search remote tool in the icon bar, along with remote web search functionality in OpenRouter (#135).
10
+ - Added: The ability to mute audio in real-time mode via the audio icon.
11
+
1
12
  2.6.52 (2025-09-17)
2
13
 
3
14
  - Added MCP plugin: Provides access to remote tools via the Model Context Protocol (MCP), including stdio, SSE, and Streamable HTTP transports, with per-server allow/deny filtering, Authorization header support, and a tools cache.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
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 00:00:00 #
9
+ # Updated Date: 2025.09.18 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.52"
17
- __build__ = "2025-09-17"
16
+ __version__ = "2.6.54"
17
+ __build__ = "2025-09-18"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.py CHANGED
@@ -97,6 +97,8 @@ from pygpt_net.plugin.server import Plugin as ServerPlugin
97
97
  from pygpt_net.plugin.tuya import Plugin as TuyaPlugin
98
98
  from pygpt_net.plugin.wikipedia import Plugin as WikipediaPlugin
99
99
  from pygpt_net.plugin.mcp import Plugin as MCPPlugin
100
+ from pygpt_net.plugin.wolfram import Plugin as WolframPlugin
101
+ from pygpt_net.plugin.osm import Plugin as OSMPlugin
100
102
 
101
103
  # agents (Llama-index)
102
104
  # from pygpt_net.provider.agents.llama_index.legacy.openai import OpenAIAgent
@@ -427,6 +429,8 @@ def run(**kwargs):
427
429
  launcher.add_plugin(TuyaPlugin())
428
430
  launcher.add_plugin(WikipediaPlugin())
429
431
  launcher.add_plugin(MCPPlugin())
432
+ launcher.add_plugin(WolframPlugin())
433
+ launcher.add_plugin(OSMPlugin())
430
434
 
431
435
  # register custom plugins
432
436
  plugins = kwargs.get('plugins', None)
@@ -6,7 +6,7 @@
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.08.31 23:00:00 #
9
+ # Updated Date: 2025.09.17 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -36,6 +36,7 @@ class Audio:
36
36
  Tab.TAB_CHAT,
37
37
  Tab.TAB_TOOL_CALENDAR,
38
38
  ]
39
+ self.muted = False
39
40
 
40
41
  def setup(self):
41
42
  """Setup controller"""
@@ -53,6 +54,26 @@ class Audio:
53
54
  if self.window.core.config.get("audio.input.loop", False):
54
55
  self.window.ui.nodes['audio.loop'].box.setChecked(True)
55
56
 
57
+ def is_muted(self) -> bool:
58
+ """
59
+ Check if audio output is muted
60
+
61
+ :return: True if muted
62
+ """
63
+ return self.muted
64
+
65
+ def set_muted(self, state: bool):
66
+ """
67
+ Set audio output muted state
68
+
69
+ :param state: True to mute, False to unmute
70
+ """
71
+ self.muted = state
72
+ if state:
73
+ self.toggle_output_icon(False)
74
+ else:
75
+ self.toggle_output_icon(True)
76
+
56
77
  def execute_input_stop(self):
57
78
  """Execute input stop (from UI)"""
58
79
  self.window.dispatch(Event(Event.AUDIO_INPUT_RECORD_TOGGLE, {
@@ -6,7 +6,7 @@
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.08.23 15:00:00 #
9
+ # Updated Date: 2025.09.17 05:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any
@@ -22,6 +22,7 @@ from .files import Files
22
22
  from .image import Image
23
23
  from .input import Input
24
24
  from .output import Output
25
+ from .remote_tools import RemoteTools
25
26
  from .render import Render
26
27
  from .response import Response
27
28
  from .stream import Stream
@@ -46,6 +47,7 @@ class Chat:
46
47
  self.image = Image(w)
47
48
  self.input = Input(w)
48
49
  self.output = Output(w)
50
+ self.remote_tools = RemoteTools(w)
49
51
  self.render = Render(w)
50
52
  self.response = Response(w)
51
53
  self.stream = Stream(w)
@@ -60,12 +62,14 @@ class Chat:
60
62
  """Setup"""
61
63
  self.common.setup()
62
64
  self.attachment.setup()
65
+ self.remote_tools.setup()
63
66
 
64
67
  def reload(self) -> None:
65
68
  """Reload"""
66
69
  self.common.setup()
67
70
  self.render.reload()
68
71
  self.attachment.reload()
72
+ self.remote_tools.setup()
69
73
 
70
74
  def handle_error(self, err: Any) -> None:
71
75
  """
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.17 20:00:00 #
10
+ # ================================================== #
11
+
12
+ from typing import Union
13
+
14
+ from pygpt_net.item.model import ModelItem
15
+
16
+
17
+ class RemoteTools:
18
+ def __init__(self, window=None):
19
+ """
20
+ Remote tools controller
21
+
22
+ :param window: Window instance
23
+ """
24
+ self.window = window
25
+ self.enabled_global = {
26
+ "web_search": False,
27
+ }
28
+
29
+ def setup(self):
30
+ """
31
+ Setup remote tools
32
+
33
+ :return: None
34
+ """
35
+ cfg_get = self.window.core.config.get
36
+ self.enabled_global["web_search"] = cfg_get("remote_tools.global.web_search", False)
37
+ self.update_icons()
38
+
39
+ def enabled(self, model: Union[ModelItem, str], tool_name: str) -> bool:
40
+ """
41
+ Check if remote tool is enabled
42
+
43
+ :param model: ModelItem or model name
44
+ :param tool_name: Tool name
45
+ :return: True if enabled, False otherwise
46
+ """
47
+ if isinstance(model, str):
48
+ model = self.window.core.models.get(model)
49
+ if not model:
50
+ return False
51
+ if tool_name == "web_search":
52
+ return self.is_web(model)
53
+ return False
54
+
55
+ def is_web(self, model: ModelItem) -> bool:
56
+ """
57
+ Check if web search is enabled for the given provider
58
+
59
+ :param model: ModelItem
60
+ :return: True if web search is enabled, False otherwise
61
+ """
62
+ # at first, check provider-specific config
63
+ cfg_get = self.window.core.config.get
64
+ state = False
65
+ if model.provider == "openai": # native SDK, responses API
66
+ state = cfg_get("remote_tools.web_search", False)
67
+ elif model.provider == "google": # native SDK
68
+ state = cfg_get("remote_tools.google.web_search", False)
69
+ elif model.provider == "anthropic": # native SDK
70
+ state = cfg_get("remote_tools.anthropic.web_search", False)
71
+ elif model.provider == "x_ai": # native SDK
72
+ mode = cfg_get("remote_tools.xai.mode", "auto")
73
+ if mode not in ("auto", "on", "off"):
74
+ mode = "auto"
75
+ if mode == "auto" or mode == "on":
76
+ state = True
77
+
78
+ # if not enabled by default or other provider, then use global config
79
+ if not state:
80
+ state = self.enabled_global["web_search"]
81
+
82
+ return state
83
+
84
+ def update_icons(self):
85
+ """
86
+ Update remote tools icons in chat tabs
87
+ """
88
+ state = self.enabled_global["web_search"]
89
+ if state:
90
+ self.window.ui.nodes['icon.remote_tool.web'].set_icon(":/icons/web_on.svg")
91
+ else:
92
+ self.window.ui.nodes['icon.remote_tool.web'].set_icon(":/icons/web_off.svg")
93
+
94
+ def toggle(self, tool_name: str):
95
+ """
96
+ Toggle remote tool (for global toggle button)
97
+
98
+ :param tool_name: Tool name
99
+ """
100
+ cfg_set = self.window.core.config.set
101
+
102
+ # tool: web search
103
+ if tool_name == "web_search":
104
+ state = not self.enabled_global["web_search"]
105
+ self.enabled_global["web_search"] = state
106
+ cfg_set("remote_tools.global.web_search", state)
107
+ cfg_set("remote_tools.web_search", state)
108
+ cfg_set("remote_tools.google.web_search", state)
109
+ cfg_set("remote_tools.anthropic.web_search", state)
110
+
111
+ # xAI has 3 modes: auto, on, off
112
+ cfg_set("remote_tools.xai.mode", "on" if state else "off")
113
+
114
+ # save config
115
+ self.window.core.config.save()
116
+ self.update_icons()
@@ -6,7 +6,7 @@
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.08.15 23:00:00 #
9
+ # Updated Date: 2025.09.17 05:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Dict
@@ -423,6 +423,7 @@ class Mapping:
423
423
  tooltips['icon.video.capture'] = 'icon.video.capture'
424
424
  tooltips['icon.audio.output'] = 'icon.audio.output'
425
425
  tooltips['icon.audio.input'] = 'icon.audio.input'
426
+ tooltips['icon.remote_tool.web'] = 'icon.remote_tool.web'
426
427
  tooltips['assistant.store.btn.refresh_status'] = 'dialog.assistant.store.btn.refresh_status'
427
428
  tooltips['agent.llama.loop.score'] = 'toolbox.agent.llama.loop.score.tooltip'
428
429
 
@@ -6,13 +6,13 @@
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.08.28 09:00:00 #
9
+ # Updated Date: 2025.09.17 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.events import Event, AppEvent
13
13
  from pygpt_net.core.types import (
14
14
  MODE_ASSISTANT,
15
- MODE_CHAT,
15
+ MODE_CHAT, MODE_AUDIO,
16
16
  )
17
17
  from pygpt_net.item.ctx import CtxItem
18
18
  from pygpt_net.utils import trans
@@ -92,6 +92,9 @@ class Mode:
92
92
  else:
93
93
  c.assistant.select_current()
94
94
 
95
+ elif mode == MODE_AUDIO:
96
+ c.audio.set_muted(False) # un-mute and show audio output icon by default
97
+
95
98
  cfg.set('mode', mode)
96
99
 
97
100
  # reset model and preset at start
@@ -6,7 +6,7 @@
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.05 18:00:00 #
9
+ # Updated Date: 2025.09.17 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import List, Dict, Any, Optional
@@ -14,7 +14,7 @@ from typing import List, Dict, Any, Optional
14
14
  from PySide6.QtGui import QAction
15
15
 
16
16
  from pygpt_net.core.types import (
17
- MODE_AGENT,
17
+ MODE_AGENT, MODE_AUDIO,
18
18
  )
19
19
  from pygpt_net.controller.plugins.presets import Presets
20
20
  from pygpt_net.controller.plugins.settings import Settings
@@ -194,20 +194,46 @@ class Plugins:
194
194
 
195
195
  def toggle(self, id: str):
196
196
  """
197
- Toggle plugin
197
+ Toggle plugin (from menu)
198
198
 
199
199
  :param id: plugin id
200
200
  """
201
201
  if self.window.core.plugins.is_registered(id):
202
202
  if self.is_enabled(id):
203
203
  self.disable(id)
204
+ if id == "audio_output":
205
+ self.window.controller.audio.set_muted(True)
204
206
  else:
205
207
  self.enable(id)
208
+ if id == "audio_output":
209
+ self.window.controller.audio.set_muted(False)
206
210
 
207
211
  self.window.controller.ui.update_tokens()
208
212
  self.window.controller.attachment.update()
209
213
  self.presets.save_current()
210
214
 
215
+ def toggle_audio_output(self):
216
+ """
217
+ Toggle plugin (from icon, audio output only)
218
+ """
219
+ id = "audio_output"
220
+ mode = self.window.core.config.get('mode')
221
+
222
+ if mode == MODE_AUDIO:
223
+ if not self.window.controller.audio.is_muted():
224
+ self.window.controller.audio.set_muted(True)
225
+ else:
226
+ self.window.controller.audio.set_muted(False)
227
+ return
228
+
229
+ if self.window.core.plugins.is_registered(id):
230
+ if self.is_enabled(id):
231
+ self.disable(id)
232
+ else:
233
+ self.enable(id)
234
+
235
+ self.presets.save_current()
236
+
211
237
  def set_by_tab(self, idx: int):
212
238
  """
213
239
  Set current plugin by tab index
@@ -6,7 +6,7 @@
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.08.31 23:00:00 #
9
+ # Updated Date: 2025.09.17 07:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from PySide6.QtCore import Slot, QTimer
@@ -64,6 +64,8 @@ class Realtime:
64
64
 
65
65
  :param event: RealtimeEvent instance
66
66
  """
67
+ is_muted = self.window.controller.audio.is_muted() # global mute state
68
+
67
69
  # check if mode is supported
68
70
  if not self.is_supported() and isinstance(event, RealtimeEvent):
69
71
  event.stop = True # stop further propagation
@@ -75,7 +77,7 @@ class Realtime:
75
77
  if event.name == RealtimeEvent.RT_OUTPUT_AUDIO_DELTA:
76
78
  self.set_idle()
77
79
  payload = event.data.get("payload", None)
78
- if payload:
80
+ if payload and not is_muted: # do not play if muted
79
81
  self.window.core.audio.output.handle_realtime(payload, self.signals)
80
82
 
81
83
  # audio input chunk: send to the active realtime client
@@ -148,7 +150,10 @@ class Realtime:
148
150
 
149
151
  # volume change: update volume in audio output handler
150
152
  elif event.name == RealtimeEvent.RT_OUTPUT_AUDIO_VOLUME_CHANGED:
151
- volume = event.data.get("volume", 1.0)
153
+ if not is_muted:
154
+ volume = event.data.get("volume", 1.0)
155
+ else:
156
+ volume = 0.0
152
157
  self.window.controller.audio.ui.on_output_volume_change(volume)
153
158
 
154
159
  # error: audio output error
@@ -6,7 +6,7 @@
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.01 23:00:00 #
9
+ # Updated Date: 2025.09.17 20:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from pygpt_net.core.types import (
@@ -59,11 +59,17 @@ class Mode:
59
59
  is_audio = mode == MODE_AUDIO
60
60
 
61
61
  if not is_audio:
62
- self.window.ui.nodes['audio.auto_turn'].setVisible(False)
63
- self.window.ui.nodes["audio.loop"].setVisible(False)
62
+ ui_nodes['audio.auto_turn'].setVisible(False)
63
+ ui_nodes["audio.loop"].setVisible(False)
64
+ if not ctrl.plugins.is_enabled('audio_output'):
65
+ ctrl.audio.toggle_output_icon(False)
64
66
  else:
65
- self.window.ui.nodes['audio.auto_turn'].setVisible(True)
66
- self.window.ui.nodes["audio.loop"].setVisible(True)
67
+ ui_nodes['audio.auto_turn'].setVisible(True)
68
+ ui_nodes["audio.loop"].setVisible(True)
69
+ if not ctrl.audio.is_muted():
70
+ ctrl.audio.toggle_output_icon(True)
71
+ else:
72
+ ctrl.audio.toggle_output_icon(False)
67
73
 
68
74
  if not is_assistant:
69
75
  ui_nodes['presets.widget'].setVisible(True)
@@ -189,6 +195,12 @@ class Mode:
189
195
  show = self.are_attachments(mode)
190
196
  ui_tabs['input'].setTabVisible(1, show)
191
197
 
198
+ # remote tools icon visibility
199
+ if not is_image and not is_completion:
200
+ ui_nodes['icon.remote_tool.web'].setVisible(True)
201
+ else:
202
+ ui_nodes['icon.remote_tool.web'].setVisible(False)
203
+
192
204
  ui_tabs['input'].setTabVisible(2, is_assistant)
193
205
  ui_tabs['input'].setTabVisible(3, (not is_assistant) and (not is_image))
194
206
 
@@ -6,9 +6,9 @@
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.08.24 03:00:00 #
9
+ # Updated Date: 2025.09.17 19:00:00 #
10
10
  # ================================================== #
11
- import os
11
+
12
12
  from typing import List, Dict, Any
13
13
 
14
14
  from pygpt_net.core.types import MODE_CHAT
@@ -108,11 +108,18 @@ class Provider:
108
108
  from agents import (
109
109
  OpenAIChatCompletionsModel,
110
110
  )
111
- if model.provider == "openai":
111
+ models = self.window.core.models
112
+ if isinstance(model, str):
113
+ model = models.get(model)
114
+
115
+ model_id = model.id
116
+ if model.provider in ("openai", "azure_openai"):
112
117
  return model.id
113
- else:
114
- args = self.window.core.models.prepare_client_args(MODE_CHAT, model)
115
- return OpenAIChatCompletionsModel(
116
- model=model.id,
117
- openai_client=AsyncOpenAI(**args),
118
- )
118
+ elif model.provider == "open_router":
119
+ model_id = models.get_openrouter_model(model)
120
+
121
+ args = models.prepare_client_args(MODE_CHAT, model)
122
+ return OpenAIChatCompletionsModel(
123
+ model=model_id,
124
+ openai_client=AsyncOpenAI(**args),
125
+ )
@@ -6,7 +6,7 @@
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.12 00:00:00 #
9
+ # Updated Date: 2025.09.17 19:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import copy
@@ -526,6 +526,30 @@ class Models:
526
526
  self.window.core.debug.info("[api] No model provided, using default OpenAI client")
527
527
  return args
528
528
 
529
+ def get_openrouter_model(self, model: ModelItem) -> str:
530
+ """
531
+ Get OpenRouter model by model id
532
+
533
+ :param model: ModelItem
534
+ :return: OpenRouter model id
535
+ """
536
+ if isinstance(model, str):
537
+ model = self.get(model)
538
+ if not model or model.provider != "open_router":
539
+ return model.id if model else None
540
+
541
+ # OpenRouter: add web search remote tool (if enabled)
542
+ # https://openrouter.ai/docs/features/web-search
543
+ model_id = model.id
544
+ is_web = self.window.controller.chat.remote_tools.enabled(model, "web_search") # web search config
545
+ if is_web:
546
+ if not model_id.endswith(":online"):
547
+ model_id += ":online"
548
+ else:
549
+ if model_id.endswith(":online"):
550
+ model_id = model_id.replace(":online", "")
551
+ return model_id
552
+
529
553
  def is_tool_call_allowed(self, mode: str, model: ModelItem) -> bool:
530
554
  """
531
555
  Check if native tool call is allowed for model and mode
@@ -6,7 +6,7 @@
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.16 22:00:00 #
9
+ # Updated Date: 2025.09.17 05:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -1289,6 +1289,8 @@ class Renderer(BaseRenderer):
1289
1289
  :param meta: context meta
1290
1290
  """
1291
1291
  tab = node.get_tab()
1292
+ if tab is None:
1293
+ return
1292
1294
  layout = tab.child.layout()
1293
1295
  tab.unwrap(node)
1294
1296
  self.window.ui.nodes['output'].pop(tab.pid, None)
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.52",
4
- "app.version": "2.6.52",
5
- "updated_at": "2025-09-17T00:00:00"
3
+ "version": "2.6.54",
4
+ "app.version": "2.6.54",
5
+ "updated_at": "2025-09-18T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -97,7 +97,7 @@
97
97
  "api_native_xai": true,
98
98
  "api_proxy": "",
99
99
  "api_use_responses": true,
100
- "api_use_responses_llama": false,
100
+ "api_use_responses_llama": true,
101
101
  "app.env": [
102
102
  {
103
103
  "name": "OLLAMA_API_BASE",
@@ -417,6 +417,7 @@
417
417
  "remote_tools.computer_use.env": "",
418
418
  "remote_tools.file_search": false,
419
419
  "remote_tools.file_search.args": "",
420
+ "remote_tools.global.web_search": true,
420
421
  "remote_tools.google.code_interpreter": false,
421
422
  "remote_tools.google.url_ctx": false,
422
423
  "remote_tools.google.web_search": true,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.52",
4
- "app.version": "2.6.52",
5
- "updated_at": "2025-09-17T08:03:34"
3
+ "version": "2.6.54",
4
+ "app.version": "2.6.54",
5
+ "updated_at": "2025-09-18T00:00:00"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="M819-28 701-146q-48 32-103.5 49T480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-62 17-117.5T146-701L27-820l57-57L876-85l-57 57ZM440-162v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm374-99-58-58q21-37 32.5-77.5T800-480q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v45L261-814q48-31 103-48.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 61-17.5 116T814-261Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#686868"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm-40-82v-78q-33 0-56.5-23.5T360-320v-40L168-552q-3 18-5.5 36t-2.5 36q0 121 79.5 212T440-162Zm276-102q41-45 62.5-100.5T800-480q0-98-54.5-179T600-776v16q0 33-23.5 56.5T520-680h-80v80q0 17-11.5 28.5T400-560h-80v80h240q17 0 28.5 11.5T600-440v120h40q26 0 47 15.5t29 40.5Z"/></svg>
pygpt_net/data/js/app.js CHANGED
@@ -2082,6 +2082,25 @@
2082
2082
  // Streaming renderer (no linkify) – hot path
2083
2083
  this.MD_STREAM = window.markdownit({ html: false, linkify: false, breaks: true, highlight: () => '' });
2084
2084
 
2085
+ //allow local file-like schemes in links/images (markdown-it blocks file:// by default).
2086
+ const installLinkValidator = (md) => {
2087
+ const orig = (md && typeof md.validateLink === 'function') ? md.validateLink.bind(md) : null;
2088
+ md.validateLink = (url) => {
2089
+ try {
2090
+ const s = String(url || '').trim().toLowerCase();
2091
+ if (s.startsWith('file:')) return true; // local files
2092
+ if (s.startsWith('qrc:')) return true; // Qt resources
2093
+ if (s.startsWith('bridge:')) return true; // app bridge scheme
2094
+ if (s.startsWith('blob:')) return true; // blobs
2095
+ if (s.startsWith('data:image/')) return true; // inline images
2096
+ } catch (_) {}
2097
+ return orig ? orig(url) : true;
2098
+ };
2099
+ };
2100
+
2101
+ installLinkValidator(this.MD);
2102
+ installLinkValidator(this.MD_STREAM);
2103
+
2085
2104
  // SAFETY: disable CommonMark "indented code blocks" unless explicitly enabled.
2086
2105
  if (!this.cfg.MD || this.cfg.MD.ALLOW_INDENTED_CODE !== true) {
2087
2106
  try { this.MD.block.ruler.disable('code'); } catch (_) {}
@@ -677,6 +677,7 @@ html_canvas.btn.edit = Quellcode bearbeiten
677
677
  html_canvas.clear.confirm = HTML-Canvas-Ausgabe löschen?
678
678
  icon.audio.input = Audioeingang aktivieren/deaktivieren
679
679
  icon.audio.output = Audioausgang aktivieren/deaktivieren
680
+ icon.remote_tool.web = Websuche (ferngesteuertes Werkzeug, kein lokales Werkzeug)
680
681
  icon.video.capture = Videoaufnahme von der Kamera aktivieren/deaktivieren
681
682
  idx.btn.clear = Index löschen
682
683
  idx.btn.index_all = Alles indizieren
@@ -680,6 +680,7 @@ html_canvas.btn.edit = Edit source
680
680
  html_canvas.clear.confirm = Clear HTML canvas output?
681
681
  icon.audio.input = Enable/disable audio input
682
682
  icon.audio.output = Enable/disable audio output
683
+ icon.remote_tool.web = Web Search (remote tool, not a local tool)
683
684
  icon.video.capture = Enable/disable camera capture
684
685
  idx.btn.clear = Clear index
685
686
  idx.btn.index_all = Index all
@@ -1391,6 +1392,8 @@ settings.render.code_syntax.stream_n_line.desc = Syntax highlight: highlight eve
1391
1392
  settings.render.engine = Rendering engine
1392
1393
  settings.render.memory.limit = Memory Limit
1393
1394
  settings.render.memory.limit.desc = Renderer memory limit; set to 0 to disable. If > 0, the app will try to free memory after the limit is reached. Accepted formats: 3.5GB, 2GB, 2048MB, 1_000_000. Minimum: 2GB.
1395
+ settings.render.msg.user.collapse.px = Auto-collapse user message (px)
1396
+ settings.render.msg.user.collapse.px.desc = Auto-collapse user message after N pixels of height, set to 0 to disable auto-collapse
1394
1397
  settings.render.open_gl = OpenGL hardware acceleration
1395
1398
  settings.render.plain = Disable markdown formatting in output (RAW plain text mode)
1396
1399
  settings.render.web.only.desc = WebEngine / Chromium rendering engine only
@@ -1649,5 +1652,3 @@ vision.capture.manual.captured.success = Image captured from the camera:
1649
1652
  vision.capture.name.prefix = Camera capture:
1650
1653
  vision.capture.options.title = Video capture
1651
1654
  vision.checkbox.tooltip = If checked, the vision model is active. It will be automatically activated upon image upload. You can deactivate it in real-time.
1652
- settings.render.msg.user.collapse.px = Auto-collapse user message (px)
1653
- settings.render.msg.user.collapse.px.desc = Auto-collapse user message after N pixels of height, set to 0 to disable auto-collapse
@@ -678,6 +678,7 @@ html_canvas.btn.edit = Editar código fuente
678
678
  html_canvas.clear.confirm = ¿Limpiar la salida del lienzo HTML?
679
679
  icon.audio.input = Activar/desactivar entrada de audio
680
680
  icon.audio.output = Activar/desactivar salida de audio
681
+ icon.remote_tool.web = Búsqueda en la Web (herramienta remota, no una herramienta local)
681
682
  icon.video.capture = Activar/desactivar captura de vídeo desde la cámara
682
683
  idx.btn.clear = Limpiar índice
683
684
  idx.btn.index_all = Indexar todo