dsh-plugin-image-workbench 1.1.5 → 1.1.7
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.
- package/LICENSE +21 -21
- package/README.en.md +5 -3
- package/README.md +7 -3
- package/client.js +241 -241
- package/cordis.patch.yml +14 -14
- package/host.js +51 -0
- package/install.mjs +333 -387
- package/package.json +3 -2
- package/scripts/fetch-style-images.mjs +104 -0
- package/scripts/vendor-snapshot.py +154 -154
- package/server/access.py +26 -26
- package/server/app.py +1371 -1329
- package/server/catalog.py +147 -147
- package/server/composer.py +391 -391
- package/server/drafts.py +158 -158
- package/server/history.py +575 -575
- package/server/imaging.py +172 -172
- package/server/pool.py +1335 -1335
- package/server/pool_admin.py +921 -921
- package/server/pool_service.py +1073 -1073
- package/server/requirements.txt +6 -6
- package/server/smoke_check.py +402 -402
- package/server/static/access.html +202 -202
- package/server/static/index.html +15 -0
- package/vendor/chatgpt2api/source/.env.example +82 -82
- package/vendor/chatgpt2api/source/.python-version +1 -1
- package/vendor/chatgpt2api/source/LICENSE +21 -21
- package/vendor/chatgpt2api/source/README.md +391 -391
- package/vendor/chatgpt2api/source/VERSION +1 -1
- package/vendor/chatgpt2api/source/api/__init__.py +2 -2
- package/vendor/chatgpt2api/source/api/accounts.py +537 -537
- package/vendor/chatgpt2api/source/api/ai.py +217 -217
- package/vendor/chatgpt2api/source/api/app.py +62 -62
- package/vendor/chatgpt2api/source/api/errors.py +46 -46
- package/vendor/chatgpt2api/source/api/image_inputs.py +309 -309
- package/vendor/chatgpt2api/source/api/image_tasks.py +120 -120
- package/vendor/chatgpt2api/source/api/support.py +134 -134
- package/vendor/chatgpt2api/source/api/system.py +365 -365
- package/vendor/chatgpt2api/source/assets/kunrouai.svg +50 -50
- package/vendor/chatgpt2api/source/config.default.json +108 -108
- package/vendor/chatgpt2api/source/main.py +9 -9
- package/vendor/chatgpt2api/source/pyproject.toml +27 -27
- package/vendor/chatgpt2api/source/scripts/init_proxy_config.py +186 -186
- package/vendor/chatgpt2api/source/scripts/migrate_storage.py +163 -163
- package/vendor/chatgpt2api/source/scripts/privoxy-warp.conf +13 -13
- package/vendor/chatgpt2api/source/scripts/test_outlook_token_mailbox.py +385 -385
- package/vendor/chatgpt2api/source/scripts/test_storage.py +129 -129
- package/vendor/chatgpt2api/source/scripts/verify_oauth_refresh.py +92 -92
- package/vendor/chatgpt2api/source/services/account_service.py +1757 -1757
- package/vendor/chatgpt2api/source/services/auth_service.py +253 -253
- package/vendor/chatgpt2api/source/services/backup_service.py +671 -671
- package/vendor/chatgpt2api/source/services/config.py +667 -667
- package/vendor/chatgpt2api/source/services/content_filter.py +242 -242
- package/vendor/chatgpt2api/source/services/cpa_service.py +315 -315
- package/vendor/chatgpt2api/source/services/editable_file_task_service.py +260 -260
- package/vendor/chatgpt2api/source/services/image_service.py +376 -376
- package/vendor/chatgpt2api/source/services/image_storage_service.py +405 -405
- package/vendor/chatgpt2api/source/services/image_tags_service.py +76 -76
- package/vendor/chatgpt2api/source/services/image_task_service.py +550 -550
- package/vendor/chatgpt2api/source/services/log_service.py +337 -337
- package/vendor/chatgpt2api/source/services/model_service.py +182 -182
- package/vendor/chatgpt2api/source/services/oauth_login_service.py +267 -267
- package/vendor/chatgpt2api/source/services/openai_backend_api.py +2763 -2763
- package/vendor/chatgpt2api/source/services/openai_oauth.py +32 -32
- package/vendor/chatgpt2api/source/services/protocol/__init__.py +2 -2
- package/vendor/chatgpt2api/source/services/protocol/anthropic_v1_messages.py +307 -307
- package/vendor/chatgpt2api/source/services/protocol/chat_completion_cache.py +238 -238
- package/vendor/chatgpt2api/source/services/protocol/conversation.py +1644 -1644
- package/vendor/chatgpt2api/source/services/protocol/error_response.py +124 -124
- package/vendor/chatgpt2api/source/services/protocol/openai_search.py +19 -19
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_chat_complete.py +316 -316
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_edit.py +86 -86
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_image_generations.py +42 -42
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_models.py +53 -53
- package/vendor/chatgpt2api/source/services/protocol/openai_v1_response.py +453 -453
- package/vendor/chatgpt2api/source/services/protocol/web_search_tool.py +160 -160
- package/vendor/chatgpt2api/source/services/proxy_service.py +648 -648
- package/vendor/chatgpt2api/source/services/storage/__init__.py +5 -5
- package/vendor/chatgpt2api/source/services/storage/base.py +38 -38
- package/vendor/chatgpt2api/source/services/storage/database_storage.py +194 -194
- package/vendor/chatgpt2api/source/services/storage/factory.py +104 -104
- package/vendor/chatgpt2api/source/services/storage/git_storage.py +183 -183
- package/vendor/chatgpt2api/source/services/storage/json_storage.py +95 -95
- package/vendor/chatgpt2api/source/services/sub2api_service.py +531 -531
- package/vendor/chatgpt2api/source/utils/helper.py +469 -469
- package/vendor/chatgpt2api/source/utils/image_tokens.py +346 -346
- package/vendor/chatgpt2api/source/utils/log.py +110 -110
- package/vendor/chatgpt2api/source/utils/pkce.py +19 -19
- package/vendor/chatgpt2api/source/utils/pow.py +207 -207
- package/vendor/chatgpt2api/source/utils/sentinel.py +159 -159
- package/vendor/chatgpt2api/source/utils/turnstile.py +184 -184
- package/vendor/chatgpt2api/source/uv.lock +904 -904
- package/vendor/chatgpt2api/source/web/bun.lock +1501 -1501
- package/vendor/chatgpt2api/source/web/components.json +24 -24
- package/vendor/chatgpt2api/source/web/eslint.config.mjs +17 -17
- package/vendor/chatgpt2api/source/web/next.config.ts +40 -40
- package/vendor/chatgpt2api/source/web/package.json +58 -58
- package/vendor/chatgpt2api/source/web/postcss.config.mjs +5 -5
- package/vendor/chatgpt2api/source/web/src/app/accounts/components/account-import-dialog.tsx +925 -925
- package/vendor/chatgpt2api/source/web/src/app/accounts/page.tsx +1307 -1307
- package/vendor/chatgpt2api/source/web/src/app/debug/components/chat-panel.tsx +224 -224
- package/vendor/chatgpt2api/source/web/src/app/debug/components/editable-file-panel.tsx +444 -444
- package/vendor/chatgpt2api/source/web/src/app/debug/components/ppt-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/psd-panel.tsx +9 -9
- package/vendor/chatgpt2api/source/web/src/app/debug/components/search-panel.tsx +172 -172
- package/vendor/chatgpt2api/source/web/src/app/debug/components/skill-panel.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/debug/components/types.ts +39 -39
- package/vendor/chatgpt2api/source/web/src/app/debug/page.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/app/globals.css +230 -230
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-composer.tsx +542 -542
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-results.tsx +554 -554
- package/vendor/chatgpt2api/source/web/src/app/image/components/image-sidebar.tsx +176 -176
- package/vendor/chatgpt2api/source/web/src/app/image/page.tsx +1799 -1799
- package/vendor/chatgpt2api/source/web/src/app/image-manager/page.tsx +732 -732
- package/vendor/chatgpt2api/source/web/src/app/layout.tsx +50 -50
- package/vendor/chatgpt2api/source/web/src/app/login/page.tsx +101 -101
- package/vendor/chatgpt2api/source/web/src/app/logs/page.tsx +356 -356
- package/vendor/chatgpt2api/source/web/src/app/page.tsx +30 -30
- package/vendor/chatgpt2api/source/web/src/app/settings/components/api-docs-card.tsx +341 -341
- package/vendor/chatgpt2api/source/web/src/app/settings/components/backup-settings-card.tsx +447 -447
- package/vendor/chatgpt2api/source/web/src/app/settings/components/base-url-card.tsx +71 -71
- package/vendor/chatgpt2api/source/web/src/app/settings/components/config-card.tsx +491 -491
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pool-dialog.tsx +108 -108
- package/vendor/chatgpt2api/source/web/src/app/settings/components/cpa-pools-card.tsx +169 -169
- package/vendor/chatgpt2api/source/web/src/app/settings/components/import-browser-dialog.tsx +185 -185
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-runtime-card.tsx +379 -379
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings-card.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/app/settings/components/proxy-settings.tsx +200 -200
- package/vendor/chatgpt2api/source/web/src/app/settings/components/settings-header.tsx +12 -12
- package/vendor/chatgpt2api/source/web/src/app/settings/components/sub2api-connections.tsx +886 -886
- package/vendor/chatgpt2api/source/web/src/app/settings/components/third-party-apps-card.tsx +81 -81
- package/vendor/chatgpt2api/source/web/src/app/settings/components/user-keys-card.tsx +422 -422
- package/vendor/chatgpt2api/source/web/src/app/settings/page.tsx +135 -135
- package/vendor/chatgpt2api/source/web/src/app/settings/store.ts +1069 -1069
- package/vendor/chatgpt2api/source/web/src/components/date-range-filter.tsx +49 -49
- package/vendor/chatgpt2api/source/web/src/components/header-actions.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/image-lightbox.tsx +434 -434
- package/vendor/chatgpt2api/source/web/src/components/image-thumbnail.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/theme-script.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/theme-toggle.tsx +14 -14
- package/vendor/chatgpt2api/source/web/src/components/top-nav.tsx +263 -263
- package/vendor/chatgpt2api/source/web/src/components/ui/animated-theme-toggler.tsx +256 -256
- package/vendor/chatgpt2api/source/web/src/components/ui/badge.tsx +41 -41
- package/vendor/chatgpt2api/source/web/src/components/ui/button.tsx +59 -59
- package/vendor/chatgpt2api/source/web/src/components/ui/calendar.tsx +46 -46
- package/vendor/chatgpt2api/source/web/src/components/ui/card.tsx +61 -61
- package/vendor/chatgpt2api/source/web/src/components/ui/checkbox.tsx +27 -27
- package/vendor/chatgpt2api/source/web/src/components/ui/dia-text-reveal.tsx +267 -267
- package/vendor/chatgpt2api/source/web/src/components/ui/dialog.tsx +132 -132
- package/vendor/chatgpt2api/source/web/src/components/ui/field.tsx +13 -13
- package/vendor/chatgpt2api/source/web/src/components/ui/input.tsx +19 -19
- package/vendor/chatgpt2api/source/web/src/components/ui/label.tsx +24 -24
- package/vendor/chatgpt2api/source/web/src/components/ui/morphing-text.tsx +149 -149
- package/vendor/chatgpt2api/source/web/src/components/ui/popover.tsx +38 -38
- package/vendor/chatgpt2api/source/web/src/components/ui/select.tsx +175 -175
- package/vendor/chatgpt2api/source/web/src/components/ui/sheet.tsx +143 -143
- package/vendor/chatgpt2api/source/web/src/components/ui/table.tsx +29 -29
- package/vendor/chatgpt2api/source/web/src/components/ui/tabs.tsx +91 -91
- package/vendor/chatgpt2api/source/web/src/components/ui/text-3d-flip.tsx +269 -269
- package/vendor/chatgpt2api/source/web/src/components/ui/textarea.tsx +18 -18
- package/vendor/chatgpt2api/source/web/src/components/version-release-dialog.tsx +127 -127
- package/vendor/chatgpt2api/source/web/src/constants/common-env.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/hooks/use-version-check.ts +85 -85
- package/vendor/chatgpt2api/source/web/src/lib/api.ts +926 -926
- package/vendor/chatgpt2api/source/web/src/lib/auth-session.ts +26 -26
- package/vendor/chatgpt2api/source/web/src/lib/release.ts +25 -25
- package/vendor/chatgpt2api/source/web/src/lib/request.ts +93 -93
- package/vendor/chatgpt2api/source/web/src/lib/use-auth-guard.ts +89 -89
- package/vendor/chatgpt2api/source/web/src/lib/utils.ts +6 -6
- package/vendor/chatgpt2api/source/web/src/store/auth.ts +112 -112
- package/vendor/chatgpt2api/source/web/src/store/editable-file-history.ts +33 -33
- package/vendor/chatgpt2api/source/web/src/store/image-conversations.ts +318 -318
- package/vendor/chatgpt2api/source/web/tsconfig.json +42 -42
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import re
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
from services.account_service import account_service
|
|
7
|
-
from services.openai_backend_api import OpenAIBackendAPI
|
|
8
|
-
|
|
9
|
-
WEB_SEARCH_TOOL_TYPES = {"web_search", "web_search_preview", "web_search_preview_2025_03_11"}
|
|
10
|
-
SEARCH_CHAT_MODEL_PREFIXES = (
|
|
11
|
-
"gpt-4o-search-preview",
|
|
12
|
-
"gpt-4o-mini-search-preview",
|
|
13
|
-
"gpt-5-search-api",
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _tool_type(tool: object) -> str:
|
|
18
|
-
return str(tool.get("type") or "").strip() if isinstance(tool, dict) else ""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def has_web_search_tool(body: dict[str, Any]) -> bool:
|
|
22
|
-
tools = body.get("tools")
|
|
23
|
-
if isinstance(tools, list):
|
|
24
|
-
return any(_tool_type(tool) in WEB_SEARCH_TOOL_TYPES for tool in tools)
|
|
25
|
-
tool_choice = body.get("tool_choice")
|
|
26
|
-
return _tool_type(tool_choice) in WEB_SEARCH_TOOL_TYPES
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def is_web_search_chat_request(body: dict[str, Any]) -> bool:
|
|
30
|
-
model = str(body.get("model") or "").strip()
|
|
31
|
-
return (
|
|
32
|
-
has_web_search_tool(body)
|
|
33
|
-
or isinstance(body.get("web_search_options"), dict)
|
|
34
|
-
or any(
|
|
35
|
-
model == prefix or model.startswith(f"{prefix}-")
|
|
36
|
-
for prefix in SEARCH_CHAT_MODEL_PREFIXES
|
|
37
|
-
)
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def has_unsupported_tools(body: dict[str, Any], allowed_types: set[str]) -> bool:
|
|
42
|
-
tools = body.get("tools")
|
|
43
|
-
if not isinstance(tools, list):
|
|
44
|
-
return False
|
|
45
|
-
return any(_tool_type(tool) not in allowed_types for tool in tools if isinstance(tool, dict))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def message_text(value: object) -> str:
|
|
49
|
-
if isinstance(value, str):
|
|
50
|
-
return value.strip()
|
|
51
|
-
if isinstance(value, list):
|
|
52
|
-
parts: list[str] = []
|
|
53
|
-
for item in value:
|
|
54
|
-
if isinstance(item, str):
|
|
55
|
-
text = item.strip()
|
|
56
|
-
elif isinstance(item, dict):
|
|
57
|
-
text = str(item.get("text") or item.get("input_text") or "").strip()
|
|
58
|
-
else:
|
|
59
|
-
text = ""
|
|
60
|
-
if text:
|
|
61
|
-
parts.append(text)
|
|
62
|
-
return "\n".join(parts).strip()
|
|
63
|
-
return ""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def search_query_from_messages(messages: list[dict[str, Any]]) -> str:
|
|
67
|
-
for message in reversed(messages):
|
|
68
|
-
if str(message.get("role") or "").strip().lower() != "user":
|
|
69
|
-
continue
|
|
70
|
-
text = message_text(message.get("content"))
|
|
71
|
-
if text:
|
|
72
|
-
return text
|
|
73
|
-
return ""
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def _readable_annotation_part(parts: list[str]) -> str:
|
|
77
|
-
for part in parts:
|
|
78
|
-
value = part.strip()
|
|
79
|
-
lower = value.lower()
|
|
80
|
-
if value and not (
|
|
81
|
-
lower.startswith(("turn", "source", "sources"))
|
|
82
|
-
or re.fullmatch(r"\d+", value)
|
|
83
|
-
):
|
|
84
|
-
return value
|
|
85
|
-
return ""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def clean_search_text(text: str) -> str:
|
|
89
|
-
def replace_annotation(match: re.Match[str]) -> str:
|
|
90
|
-
parts = [part.strip() for part in match.group(1).split("\ue202")]
|
|
91
|
-
kind = (parts[0] if parts else "").lower()
|
|
92
|
-
data = parts[1:]
|
|
93
|
-
if kind == "url":
|
|
94
|
-
label = data[0] if data else ""
|
|
95
|
-
url = data[1] if len(data) > 1 else ""
|
|
96
|
-
if label and url.startswith(("http://", "https://")):
|
|
97
|
-
return f"{label} ({url})"
|
|
98
|
-
return label or url
|
|
99
|
-
if kind == "cite":
|
|
100
|
-
return _readable_annotation_part(data)
|
|
101
|
-
return _readable_annotation_part(data)
|
|
102
|
-
|
|
103
|
-
text = re.sub(r"\ue200([^\ue201]*)\ue201", replace_annotation, text)
|
|
104
|
-
text = re.sub(r"\ue200[^\ue201]*$", "", text)
|
|
105
|
-
return re.sub(r"\s+([.,;:!?])", r"\1", text).strip()
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def normalized_sources(result: dict[str, Any]) -> list[dict[str, str]]:
|
|
109
|
-
sources = result.get("sources")
|
|
110
|
-
if not isinstance(sources, list):
|
|
111
|
-
return []
|
|
112
|
-
output: list[dict[str, str]] = []
|
|
113
|
-
seen: set[str] = set()
|
|
114
|
-
for item in sources:
|
|
115
|
-
if not isinstance(item, dict):
|
|
116
|
-
continue
|
|
117
|
-
url = str(item.get("url") or "").strip()
|
|
118
|
-
title = str(item.get("title") or "").strip()
|
|
119
|
-
snippet = str(item.get("snippet") or "").strip()
|
|
120
|
-
if not url or url in seen:
|
|
121
|
-
continue
|
|
122
|
-
seen.add(url)
|
|
123
|
-
output.append({"title": title, "url": url, "snippet": snippet})
|
|
124
|
-
return output
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def text_with_url_citations(result: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]:
|
|
128
|
-
text = clean_search_text(str(result.get("answer") or ""))
|
|
129
|
-
annotations: list[dict[str, Any]] = []
|
|
130
|
-
sources = normalized_sources(result)
|
|
131
|
-
if sources:
|
|
132
|
-
text = text.rstrip()
|
|
133
|
-
if text:
|
|
134
|
-
text += "\n\n"
|
|
135
|
-
text += "Sources:\n"
|
|
136
|
-
for index, source in enumerate(sources, start=1):
|
|
137
|
-
title = source["title"] or source["url"]
|
|
138
|
-
line_prefix = f"{index}. {title}"
|
|
139
|
-
text += line_prefix
|
|
140
|
-
if source["url"]:
|
|
141
|
-
if source["title"]:
|
|
142
|
-
text += " - "
|
|
143
|
-
start = len(text)
|
|
144
|
-
text += source["url"]
|
|
145
|
-
annotations.append({
|
|
146
|
-
"type": "url_citation",
|
|
147
|
-
"start_index": start,
|
|
148
|
-
"end_index": len(text),
|
|
149
|
-
"url": source["url"],
|
|
150
|
-
"title": source["title"] or source["url"],
|
|
151
|
-
})
|
|
152
|
-
text += "\n"
|
|
153
|
-
return text.strip(), annotations
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def run_web_search(query: str) -> dict[str, Any]:
|
|
157
|
-
token = account_service.get_text_access_token()
|
|
158
|
-
result = OpenAIBackendAPI(token).search(query)
|
|
159
|
-
account_service.mark_text_used(token)
|
|
160
|
-
return result
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from services.account_service import account_service
|
|
7
|
+
from services.openai_backend_api import OpenAIBackendAPI
|
|
8
|
+
|
|
9
|
+
WEB_SEARCH_TOOL_TYPES = {"web_search", "web_search_preview", "web_search_preview_2025_03_11"}
|
|
10
|
+
SEARCH_CHAT_MODEL_PREFIXES = (
|
|
11
|
+
"gpt-4o-search-preview",
|
|
12
|
+
"gpt-4o-mini-search-preview",
|
|
13
|
+
"gpt-5-search-api",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _tool_type(tool: object) -> str:
|
|
18
|
+
return str(tool.get("type") or "").strip() if isinstance(tool, dict) else ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def has_web_search_tool(body: dict[str, Any]) -> bool:
|
|
22
|
+
tools = body.get("tools")
|
|
23
|
+
if isinstance(tools, list):
|
|
24
|
+
return any(_tool_type(tool) in WEB_SEARCH_TOOL_TYPES for tool in tools)
|
|
25
|
+
tool_choice = body.get("tool_choice")
|
|
26
|
+
return _tool_type(tool_choice) in WEB_SEARCH_TOOL_TYPES
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_web_search_chat_request(body: dict[str, Any]) -> bool:
|
|
30
|
+
model = str(body.get("model") or "").strip()
|
|
31
|
+
return (
|
|
32
|
+
has_web_search_tool(body)
|
|
33
|
+
or isinstance(body.get("web_search_options"), dict)
|
|
34
|
+
or any(
|
|
35
|
+
model == prefix or model.startswith(f"{prefix}-")
|
|
36
|
+
for prefix in SEARCH_CHAT_MODEL_PREFIXES
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def has_unsupported_tools(body: dict[str, Any], allowed_types: set[str]) -> bool:
|
|
42
|
+
tools = body.get("tools")
|
|
43
|
+
if not isinstance(tools, list):
|
|
44
|
+
return False
|
|
45
|
+
return any(_tool_type(tool) not in allowed_types for tool in tools if isinstance(tool, dict))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def message_text(value: object) -> str:
|
|
49
|
+
if isinstance(value, str):
|
|
50
|
+
return value.strip()
|
|
51
|
+
if isinstance(value, list):
|
|
52
|
+
parts: list[str] = []
|
|
53
|
+
for item in value:
|
|
54
|
+
if isinstance(item, str):
|
|
55
|
+
text = item.strip()
|
|
56
|
+
elif isinstance(item, dict):
|
|
57
|
+
text = str(item.get("text") or item.get("input_text") or "").strip()
|
|
58
|
+
else:
|
|
59
|
+
text = ""
|
|
60
|
+
if text:
|
|
61
|
+
parts.append(text)
|
|
62
|
+
return "\n".join(parts).strip()
|
|
63
|
+
return ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def search_query_from_messages(messages: list[dict[str, Any]]) -> str:
|
|
67
|
+
for message in reversed(messages):
|
|
68
|
+
if str(message.get("role") or "").strip().lower() != "user":
|
|
69
|
+
continue
|
|
70
|
+
text = message_text(message.get("content"))
|
|
71
|
+
if text:
|
|
72
|
+
return text
|
|
73
|
+
return ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _readable_annotation_part(parts: list[str]) -> str:
|
|
77
|
+
for part in parts:
|
|
78
|
+
value = part.strip()
|
|
79
|
+
lower = value.lower()
|
|
80
|
+
if value and not (
|
|
81
|
+
lower.startswith(("turn", "source", "sources"))
|
|
82
|
+
or re.fullmatch(r"\d+", value)
|
|
83
|
+
):
|
|
84
|
+
return value
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def clean_search_text(text: str) -> str:
|
|
89
|
+
def replace_annotation(match: re.Match[str]) -> str:
|
|
90
|
+
parts = [part.strip() for part in match.group(1).split("\ue202")]
|
|
91
|
+
kind = (parts[0] if parts else "").lower()
|
|
92
|
+
data = parts[1:]
|
|
93
|
+
if kind == "url":
|
|
94
|
+
label = data[0] if data else ""
|
|
95
|
+
url = data[1] if len(data) > 1 else ""
|
|
96
|
+
if label and url.startswith(("http://", "https://")):
|
|
97
|
+
return f"{label} ({url})"
|
|
98
|
+
return label or url
|
|
99
|
+
if kind == "cite":
|
|
100
|
+
return _readable_annotation_part(data)
|
|
101
|
+
return _readable_annotation_part(data)
|
|
102
|
+
|
|
103
|
+
text = re.sub(r"\ue200([^\ue201]*)\ue201", replace_annotation, text)
|
|
104
|
+
text = re.sub(r"\ue200[^\ue201]*$", "", text)
|
|
105
|
+
return re.sub(r"\s+([.,;:!?])", r"\1", text).strip()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def normalized_sources(result: dict[str, Any]) -> list[dict[str, str]]:
|
|
109
|
+
sources = result.get("sources")
|
|
110
|
+
if not isinstance(sources, list):
|
|
111
|
+
return []
|
|
112
|
+
output: list[dict[str, str]] = []
|
|
113
|
+
seen: set[str] = set()
|
|
114
|
+
for item in sources:
|
|
115
|
+
if not isinstance(item, dict):
|
|
116
|
+
continue
|
|
117
|
+
url = str(item.get("url") or "").strip()
|
|
118
|
+
title = str(item.get("title") or "").strip()
|
|
119
|
+
snippet = str(item.get("snippet") or "").strip()
|
|
120
|
+
if not url or url in seen:
|
|
121
|
+
continue
|
|
122
|
+
seen.add(url)
|
|
123
|
+
output.append({"title": title, "url": url, "snippet": snippet})
|
|
124
|
+
return output
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def text_with_url_citations(result: dict[str, Any]) -> tuple[str, list[dict[str, Any]]]:
|
|
128
|
+
text = clean_search_text(str(result.get("answer") or ""))
|
|
129
|
+
annotations: list[dict[str, Any]] = []
|
|
130
|
+
sources = normalized_sources(result)
|
|
131
|
+
if sources:
|
|
132
|
+
text = text.rstrip()
|
|
133
|
+
if text:
|
|
134
|
+
text += "\n\n"
|
|
135
|
+
text += "Sources:\n"
|
|
136
|
+
for index, source in enumerate(sources, start=1):
|
|
137
|
+
title = source["title"] or source["url"]
|
|
138
|
+
line_prefix = f"{index}. {title}"
|
|
139
|
+
text += line_prefix
|
|
140
|
+
if source["url"]:
|
|
141
|
+
if source["title"]:
|
|
142
|
+
text += " - "
|
|
143
|
+
start = len(text)
|
|
144
|
+
text += source["url"]
|
|
145
|
+
annotations.append({
|
|
146
|
+
"type": "url_citation",
|
|
147
|
+
"start_index": start,
|
|
148
|
+
"end_index": len(text),
|
|
149
|
+
"url": source["url"],
|
|
150
|
+
"title": source["title"] or source["url"],
|
|
151
|
+
})
|
|
152
|
+
text += "\n"
|
|
153
|
+
return text.strip(), annotations
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def run_web_search(query: str) -> dict[str, Any]:
|
|
157
|
+
token = account_service.get_text_access_token()
|
|
158
|
+
result = OpenAIBackendAPI(token).search(query)
|
|
159
|
+
account_service.mark_text_used(token)
|
|
160
|
+
return result
|