pygpt-net 2.7.6__py3-none-any.whl → 2.7.8__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.
- pygpt_net/CHANGELOG.txt +13 -0
- pygpt_net/__init__.py +3 -3
- pygpt_net/app.py +5 -1
- pygpt_net/controller/assistant/batch.py +2 -2
- pygpt_net/controller/assistant/files.py +7 -6
- pygpt_net/controller/assistant/threads.py +0 -0
- pygpt_net/controller/chat/command.py +0 -0
- pygpt_net/controller/chat/remote_tools.py +3 -9
- pygpt_net/controller/chat/stream.py +2 -2
- pygpt_net/controller/chat/{handler/worker.py → stream_worker.py} +13 -35
- pygpt_net/controller/dialogs/confirm.py +35 -58
- pygpt_net/controller/lang/mapping.py +9 -9
- pygpt_net/controller/remote_store/{google/batch.py → batch.py} +209 -252
- pygpt_net/controller/remote_store/remote_store.py +982 -13
- pygpt_net/core/command/command.py +0 -0
- pygpt_net/core/db/viewer.py +1 -1
- pygpt_net/core/debug/models.py +2 -2
- pygpt_net/core/realtime/worker.py +3 -1
- pygpt_net/{controller/remote_store/google → core/remote_store/anthropic}/__init__.py +0 -1
- pygpt_net/core/remote_store/anthropic/files.py +211 -0
- pygpt_net/core/remote_store/anthropic/store.py +208 -0
- pygpt_net/core/remote_store/openai/store.py +5 -4
- pygpt_net/core/remote_store/remote_store.py +5 -1
- pygpt_net/{controller/remote_store/openai → core/remote_store/xai}/__init__.py +0 -1
- pygpt_net/core/remote_store/xai/files.py +225 -0
- pygpt_net/core/remote_store/xai/store.py +219 -0
- pygpt_net/data/config/config.json +18 -5
- pygpt_net/data/config/models.json +193 -4
- pygpt_net/data/config/settings.json +179 -36
- pygpt_net/data/icons/folder_eye.svg +1 -0
- pygpt_net/data/icons/folder_eye_filled.svg +1 -0
- pygpt_net/data/icons/folder_open.svg +1 -0
- pygpt_net/data/icons/folder_open_filled.svg +1 -0
- pygpt_net/data/locale/locale.de.ini +6 -3
- pygpt_net/data/locale/locale.en.ini +46 -12
- pygpt_net/data/locale/locale.es.ini +6 -3
- pygpt_net/data/locale/locale.fr.ini +6 -3
- pygpt_net/data/locale/locale.it.ini +6 -3
- pygpt_net/data/locale/locale.pl.ini +7 -4
- pygpt_net/data/locale/locale.uk.ini +6 -3
- pygpt_net/data/locale/locale.zh.ini +6 -3
- pygpt_net/icons.qrc +4 -0
- pygpt_net/icons_rc.py +282 -138
- pygpt_net/plugin/cmd_mouse_control/worker.py +2 -1
- pygpt_net/plugin/cmd_mouse_control/worker_sandbox.py +2 -1
- pygpt_net/provider/api/anthropic/__init__.py +10 -3
- pygpt_net/provider/api/anthropic/chat.py +342 -11
- pygpt_net/provider/api/anthropic/computer.py +844 -0
- pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
- pygpt_net/provider/api/anthropic/store.py +307 -0
- pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +99 -10
- pygpt_net/provider/api/anthropic/tools.py +32 -77
- pygpt_net/provider/api/anthropic/utils.py +30 -0
- pygpt_net/{controller/chat/handler → provider/api/anthropic/worker}/__init__.py +0 -0
- pygpt_net/provider/api/anthropic/worker/importer.py +278 -0
- pygpt_net/provider/api/google/chat.py +62 -9
- pygpt_net/provider/api/google/store.py +124 -3
- pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +92 -25
- pygpt_net/provider/api/google/utils.py +185 -0
- pygpt_net/provider/api/google/worker/importer.py +16 -28
- pygpt_net/provider/api/langchain/__init__.py +0 -0
- pygpt_net/{controller/chat/handler/langchain_stream.py → provider/api/langchain/stream.py} +1 -1
- pygpt_net/provider/api/llama_index/__init__.py +0 -0
- pygpt_net/{controller/chat/handler/llamaindex_stream.py → provider/api/llama_index/stream.py} +1 -1
- pygpt_net/provider/api/openai/assistants.py +2 -2
- pygpt_net/provider/api/openai/image.py +2 -2
- pygpt_net/provider/api/openai/store.py +4 -1
- pygpt_net/{controller/chat/handler/openai_stream.py → provider/api/openai/stream.py} +1 -1
- pygpt_net/provider/api/openai/utils.py +69 -3
- pygpt_net/provider/api/openai/worker/importer.py +19 -61
- pygpt_net/provider/api/openai/worker/importer_assistants.py +230 -0
- pygpt_net/provider/api/x_ai/__init__.py +138 -15
- pygpt_net/provider/api/x_ai/audio.py +43 -11
- pygpt_net/provider/api/x_ai/chat.py +92 -4
- pygpt_net/provider/api/x_ai/image.py +149 -47
- pygpt_net/provider/api/x_ai/realtime/__init__.py +12 -0
- pygpt_net/provider/api/x_ai/realtime/client.py +1825 -0
- pygpt_net/provider/api/x_ai/realtime/realtime.py +198 -0
- pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +183 -70
- pygpt_net/provider/api/x_ai/responses.py +507 -0
- pygpt_net/provider/api/x_ai/store.py +610 -0
- pygpt_net/{controller/chat/handler/xai_stream.py → provider/api/x_ai/stream.py} +42 -10
- pygpt_net/provider/api/x_ai/tools.py +59 -8
- pygpt_net/{controller/chat/handler → provider/api/x_ai}/utils.py +1 -2
- pygpt_net/provider/api/x_ai/vision.py +1 -4
- pygpt_net/provider/api/x_ai/worker/importer.py +308 -0
- pygpt_net/provider/audio_input/xai_grok_voice.py +390 -0
- pygpt_net/provider/audio_output/xai_tts.py +325 -0
- pygpt_net/provider/core/config/patch.py +39 -3
- pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +2 -2
- pygpt_net/provider/core/model/patch.py +39 -1
- pygpt_net/tools/image_viewer/tool.py +334 -34
- pygpt_net/tools/image_viewer/ui/dialogs.py +319 -22
- pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
- pygpt_net/tools/text_editor/ui/widgets.py +0 -0
- pygpt_net/ui/dialog/assistant.py +1 -1
- pygpt_net/ui/dialog/plugins.py +13 -5
- pygpt_net/ui/dialog/remote_store.py +552 -0
- pygpt_net/ui/dialogs.py +3 -5
- pygpt_net/ui/layout/ctx/ctx_list.py +58 -7
- pygpt_net/ui/menu/tools.py +6 -13
- pygpt_net/ui/widget/dialog/base.py +16 -5
- pygpt_net/ui/widget/dialog/{remote_store_google.py → remote_store.py} +10 -10
- pygpt_net/ui/widget/element/button.py +4 -4
- pygpt_net/ui/widget/image/display.py +2 -2
- pygpt_net/ui/widget/lists/context.py +2 -2
- pygpt_net/ui/widget/textarea/editor.py +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/METADATA +15 -2
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/RECORD +107 -89
- pygpt_net/controller/remote_store/google/store.py +0 -615
- pygpt_net/controller/remote_store/openai/batch.py +0 -524
- pygpt_net/controller/remote_store/openai/store.py +0 -699
- pygpt_net/ui/dialog/remote_store_google.py +0 -539
- pygpt_net/ui/dialog/remote_store_openai.py +0 -539
- pygpt_net/ui/widget/dialog/remote_store_openai.py +0 -56
- pygpt_net/ui/widget/lists/remote_store_google.py +0 -248
- pygpt_net/ui/widget/lists/remote_store_openai.py +0 -317
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.6.dist-info → pygpt_net-2.7.8.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,198 @@
|
|
|
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: 2026.01.06 20:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from typing import Optional, Dict, Any
|
|
14
|
+
|
|
15
|
+
from pygpt_net.core.bridge import BridgeContext
|
|
16
|
+
from pygpt_net.core.events import RealtimeEvent
|
|
17
|
+
from pygpt_net.core.realtime.options import RealtimeOptions
|
|
18
|
+
from pygpt_net.core.realtime.shared.session import extract_last_session_id
|
|
19
|
+
from pygpt_net.item.model import ModelItem
|
|
20
|
+
from pygpt_net.utils import trans
|
|
21
|
+
|
|
22
|
+
from .client import xAIIRealtimeClient
|
|
23
|
+
|
|
24
|
+
class Realtime:
|
|
25
|
+
|
|
26
|
+
PROVIDER = "x_ai"
|
|
27
|
+
|
|
28
|
+
def __init__(self, window=None):
|
|
29
|
+
"""
|
|
30
|
+
OpenAI API realtime controller
|
|
31
|
+
|
|
32
|
+
:param window: Window instance
|
|
33
|
+
"""
|
|
34
|
+
self.window = window
|
|
35
|
+
self.handler = xAIIRealtimeClient(window)
|
|
36
|
+
self.prev_auto_turn = False
|
|
37
|
+
self.prev_vad_silence = 2000
|
|
38
|
+
self.prev_vad_prefix = 300
|
|
39
|
+
|
|
40
|
+
def begin(
|
|
41
|
+
self,
|
|
42
|
+
context: BridgeContext,
|
|
43
|
+
model: Optional[ModelItem] = None,
|
|
44
|
+
extra: Optional[Dict[str, Any]] = None,
|
|
45
|
+
rt_signals=None
|
|
46
|
+
) -> bool:
|
|
47
|
+
"""
|
|
48
|
+
Begin realtime session if applicable
|
|
49
|
+
|
|
50
|
+
:param context: BridgeContext
|
|
51
|
+
:param model: Optional[ModelItem]
|
|
52
|
+
:param extra: Optional dict with extra parameters
|
|
53
|
+
:param rt_signals: RealtimeSignals
|
|
54
|
+
:return: True if realtime session started, False otherwise
|
|
55
|
+
"""
|
|
56
|
+
mm = context.multimodal_ctx
|
|
57
|
+
audio_bytes = getattr(mm, "audio_data", None) if mm and getattr(mm, "is_audio_input", False) else None
|
|
58
|
+
audio_format = getattr(mm, "audio_format", None) if mm else None
|
|
59
|
+
audio_rate = getattr(mm, "audio_rate", None) if mm else None
|
|
60
|
+
is_debug = self.window.core.config.get("log.realtime", False)
|
|
61
|
+
auto_turn = self.window.core.config.get("audio.input.auto_turn", True)
|
|
62
|
+
opt_vad_silence = self.window.core.config.get("audio.input.vad.silence", 2000)
|
|
63
|
+
opt_vad_prefix = self.window.core.config.get("audio.input.vad.prefix", 300)
|
|
64
|
+
|
|
65
|
+
# setup manager
|
|
66
|
+
self.window.controller.realtime.set_current_active(self.PROVIDER)
|
|
67
|
+
self.window.controller.realtime.set_busy()
|
|
68
|
+
self.handler.set_debug(is_debug)
|
|
69
|
+
|
|
70
|
+
# tools
|
|
71
|
+
tools = []
|
|
72
|
+
'''
|
|
73
|
+
tools = self.window.core.api.xai.tools.prepare(model, context.external_functions)
|
|
74
|
+
'''
|
|
75
|
+
|
|
76
|
+
# remote tools
|
|
77
|
+
remote_tools = []
|
|
78
|
+
'''
|
|
79
|
+
remote_tools = self.window.core.api.openai.remote_tools.append_to_tools(
|
|
80
|
+
mode=context.mode,
|
|
81
|
+
model=model,
|
|
82
|
+
stream=context.stream,
|
|
83
|
+
is_expert_call=context.is_expert_call,
|
|
84
|
+
tools=remote_tools,
|
|
85
|
+
preset=context.preset,
|
|
86
|
+
)
|
|
87
|
+
'''
|
|
88
|
+
|
|
89
|
+
# handle sub-reply (tool results from tool calls)
|
|
90
|
+
if context.ctx.internal:
|
|
91
|
+
if context.ctx.prev_ctx and context.ctx.prev_ctx.extra.get("prev_tool_calls"):
|
|
92
|
+
tool_calls = context.ctx.prev_ctx.extra.get("prev_tool_calls", [])
|
|
93
|
+
tool_call_id = None
|
|
94
|
+
if isinstance(tool_calls, list) and len(tool_calls) > 0:
|
|
95
|
+
tool_call_id = tool_calls[0].get("call_id", "") # get first call_id
|
|
96
|
+
if not tool_call_id:
|
|
97
|
+
tool_call_id = tool_calls[0].get("id", "") # fallback to id
|
|
98
|
+
if tool_call_id:
|
|
99
|
+
tool_results = context.ctx.input
|
|
100
|
+
try:
|
|
101
|
+
tool_results = json.loads(tool_results)
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
self.handler.send_tool_results_sync({
|
|
105
|
+
tool_call_id: tool_results
|
|
106
|
+
})
|
|
107
|
+
self.handler.update_ctx(context.ctx)
|
|
108
|
+
return True # do not start new session, just send tool results
|
|
109
|
+
|
|
110
|
+
# update auto-turn in active session
|
|
111
|
+
if (self.handler.is_session_active()
|
|
112
|
+
and (auto_turn != self.prev_auto_turn
|
|
113
|
+
or opt_vad_silence != self.prev_vad_silence
|
|
114
|
+
or opt_vad_prefix != self.prev_vad_prefix)):
|
|
115
|
+
self.handler.update_session_autoturn_sync(auto_turn, opt_vad_silence, opt_vad_prefix)
|
|
116
|
+
|
|
117
|
+
# if auto-turn is enabled and prompt is empty, update session and context only
|
|
118
|
+
if auto_turn and self.handler.is_session_active() and (context.prompt.strip() == "" or context.prompt == "..."):
|
|
119
|
+
self.handler.update_session_tools_sync(tools, remote_tools)
|
|
120
|
+
self.handler.update_ctx(context.ctx)
|
|
121
|
+
self.window.update_status(trans("speech.listening"))
|
|
122
|
+
return True # do not send new request if session is active
|
|
123
|
+
|
|
124
|
+
# Last session ID
|
|
125
|
+
last_session_id = extract_last_session_id(context.history)
|
|
126
|
+
if is_debug:
|
|
127
|
+
print("[realtime session] Last ID", last_session_id)
|
|
128
|
+
|
|
129
|
+
# Voice
|
|
130
|
+
voice = "ara"
|
|
131
|
+
try:
|
|
132
|
+
v = self.window.core.plugins.get_option("audio_output", "xai_tts_voice")
|
|
133
|
+
if v:
|
|
134
|
+
voice = str(v)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
# Options
|
|
139
|
+
opts = RealtimeOptions(
|
|
140
|
+
provider=self.PROVIDER,
|
|
141
|
+
model=context.model.id,
|
|
142
|
+
system_prompt=context.system_prompt,
|
|
143
|
+
prompt=context.prompt,
|
|
144
|
+
voice=voice,
|
|
145
|
+
audio_data=audio_bytes,
|
|
146
|
+
audio_format=audio_format,
|
|
147
|
+
audio_rate=audio_rate,
|
|
148
|
+
vad="server_vad",
|
|
149
|
+
extra=extra or {},
|
|
150
|
+
tools=tools,
|
|
151
|
+
remote_tools=remote_tools,
|
|
152
|
+
rt_signals=rt_signals,
|
|
153
|
+
rt_session_id=last_session_id,
|
|
154
|
+
auto_turn=auto_turn,
|
|
155
|
+
vad_end_silence_ms=opt_vad_silence,
|
|
156
|
+
vad_prefix_padding_ms=opt_vad_prefix,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Start or append to realtime session via manager
|
|
160
|
+
try:
|
|
161
|
+
if is_debug:
|
|
162
|
+
print("[realtime] Starting session with options:", opts.to_dict())
|
|
163
|
+
rt = self.window.controller.realtime.manager
|
|
164
|
+
rt.start(context.ctx, opts)
|
|
165
|
+
|
|
166
|
+
self.prev_auto_turn = auto_turn
|
|
167
|
+
self.prev_vad_silence = opt_vad_silence
|
|
168
|
+
self.prev_vad_prefix = opt_vad_prefix
|
|
169
|
+
return True
|
|
170
|
+
except Exception as e:
|
|
171
|
+
self.window.core.debug.log(e)
|
|
172
|
+
return False # fallback to non-live path
|
|
173
|
+
|
|
174
|
+
def handle_audio_input(self, event: RealtimeEvent):
|
|
175
|
+
"""
|
|
176
|
+
Handle Realtime audio input event
|
|
177
|
+
|
|
178
|
+
:param event: RealtimeEvent
|
|
179
|
+
"""
|
|
180
|
+
self.handler.rt_handle_audio_input_sync(event)
|
|
181
|
+
|
|
182
|
+
def manual_commit(self):
|
|
183
|
+
"""Manually commit audio input to realtime session"""
|
|
184
|
+
self.handler.force_response_now_sync()
|
|
185
|
+
|
|
186
|
+
def shutdown(self):
|
|
187
|
+
"""Shutdown realtime loops"""
|
|
188
|
+
if self.handler.is_session_active():
|
|
189
|
+
self.handler.close_session_sync()
|
|
190
|
+
try:
|
|
191
|
+
self.handler.stop_loop_sync()
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
def reset(self):
|
|
196
|
+
"""Close realtime session"""
|
|
197
|
+
if self.handler.is_session_active():
|
|
198
|
+
self.handler.close_session_sync()
|
|
@@ -6,27 +6,194 @@
|
|
|
6
6
|
# GitHub: https://github.com/szczyglis-dev/py-gpt #
|
|
7
7
|
# MIT License #
|
|
8
8
|
# Created By : Marcin Szczygliński #
|
|
9
|
-
# Updated Date:
|
|
9
|
+
# Updated Date: 2026.01.06 18: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
|
+
from xai_sdk.tools import collections_search as x_collections_search
|
|
26
|
+
except Exception:
|
|
27
|
+
x_web_search = None
|
|
28
|
+
x_x_search = None
|
|
29
|
+
x_code_execution = None
|
|
30
|
+
x_mcp = None
|
|
31
|
+
|
|
17
32
|
|
|
18
33
|
class Remote:
|
|
19
34
|
def __init__(self, window=None):
|
|
20
35
|
"""
|
|
21
|
-
Live Search builder for xAI:
|
|
36
|
+
Live Search builder for xAI (old, grok-3):
|
|
22
37
|
- Returns a dict with 'sdk' (X-only toggle) and 'http' (full search_parameters).
|
|
23
38
|
- SDK path: native streaming (xai_sdk.chat.stream()) works only for basic X search (no advanced filters).
|
|
24
39
|
- HTTP path: full Live Search (web/news/rss/X with filters), streaming via SSE.
|
|
25
40
|
|
|
41
|
+
Server-side Tools builder for xAI (newer SDKs, Responses API):
|
|
42
|
+
- Builds xAI SDK tool objects for web_search, x_search, code_execution, MCP.
|
|
43
|
+
- Returns include flags, max_turns and use_encrypted_content settings.
|
|
44
|
+
|
|
26
45
|
:param window: Window instance
|
|
27
46
|
"""
|
|
28
47
|
self.window = window
|
|
29
48
|
|
|
49
|
+
def build_for_chat(self, model: ModelItem = None, stream: bool = False) -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Build server-side tools and options for Chat Responses.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
{
|
|
55
|
+
"tools": [ ... xai_sdk.tools.* ... ],
|
|
56
|
+
"include": [ ... ],
|
|
57
|
+
"use_encrypted_content": bool,
|
|
58
|
+
"max_turns": Optional[int],
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
cfg = self.window.core.config
|
|
62
|
+
include: List[str] = []
|
|
63
|
+
tools: List[object] = []
|
|
64
|
+
|
|
65
|
+
# global remote tools switch
|
|
66
|
+
is_web_enabled = self.window.controller.chat.remote_tools.enabled(model, "web_search")
|
|
67
|
+
is_x_enabled = bool(cfg.get("remote_tools.xai.x_search", False))
|
|
68
|
+
is_code_enabled = bool(cfg.get("remote_tools.xai.code_execution", False))
|
|
69
|
+
is_mcp_enabled = bool(cfg.get("remote_tools.xai.mcp", False))
|
|
70
|
+
|
|
71
|
+
# include flags
|
|
72
|
+
if stream:
|
|
73
|
+
include.append("verbose_streaming")
|
|
74
|
+
if bool(cfg.get("remote_tools.xai.inline_citations", True)):
|
|
75
|
+
include.append("inline_citations")
|
|
76
|
+
if bool(cfg.get("remote_tools.xai.include_code_output", True)):
|
|
77
|
+
include.append("code_execution_call_output")
|
|
78
|
+
|
|
79
|
+
# use_encrypted_content
|
|
80
|
+
use_encrypted = bool(cfg.get("remote_tools.xai.use_encrypted_content", False))
|
|
81
|
+
|
|
82
|
+
# optional max_turns
|
|
83
|
+
max_turns = None
|
|
84
|
+
try:
|
|
85
|
+
mt = cfg.get("remote_tools.xai.max_turns")
|
|
86
|
+
if isinstance(mt, int) and mt > 0:
|
|
87
|
+
max_turns = int(mt)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# WEB SEARCH
|
|
92
|
+
if is_web_enabled and x_web_search is not None:
|
|
93
|
+
kwargs: Dict[str, Any] = {}
|
|
94
|
+
enable_img = bool(cfg.get("remote_tools.xai.web.enable_image_understanding", False))
|
|
95
|
+
'''
|
|
96
|
+
allowed = self._as_list(cfg.get("remote_tools.xai.web.allowed_websites"), 5)
|
|
97
|
+
excluded = self._as_list(cfg.get("remote_tools.xai.web.excluded_websites"), 5)
|
|
98
|
+
if allowed and not excluded:
|
|
99
|
+
kwargs["allowed_domains"] = allowed
|
|
100
|
+
elif excluded and not allowed:
|
|
101
|
+
kwargs["excluded_domains"] = excluded
|
|
102
|
+
'''
|
|
103
|
+
if enable_img:
|
|
104
|
+
kwargs["enable_image_understanding"] = True
|
|
105
|
+
try:
|
|
106
|
+
tools.append(x_web_search(**kwargs))
|
|
107
|
+
except Exception:
|
|
108
|
+
tools.append(x_web_search())
|
|
109
|
+
|
|
110
|
+
# X SEARCH
|
|
111
|
+
if is_x_enabled and x_x_search is not None:
|
|
112
|
+
kwargs: Dict[str, Any] = {}
|
|
113
|
+
'''
|
|
114
|
+
inc = self._as_list(cfg.get("remote_tools.xai.x.included_handles"), 10)
|
|
115
|
+
exc = self._as_list(cfg.get("remote_tools.xai.x.excluded_handles"), 10)
|
|
116
|
+
if inc and not exc:
|
|
117
|
+
kwargs["allowed_x_handles"] = inc
|
|
118
|
+
elif exc and not inc:
|
|
119
|
+
kwargs["excluded_x_handles"] = exc
|
|
120
|
+
'''
|
|
121
|
+
# optional date range filters (YYYY-MM-DD)
|
|
122
|
+
for k_in, k_out in (("remote_tools.xai.from_date", "from_date"),
|
|
123
|
+
("remote_tools.xai.to_date", "to_date")):
|
|
124
|
+
v = cfg.get(k_in)
|
|
125
|
+
if isinstance(v, str) and v.strip():
|
|
126
|
+
kwargs[k_out] = v.strip()
|
|
127
|
+
|
|
128
|
+
if bool(cfg.get("remote_tools.xai.x.enable_image_understanding", False)):
|
|
129
|
+
kwargs["enable_image_understanding"] = True
|
|
130
|
+
if bool(cfg.get("remote_tools.xai.x.enable_video_understanding", False)):
|
|
131
|
+
kwargs["enable_video_understanding"] = True
|
|
132
|
+
|
|
133
|
+
# optional favorites/views filters (supported by live search)
|
|
134
|
+
try:
|
|
135
|
+
favs = cfg.get("remote_tools.xai.x.min_favs")
|
|
136
|
+
if isinstance(favs, int) and favs > 0:
|
|
137
|
+
kwargs["post_favorite_count"] = int(favs)
|
|
138
|
+
except Exception:
|
|
139
|
+
pass
|
|
140
|
+
try:
|
|
141
|
+
views = cfg.get("remote_tools.xai.x.min_views")
|
|
142
|
+
if isinstance(views, int) and views > 0:
|
|
143
|
+
kwargs["post_view_count"] = int(views)
|
|
144
|
+
except Exception:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
tools.append(x_x_search(**kwargs))
|
|
149
|
+
except Exception:
|
|
150
|
+
tools.append(x_x_search())
|
|
151
|
+
|
|
152
|
+
# CODE EXECUTION
|
|
153
|
+
if is_code_enabled and x_code_execution is not None:
|
|
154
|
+
try:
|
|
155
|
+
tools.append(x_code_execution())
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
# MCP
|
|
160
|
+
if is_mcp_enabled and x_mcp is not None:
|
|
161
|
+
kwargs = {}
|
|
162
|
+
mcp_config = cfg.get("remote_tools.xai.mcp.args", "")
|
|
163
|
+
if mcp_config:
|
|
164
|
+
try:
|
|
165
|
+
kwargs = json.loads(mcp_config)
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
try:
|
|
169
|
+
tools.append(x_mcp(**kwargs))
|
|
170
|
+
except Exception:
|
|
171
|
+
pass
|
|
172
|
+
|
|
173
|
+
# COLLECTIONS SEARCH
|
|
174
|
+
is_collections_enabled = bool(cfg.get("remote_tools.xai.collections", False))
|
|
175
|
+
ids = cfg.get("remote_tools.xai.collections.args", "")
|
|
176
|
+
ids_list = []
|
|
177
|
+
if ids:
|
|
178
|
+
try:
|
|
179
|
+
ids_list = [s.strip() for s in ids.split(",") if s.strip()]
|
|
180
|
+
except Exception:
|
|
181
|
+
pass
|
|
182
|
+
if is_collections_enabled and ids_list:
|
|
183
|
+
try:
|
|
184
|
+
tools.append(x_collections_search(
|
|
185
|
+
collection_ids=ids_list
|
|
186
|
+
))
|
|
187
|
+
except Exception:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
"tools": tools,
|
|
192
|
+
"include": include,
|
|
193
|
+
"use_encrypted_content": use_encrypted,
|
|
194
|
+
"max_turns": max_turns,
|
|
195
|
+
}
|
|
196
|
+
|
|
30
197
|
def build_remote_tools(self, model: ModelItem = None) -> Dict[str, Any]:
|
|
31
198
|
"""
|
|
32
199
|
Return live-search config for xAI:
|
|
@@ -58,38 +225,17 @@ class Remote:
|
|
|
58
225
|
"""
|
|
59
226
|
cfg = self.window.core.config
|
|
60
227
|
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
|
|
228
|
+
mode = "on" if is_web else "off"
|
|
69
229
|
|
|
70
230
|
# sources toggles
|
|
71
|
-
s_web =
|
|
72
|
-
s_x =
|
|
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)
|
|
231
|
+
s_web = bool(cfg.get("remote_tools.xai.web_search", False))
|
|
232
|
+
s_x = bool(cfg.get("remote_tools.xai.x_search", False))
|
|
85
233
|
|
|
86
234
|
adv_x_incl = self._has_list(cfg.get("remote_tools.xai.x.included_handles"))
|
|
87
235
|
adv_x_excl = self._has_list(cfg.get("remote_tools.xai.x.excluded_handles"))
|
|
88
236
|
adv_x_favs = self._has_int(cfg.get("remote_tools.xai.x.min_favs"))
|
|
89
237
|
adv_x_views = self._has_int(cfg.get("remote_tools.xai.x.min_views"))
|
|
90
238
|
|
|
91
|
-
adv_rss_link = self._has_str(cfg.get("remote_tools.xai.rss.link"))
|
|
92
|
-
|
|
93
239
|
adv_from = self._has_str(cfg.get("remote_tools.xai.from_date"))
|
|
94
240
|
adv_to = self._has_str(cfg.get("remote_tools.xai.to_date"))
|
|
95
241
|
|
|
@@ -97,7 +243,7 @@ class Remote:
|
|
|
97
243
|
adv_return_cits = cfg.get("remote_tools.xai.return_citations", True) is not True # different than default?
|
|
98
244
|
|
|
99
245
|
# 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
|
|
246
|
+
x_only = s_x and not s_web
|
|
101
247
|
x_filters = any([adv_x_incl, adv_x_excl, adv_x_favs, adv_x_views])
|
|
102
248
|
sdk_enabled = (mode != "off") and x_only and not any([
|
|
103
249
|
x_filters, adv_from, adv_to, adv_max_results, adv_return_cits
|
|
@@ -110,10 +256,10 @@ class Remote:
|
|
|
110
256
|
|
|
111
257
|
need_http = (mode != "off") and (
|
|
112
258
|
not sdk_enabled or # advanced X filters or other sources/date/results/citations
|
|
113
|
-
s_web
|
|
259
|
+
s_web
|
|
114
260
|
)
|
|
115
261
|
if need_http:
|
|
116
|
-
http_params = self._build_http_params(cfg, mode, s_web, s_x
|
|
262
|
+
http_params = self._build_http_params(cfg, mode, s_web, s_x)
|
|
117
263
|
http_reason = "advanced_sources_or_filters"
|
|
118
264
|
|
|
119
265
|
return {
|
|
@@ -131,8 +277,6 @@ class Remote:
|
|
|
131
277
|
mode: str,
|
|
132
278
|
s_web: bool,
|
|
133
279
|
s_x: bool,
|
|
134
|
-
s_news: bool,
|
|
135
|
-
s_rss: bool
|
|
136
280
|
) -> dict:
|
|
137
281
|
"""
|
|
138
282
|
Build search_parameters for Chat Completions (HTTP).
|
|
@@ -141,8 +285,6 @@ class Remote:
|
|
|
141
285
|
:param mode: "auto"|"on"|"off"
|
|
142
286
|
:param s_web: Include web search
|
|
143
287
|
:param s_x: Include X search
|
|
144
|
-
:param s_news: Include news search
|
|
145
|
-
:param s_rss: Include RSS search
|
|
146
288
|
:return: search_parameters dict
|
|
147
289
|
"""
|
|
148
290
|
params: Dict[str, Any] = {"mode": mode}
|
|
@@ -196,41 +338,12 @@ class Remote:
|
|
|
196
338
|
xsrc["post_view_count"] = int(views)
|
|
197
339
|
sources.append(xsrc)
|
|
198
340
|
|
|
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
341
|
if sources:
|
|
218
342
|
params["sources"] = sources
|
|
219
343
|
|
|
220
344
|
return params
|
|
221
345
|
|
|
222
|
-
|
|
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
|
|
346
|
+
# ---------- helpers ----------
|
|
234
347
|
|
|
235
348
|
def _has_str(self, v) -> bool:
|
|
236
349
|
"""
|
|
@@ -250,14 +363,14 @@ class Remote:
|
|
|
250
363
|
"""
|
|
251
364
|
return isinstance(v, int) and v > 0
|
|
252
365
|
|
|
253
|
-
def
|
|
254
|
-
|
|
255
|
-
|
|
366
|
+
def _has_list(self, v) -> bool:
|
|
367
|
+
if v is None:
|
|
368
|
+
return False
|
|
369
|
+
if isinstance(v, (list, tuple)):
|
|
370
|
+
return len([x for x in v if str(x).strip()]) > 0
|
|
371
|
+
return len([x for x in str(v).split(",") if x.strip()]) > 0
|
|
256
372
|
|
|
257
|
-
|
|
258
|
-
:param limit: limit number of items
|
|
259
|
-
:return: List of strings
|
|
260
|
-
"""
|
|
373
|
+
def _as_list(self, v, limit: int) -> List[str]:
|
|
261
374
|
if v is None:
|
|
262
375
|
return []
|
|
263
376
|
if isinstance(v, (list, tuple)):
|