pygpt-net 2.7.5__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.
- pygpt_net/CHANGELOG.txt +14 -0
- pygpt_net/__init__.py +4 -4
- 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} +20 -64
- pygpt_net/controller/debug/fixtures.py +3 -2
- pygpt_net/controller/files/files.py +65 -4
- pygpt_net/core/debug/models.py +2 -2
- pygpt_net/core/filesystem/url.py +4 -1
- pygpt_net/core/render/web/body.py +3 -2
- pygpt_net/core/types/chunk.py +27 -0
- pygpt_net/data/config/config.json +14 -4
- pygpt_net/data/config/models.json +192 -4
- pygpt_net/data/config/settings.json +126 -36
- pygpt_net/data/js/app/template.js +1 -1
- pygpt_net/data/js/app.min.js +2 -2
- pygpt_net/data/locale/locale.de.ini +5 -0
- pygpt_net/data/locale/locale.en.ini +35 -8
- pygpt_net/data/locale/locale.es.ini +5 -0
- pygpt_net/data/locale/locale.fr.ini +5 -0
- pygpt_net/data/locale/locale.it.ini +5 -0
- pygpt_net/data/locale/locale.pl.ini +5 -0
- pygpt_net/data/locale/locale.uk.ini +5 -0
- pygpt_net/data/locale/locale.zh.ini +5 -0
- pygpt_net/data/locale/plugin.cmd_mouse_control.en.ini +2 -2
- pygpt_net/item/ctx.py +3 -5
- pygpt_net/js_rc.py +2449 -2447
- pygpt_net/plugin/cmd_mouse_control/config.py +8 -7
- pygpt_net/plugin/cmd_mouse_control/plugin.py +3 -4
- 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 +16 -9
- pygpt_net/provider/api/anthropic/chat.py +259 -11
- pygpt_net/provider/api/anthropic/computer.py +844 -0
- pygpt_net/provider/api/anthropic/remote_tools.py +172 -0
- pygpt_net/{controller/chat/handler/anthropic_stream.py → provider/api/anthropic/stream.py} +24 -10
- pygpt_net/provider/api/anthropic/tools.py +32 -77
- pygpt_net/provider/api/anthropic/utils.py +30 -0
- pygpt_net/provider/api/google/__init__.py +6 -5
- pygpt_net/provider/api/google/chat.py +3 -8
- pygpt_net/{controller/chat/handler/google_stream.py → provider/api/google/stream.py} +1 -1
- pygpt_net/provider/api/google/utils.py +185 -0
- pygpt_net/{controller/chat/handler → 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/__init__.py +7 -3
- pygpt_net/provider/api/openai/image.py +2 -2
- pygpt_net/provider/api/openai/responses.py +0 -0
- 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/x_ai/__init__.py +117 -17
- pygpt_net/provider/api/x_ai/chat.py +272 -102
- pygpt_net/provider/api/x_ai/image.py +149 -47
- pygpt_net/provider/api/x_ai/{remote.py → remote_tools.py} +165 -70
- pygpt_net/provider/api/x_ai/responses.py +507 -0
- pygpt_net/provider/api/x_ai/stream.py +715 -0
- 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/core/config/patch.py +22 -1
- pygpt_net/provider/core/model/patch.py +26 -1
- pygpt_net/tools/image_viewer/ui/dialogs.py +300 -13
- pygpt_net/tools/text_editor/ui/dialogs.py +3 -2
- pygpt_net/tools/text_editor/ui/widgets.py +5 -1
- pygpt_net/ui/base/context_menu.py +44 -1
- pygpt_net/ui/layout/toolbox/indexes.py +22 -19
- pygpt_net/ui/layout/toolbox/model.py +28 -5
- pygpt_net/ui/widget/dialog/base.py +16 -5
- pygpt_net/ui/widget/image/display.py +25 -8
- pygpt_net/ui/widget/tabs/output.py +9 -1
- pygpt_net/ui/widget/textarea/editor.py +14 -1
- pygpt_net/ui/widget/textarea/input.py +20 -7
- pygpt_net/ui/widget/textarea/notepad.py +24 -1
- pygpt_net/ui/widget/textarea/output.py +23 -1
- pygpt_net/ui/widget/textarea/web.py +16 -1
- {pygpt_net-2.7.5.dist-info → pygpt_net-2.7.7.dist-info}/METADATA +16 -2
- {pygpt_net-2.7.5.dist-info → pygpt_net-2.7.7.dist-info}/RECORD +80 -73
- pygpt_net/controller/chat/handler/xai_stream.py +0 -135
- {pygpt_net-2.7.5.dist-info → pygpt_net-2.7.7.dist-info}/LICENSE +0 -0
- {pygpt_net-2.7.5.dist-info → pygpt_net-2.7.7.dist-info}/WHEEL +0 -0
- {pygpt_net-2.7.5.dist-info → pygpt_net-2.7.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,172 @@
|
|
|
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.05 20:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from typing import List, Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
from pygpt_net.item.model import ModelItem
|
|
16
|
+
|
|
17
|
+
class RemoteTools:
|
|
18
|
+
def __init__(self, window=None):
|
|
19
|
+
"""
|
|
20
|
+
Remote tools mapper for Anthropic Messages API.
|
|
21
|
+
|
|
22
|
+
:param window: Window instance
|
|
23
|
+
"""
|
|
24
|
+
self.window = window
|
|
25
|
+
|
|
26
|
+
def build_remote_tools(self, model: ModelItem = None) -> List[dict]:
|
|
27
|
+
"""
|
|
28
|
+
Build Anthropic server tools (remote tools) based on config flags.
|
|
29
|
+
Supports: Web Search, Code Execution, Web Fetch, Tool Search, MCP toolset.
|
|
30
|
+
|
|
31
|
+
Returns a list of tool dicts to be appended to 'tools' in messages.create.
|
|
32
|
+
|
|
33
|
+
:param model: ModelItem
|
|
34
|
+
:return: List of remote tool dicts
|
|
35
|
+
"""
|
|
36
|
+
cfg = self.window.core.config
|
|
37
|
+
tools: List[dict] = []
|
|
38
|
+
|
|
39
|
+
# keep compatibility with previous models that had no remote tool support
|
|
40
|
+
if model and model.id and model.id.startswith("claude-3-5"):
|
|
41
|
+
# remote tool availability on 3.5 models varies; previous behavior was to skip
|
|
42
|
+
return tools
|
|
43
|
+
|
|
44
|
+
# Helper: bool from config with provider-specific fallback
|
|
45
|
+
def cfg_bool(*keys: str, default: bool = False) -> bool:
|
|
46
|
+
for k in keys:
|
|
47
|
+
v = cfg.get(k)
|
|
48
|
+
if isinstance(v, bool):
|
|
49
|
+
return v
|
|
50
|
+
return default
|
|
51
|
+
|
|
52
|
+
def parse_csv_list(key: str) -> list:
|
|
53
|
+
raw = cfg.get(key, "")
|
|
54
|
+
if not raw:
|
|
55
|
+
return []
|
|
56
|
+
if isinstance(raw, list):
|
|
57
|
+
return [str(x).strip() for x in raw if str(x).strip()]
|
|
58
|
+
return [s.strip() for s in str(raw).split(",") if s.strip()]
|
|
59
|
+
|
|
60
|
+
# --- Web Search (server tool) ---
|
|
61
|
+
is_web = self.window.controller.chat.remote_tools.enabled(model, "web_search")
|
|
62
|
+
if is_web:
|
|
63
|
+
ttype = cfg.get("remote_tools.anthropic.web_search.type", "web_search_20250305")
|
|
64
|
+
tname = "web_search"
|
|
65
|
+
tool_def: Dict[str, Any] = {
|
|
66
|
+
"type": ttype,
|
|
67
|
+
"name": tname,
|
|
68
|
+
}
|
|
69
|
+
max_uses = cfg.get("remote_tools.anthropic.web_search.max_uses")
|
|
70
|
+
if isinstance(max_uses, int) and max_uses > 0:
|
|
71
|
+
tool_def["max_uses"] = max_uses
|
|
72
|
+
allowed = parse_csv_list("remote_tools.anthropic.web_search.allowed_domains")
|
|
73
|
+
blocked = parse_csv_list("remote_tools.anthropic.web_search.blocked_domains")
|
|
74
|
+
if allowed:
|
|
75
|
+
tool_def["allowed_domains"] = allowed
|
|
76
|
+
elif blocked:
|
|
77
|
+
tool_def["blocked_domains"] = blocked
|
|
78
|
+
loc_city = cfg.get("remote_tools.anthropic.web_search.user_location.city")
|
|
79
|
+
loc_region = cfg.get("remote_tools.anthropic.web_search.user_location.region")
|
|
80
|
+
loc_country = cfg.get("remote_tools.anthropic.web_search.user_location.country")
|
|
81
|
+
loc_tz = cfg.get("remote_tools.anthropic.web_search.user_location.timezone")
|
|
82
|
+
if any([loc_city, loc_region, loc_country, loc_tz]):
|
|
83
|
+
tool_def["user_location"] = {
|
|
84
|
+
"type": "approximate",
|
|
85
|
+
"city": str(loc_city) if loc_city else None,
|
|
86
|
+
"region": str(loc_region) if loc_region else None,
|
|
87
|
+
"country": str(loc_country) if loc_country else None,
|
|
88
|
+
"timezone": str(loc_tz) if loc_tz else None,
|
|
89
|
+
}
|
|
90
|
+
tool_def["user_location"] = {k: v for k, v in tool_def["user_location"].items() if v is not None}
|
|
91
|
+
tools.append(tool_def)
|
|
92
|
+
|
|
93
|
+
# --- Code Execution (server tool) ---
|
|
94
|
+
is_code_exec = cfg_bool("remote_tools.anthropic.code_execution", default=False)
|
|
95
|
+
if is_code_exec:
|
|
96
|
+
tools.append({
|
|
97
|
+
"type": "code_execution_20250825",
|
|
98
|
+
"name": "code_execution",
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
# --- Web Fetch (server tool) ---
|
|
102
|
+
is_web_fetch = cfg_bool("remote_tools.anthropic.web_fetch", default=False)
|
|
103
|
+
if is_web_fetch:
|
|
104
|
+
fetch_def: Dict[str, Any] = {
|
|
105
|
+
"type": "web_fetch_20250910",
|
|
106
|
+
"name": "web_fetch",
|
|
107
|
+
}
|
|
108
|
+
max_uses = cfg.get("remote_tools.anthropic.web_fetch.max_uses")
|
|
109
|
+
if isinstance(max_uses, int) and max_uses > 0:
|
|
110
|
+
fetch_def["max_uses"] = max_uses
|
|
111
|
+
allowed = parse_csv_list("remote_tools.anthropic.web_fetch.allowed_domains")
|
|
112
|
+
blocked = parse_csv_list("remote_tools.anthropic.web_fetch.blocked_domains")
|
|
113
|
+
if allowed:
|
|
114
|
+
fetch_def["allowed_domains"] = allowed
|
|
115
|
+
elif blocked:
|
|
116
|
+
fetch_def["blocked_domains"] = blocked
|
|
117
|
+
citations_enabled = cfg_bool("remote_tools.anthropic.web_fetch.citations.enabled", default=True)
|
|
118
|
+
if citations_enabled:
|
|
119
|
+
fetch_def["citations"] = {"enabled": True}
|
|
120
|
+
max_content_tokens = cfg.get("remote_tools.anthropic.web_fetch.max_content_tokens")
|
|
121
|
+
if isinstance(max_content_tokens, int) and max_content_tokens > 0:
|
|
122
|
+
fetch_def["max_content_tokens"] = max_content_tokens
|
|
123
|
+
tools.append(fetch_def)
|
|
124
|
+
|
|
125
|
+
# --- Tool Search (server tool) ---
|
|
126
|
+
"""
|
|
127
|
+
is_tool_search = cfg_bool("remote_tools.anthropic.tool_search", default=False)
|
|
128
|
+
if is_tool_search:
|
|
129
|
+
variant = (cfg.get("remote_tools.anthropic.tool_search.variant")
|
|
130
|
+
or cfg.get("remote_tools.tool_search.variant") or "regex")
|
|
131
|
+
# accept full type as well
|
|
132
|
+
raw_type = str(cfg.get("remote_tools.anthropic.tool_search.type")
|
|
133
|
+
or cfg.get("remote_tools.tool_search.type") or "").strip()
|
|
134
|
+
if raw_type.startswith("tool_search_tool_"):
|
|
135
|
+
ttype = raw_type
|
|
136
|
+
tname = "tool_search_tool_regex" if "regex" in raw_type else "tool_search_tool_bm25"
|
|
137
|
+
else:
|
|
138
|
+
if str(variant).lower() == "bm25":
|
|
139
|
+
ttype = "tool_search_tool_bm25_20251119"
|
|
140
|
+
tname = "tool_search_tool_bm25"
|
|
141
|
+
else:
|
|
142
|
+
ttype = "tool_search_tool_regex_20251119"
|
|
143
|
+
tname = "tool_search_tool_regex"
|
|
144
|
+
tools.append({
|
|
145
|
+
"type": ttype,
|
|
146
|
+
"name": tname,
|
|
147
|
+
})
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
# --- MCP toolset (server-side tool catalog from MCP servers) ---
|
|
151
|
+
is_mcp = cfg_bool("remote_tools.anthropic.mcp", default=False)
|
|
152
|
+
if is_mcp:
|
|
153
|
+
raw_tools = cfg.get("remote_tools.anthropic.mcp.tools")
|
|
154
|
+
if raw_tools:
|
|
155
|
+
try:
|
|
156
|
+
if isinstance(raw_tools, (list, dict)):
|
|
157
|
+
mcp_tools = raw_tools
|
|
158
|
+
else:
|
|
159
|
+
mcp_tools = json.loads(raw_tools)
|
|
160
|
+
# ensure list
|
|
161
|
+
if isinstance(mcp_tools, dict):
|
|
162
|
+
mcp_tools = [mcp_tools]
|
|
163
|
+
for t in mcp_tools:
|
|
164
|
+
if isinstance(t, dict):
|
|
165
|
+
# default type if not set
|
|
166
|
+
if "type" not in t:
|
|
167
|
+
t["type"] = "mcp_toolset"
|
|
168
|
+
tools.append(t)
|
|
169
|
+
except Exception:
|
|
170
|
+
pass # ignore invalid JSON to avoid breaking existing flows
|
|
171
|
+
|
|
172
|
+
return tools
|
|
@@ -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:
|
|
9
|
+
# Updated Date: 2026.01.05 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import io
|
|
@@ -28,6 +28,18 @@ def process_anthropic_chunk(ctx, core, state, chunk) -> Optional[str]:
|
|
|
28
28
|
state.usage_vendor = "anthropic"
|
|
29
29
|
etype = str(getattr(chunk, "type", "") or "")
|
|
30
30
|
response: Optional[str] = None
|
|
31
|
+
is_computer_call = False
|
|
32
|
+
|
|
33
|
+
# Computer Use: translate Anthropic 'computer' tool_use stream into plugin calls
|
|
34
|
+
try:
|
|
35
|
+
tool_calls, has_calls = core.api.anthropic.computer.handle_stream_chunk(ctx, chunk, state.tool_calls)
|
|
36
|
+
state.tool_calls = tool_calls
|
|
37
|
+
if has_calls:
|
|
38
|
+
is_computer_call = True
|
|
39
|
+
state.force_func_call = True
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
31
43
|
|
|
32
44
|
# --- Top-level delta objects (when SDK yields deltas directly) ---
|
|
33
45
|
if etype == "text_delta":
|
|
@@ -37,7 +49,7 @@ def process_anthropic_chunk(ctx, core, state, chunk) -> Optional[str]:
|
|
|
37
49
|
if etype == "thinking_delta":
|
|
38
50
|
return None
|
|
39
51
|
|
|
40
|
-
if etype == "input_json_delta":
|
|
52
|
+
if etype == "input_json_delta" and not is_computer_call:
|
|
41
53
|
pj = getattr(chunk, "partial_json", "") or ""
|
|
42
54
|
buf = state.fn_args_buffers.get("__anthropic_last__")
|
|
43
55
|
if buf is None:
|
|
@@ -64,18 +76,20 @@ def process_anthropic_chunk(ctx, core, state, chunk) -> Optional[str]:
|
|
|
64
76
|
pass
|
|
65
77
|
return None
|
|
66
78
|
|
|
67
|
-
if etype == "content_block_start":
|
|
79
|
+
if etype == "content_block_start" and not is_computer_call:
|
|
68
80
|
try:
|
|
69
81
|
cb = getattr(chunk, "content_block", None)
|
|
70
82
|
if cb and getattr(cb, "type", "") == "tool_use":
|
|
71
83
|
idx = getattr(chunk, "index", 0) or 0
|
|
72
84
|
tid = getattr(cb, "id", "") or ""
|
|
73
85
|
name = getattr(cb, "name", "") or ""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
# Skip generic function-call for Anthropic Computer Use; the adapter will emit computer_call items.
|
|
87
|
+
if name not in {"computer", "computer.use", "anthropic/computer", "computer_use", "computer-use"}:
|
|
88
|
+
state.tool_calls.append({
|
|
89
|
+
"id": tid,
|
|
90
|
+
"type": "function",
|
|
91
|
+
"function": {"name": name, "arguments": ""}
|
|
92
|
+
})
|
|
79
93
|
state.fn_args_buffers[str(idx)] = io.StringIO()
|
|
80
94
|
state.fn_args_buffers["__anthropic_last__"] = state.fn_args_buffers[str(idx)]
|
|
81
95
|
except Exception:
|
|
@@ -97,7 +111,7 @@ def process_anthropic_chunk(ctx, core, state, chunk) -> Optional[str]:
|
|
|
97
111
|
|
|
98
112
|
return None
|
|
99
113
|
|
|
100
|
-
if etype == "content_block_delta":
|
|
114
|
+
if etype == "content_block_delta" and not is_computer_call:
|
|
101
115
|
try:
|
|
102
116
|
delta = getattr(chunk, "delta", None)
|
|
103
117
|
if not delta:
|
|
@@ -125,7 +139,7 @@ def process_anthropic_chunk(ctx, core, state, chunk) -> Optional[str]:
|
|
|
125
139
|
pass
|
|
126
140
|
return response
|
|
127
141
|
|
|
128
|
-
if etype == "content_block_stop":
|
|
142
|
+
if etype == "content_block_stop" and not is_computer_call:
|
|
129
143
|
try:
|
|
130
144
|
idx = str(getattr(chunk, "index", 0) or 0)
|
|
131
145
|
buf = state.fn_args_buffers.pop(idx, 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:
|
|
9
|
+
# Updated Date: 2026.01.05 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import json
|
|
@@ -152,78 +152,16 @@ class Tools:
|
|
|
152
152
|
if not params.get("type"):
|
|
153
153
|
params["type"] = "object"
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
# pass through tool as client tool
|
|
156
|
+
tool_def = {
|
|
156
157
|
"name": name,
|
|
157
158
|
"description": desc,
|
|
158
159
|
"input_schema": params or {"type": "object"},
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
return tools
|
|
162
|
-
|
|
163
|
-
def build_remote_tools(self, model: ModelItem = None) -> List[dict]:
|
|
164
|
-
"""
|
|
165
|
-
Build Anthropic server tools (remote tools) based on config flags.
|
|
166
|
-
Currently supports: Web Search tool.
|
|
167
|
-
|
|
168
|
-
Returns a list of tool dicts to be appended to 'tools' in messages.create.
|
|
169
|
-
|
|
170
|
-
:param model: ModelItem
|
|
171
|
-
:return: List of remote tool dicts
|
|
172
|
-
"""
|
|
173
|
-
cfg = self.window.core.config
|
|
174
|
-
tools: List[dict] = []
|
|
175
|
-
|
|
176
|
-
# sonnet-3.5 is not supported
|
|
177
|
-
if model and model.id and model.id.startswith("claude-3-5"):
|
|
178
|
-
return tools
|
|
179
|
-
|
|
180
|
-
is_web = self.window.controller.chat.remote_tools.enabled(model, "web_search") # get global config
|
|
181
|
-
|
|
182
|
-
# Web Search tool
|
|
183
|
-
if is_web:
|
|
184
|
-
ttype = cfg.get("remote_tools.anthropic.web_search.type", "web_search_20250305") # stable as of docs
|
|
185
|
-
tname = "web_search"
|
|
186
|
-
|
|
187
|
-
tool_def: Dict[str, Any] = {
|
|
188
|
-
"type": ttype,
|
|
189
|
-
"name": tname,
|
|
190
160
|
}
|
|
191
161
|
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
tool_def["max_uses"] = max_uses
|
|
196
|
-
|
|
197
|
-
def parse_csv_list(key: str) -> list:
|
|
198
|
-
raw = cfg.get(key, "")
|
|
199
|
-
if not raw:
|
|
200
|
-
return []
|
|
201
|
-
if isinstance(raw, list):
|
|
202
|
-
return [str(x).strip() for x in raw if str(x).strip()]
|
|
203
|
-
return [s.strip() for s in str(raw).split(",") if s.strip()]
|
|
204
|
-
|
|
205
|
-
allowed = parse_csv_list("remote_tools.anthropic.web_search.allowed_domains")
|
|
206
|
-
blocked = parse_csv_list("remote_tools.anthropic.web_search.blocked_domains")
|
|
207
|
-
if allowed:
|
|
208
|
-
tool_def["allowed_domains"] = allowed
|
|
209
|
-
elif blocked:
|
|
210
|
-
tool_def["blocked_domains"] = blocked
|
|
211
|
-
|
|
212
|
-
# Location (approximate)
|
|
213
|
-
loc_city = cfg.get("remote_tools.anthropic.web_search.user_location.city")
|
|
214
|
-
loc_region = cfg.get("remote_tools.anthropic.web_search.user_location.region")
|
|
215
|
-
loc_country = cfg.get("remote_tools.anthropic.web_search.user_location.country")
|
|
216
|
-
loc_tz = cfg.get("remote_tools.anthropic.web_search.user_location.timezone")
|
|
217
|
-
if any([loc_city, loc_region, loc_country, loc_tz]):
|
|
218
|
-
tool_def["user_location"] = {
|
|
219
|
-
"type": "approximate",
|
|
220
|
-
"city": str(loc_city) if loc_city else None,
|
|
221
|
-
"region": str(loc_region) if loc_region else None,
|
|
222
|
-
"country": str(loc_country) if loc_country else None,
|
|
223
|
-
"timezone": str(loc_tz) if loc_tz else None,
|
|
224
|
-
}
|
|
225
|
-
# remove None fields
|
|
226
|
-
tool_def["user_location"] = {k: v for k, v in tool_def["user_location"].items() if v is not None}
|
|
162
|
+
# optional: allow defer_loading for tool search when configured per-tool (kept compatible)
|
|
163
|
+
if isinstance(fn, dict) and fn.get("defer_loading") is True:
|
|
164
|
+
tool_def["defer_loading"] = True
|
|
227
165
|
|
|
228
166
|
tools.append(tool_def)
|
|
229
167
|
|
|
@@ -231,28 +169,45 @@ class Tools:
|
|
|
231
169
|
|
|
232
170
|
def merge_tools_dedup(self, primary: List[dict], secondary: List[dict]) -> List[dict]:
|
|
233
171
|
"""
|
|
234
|
-
Remove duplicate tools
|
|
172
|
+
Remove duplicate tools, preserving order:
|
|
235
173
|
|
|
236
174
|
- First from primary list
|
|
237
|
-
- Then from secondary list if
|
|
175
|
+
- Then from secondary list if not already present
|
|
176
|
+
|
|
177
|
+
Dedup rules:
|
|
178
|
+
* Tools with a 'name' are deduped by name.
|
|
179
|
+
* MCP toolsets (type == 'mcp_toolset') are deduped by (type, mcp_server_name).
|
|
180
|
+
* Tools without a 'name' use (type) as a fallback key.
|
|
238
181
|
|
|
239
182
|
:param primary: Primary list of tool dicts
|
|
240
183
|
:param secondary: Secondary list of tool dicts
|
|
241
184
|
:return: Merged list of tool dicts without duplicates
|
|
242
185
|
"""
|
|
186
|
+
def key_for(t: dict) -> str:
|
|
187
|
+
name = t.get("name")
|
|
188
|
+
if name:
|
|
189
|
+
return f"name::{name}"
|
|
190
|
+
ttype = t.get("type")
|
|
191
|
+
if ttype == "mcp_toolset":
|
|
192
|
+
return f"mcp::{t.get('mcp_server_name', '')}"
|
|
193
|
+
return f"type::{ttype}"
|
|
194
|
+
|
|
243
195
|
result: List[dict] = []
|
|
244
196
|
seen = set()
|
|
197
|
+
|
|
245
198
|
for t in primary or []:
|
|
246
|
-
|
|
247
|
-
if
|
|
248
|
-
seen.add(
|
|
199
|
+
k = key_for(t)
|
|
200
|
+
if k not in seen:
|
|
201
|
+
seen.add(k)
|
|
249
202
|
result.append(t)
|
|
203
|
+
|
|
250
204
|
for t in secondary or []:
|
|
251
|
-
|
|
252
|
-
if
|
|
205
|
+
k = key_for(t)
|
|
206
|
+
if k in seen:
|
|
253
207
|
continue
|
|
254
|
-
seen.add(
|
|
208
|
+
seen.add(k)
|
|
255
209
|
result.append(t)
|
|
210
|
+
|
|
256
211
|
return result
|
|
257
212
|
|
|
258
213
|
def get_all_tools(self, model: ModelItem, functions: list) -> List[dict]:
|
|
@@ -264,5 +219,5 @@ class Tools:
|
|
|
264
219
|
:return: Combined list of tool dicts
|
|
265
220
|
"""
|
|
266
221
|
base_tools = self.prepare(model, functions)
|
|
267
|
-
remote_tools = self.build_remote_tools(model)
|
|
222
|
+
remote_tools = self.window.core.api.anthropic.remote_tools.build_remote_tools(model)
|
|
268
223
|
return self.merge_tools_dedup(base_tools, remote_tools)
|
|
@@ -0,0 +1,30 @@
|
|
|
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.05 20:00:00 #
|
|
10
|
+
# ================================================== #
|
|
11
|
+
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def as_int(val: Any) -> Optional[int]:
|
|
16
|
+
"""
|
|
17
|
+
Coerce to int if possible, else None.
|
|
18
|
+
|
|
19
|
+
:param val: Input value
|
|
20
|
+
:return: int or None
|
|
21
|
+
"""
|
|
22
|
+
if val is None:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
return int(val)
|
|
26
|
+
except Exception:
|
|
27
|
+
try:
|
|
28
|
+
return int(float(val))
|
|
29
|
+
except Exception:
|
|
30
|
+
return 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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.03 17:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -25,6 +25,7 @@ from pygpt_net.core.types import (
|
|
|
25
25
|
MODE_COMPUTER,
|
|
26
26
|
)
|
|
27
27
|
from pygpt_net.core.bridge.context import BridgeContext
|
|
28
|
+
from pygpt_net.core.types.chunk import ChunkType
|
|
28
29
|
from pygpt_net.item.model import ModelItem
|
|
29
30
|
|
|
30
31
|
from .chat import Chat
|
|
@@ -124,10 +125,7 @@ class ApiGoogle:
|
|
|
124
125
|
stream = context.stream
|
|
125
126
|
ctx = context.ctx
|
|
126
127
|
ai_name = ctx.output_name if ctx else "assistant"
|
|
127
|
-
|
|
128
|
-
# No Responses API in google-genai
|
|
129
|
-
if ctx:
|
|
130
|
-
ctx.use_responses_api = False
|
|
128
|
+
ctx.chunk_type = ChunkType.GOOGLE
|
|
131
129
|
|
|
132
130
|
used_tokens = 0
|
|
133
131
|
response = None
|
|
@@ -151,6 +149,9 @@ class ApiGoogle:
|
|
|
151
149
|
if is_realtime:
|
|
152
150
|
return True
|
|
153
151
|
|
|
152
|
+
if mode == MODE_RESEARCH:
|
|
153
|
+
ctx.chunk_type = ChunkType.GOOGLE_INTERACTIONS_API # use interactions API for research
|
|
154
|
+
|
|
154
155
|
response = self.chat.send(context=context, extra=extra)
|
|
155
156
|
used_tokens = self.chat.get_used_tokens()
|
|
156
157
|
if ctx:
|
|
@@ -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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.05 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import os
|
|
@@ -121,12 +121,8 @@ class Chat:
|
|
|
121
121
|
|
|
122
122
|
# Enable Computer Use tool in computer mode (use the official Tool/ComputerUse object)
|
|
123
123
|
if mode == MODE_COMPUTER or (model and isinstance(model.id, str) and "computer-use" in model.id.lower()):
|
|
124
|
-
|
|
125
|
-
tools = [
|
|
126
|
-
computer_use=gtypes.ComputerUse(
|
|
127
|
-
environment=comp_env,
|
|
128
|
-
)
|
|
129
|
-
)] # reset tools to only Computer Use (multiple tools not supported together)
|
|
124
|
+
tool = self.window.core.api.google.computer.get_tool()
|
|
125
|
+
tools = [tool] # reset tools to only Computer Use (multiple tools not supported together)
|
|
130
126
|
|
|
131
127
|
# Some models cannot use tools; keep behavior for image-only models
|
|
132
128
|
if model and isinstance(model.id, str) and "-image" in model.id:
|
|
@@ -180,7 +176,6 @@ class Chat:
|
|
|
180
176
|
params = dict(model=model.id, contents=inputs, config=cfg)
|
|
181
177
|
|
|
182
178
|
if mode == MODE_RESEARCH:
|
|
183
|
-
ctx.use_google_interactions_api = True
|
|
184
179
|
|
|
185
180
|
# Deep Research does not support audio inputs; if an audio snippet is present, transcribe it to text first.
|
|
186
181
|
if has_audio_input:
|
|
@@ -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: 2026.01.
|
|
9
|
+
# Updated Date: 2026.01.05 20:00:00 #
|
|
10
10
|
# ================================================== #
|
|
11
11
|
|
|
12
12
|
import base64
|