libre-webui 0.20.0 → 0.21.0
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/README.md +31 -21
- package/backend/dist/db.d.ts.map +1 -1
- package/backend/dist/db.js +2 -0
- package/backend/dist/db.js.map +1 -1
- package/backend/dist/index.d.ts.map +1 -1
- package/backend/dist/index.js +2 -0
- package/backend/dist/index.js.map +1 -1
- package/backend/dist/routes/chat.d.ts.map +1 -1
- package/backend/dist/routes/chat.js +97 -10
- package/backend/dist/routes/chat.js.map +1 -1
- package/backend/dist/routes/libreClaw.d.ts.map +1 -1
- package/backend/dist/routes/libreClaw.js +41 -0
- package/backend/dist/routes/libreClaw.js.map +1 -1
- package/backend/dist/routes/ollama.d.ts.map +1 -1
- package/backend/dist/routes/ollama.js +52 -2
- package/backend/dist/routes/ollama.js.map +1 -1
- package/backend/dist/routes/search.d.ts +3 -0
- package/backend/dist/routes/search.d.ts.map +1 -0
- package/backend/dist/routes/search.js +143 -0
- package/backend/dist/routes/search.js.map +1 -0
- package/backend/dist/services/agentAccessService.d.ts +6 -0
- package/backend/dist/services/agentAccessService.d.ts.map +1 -0
- package/backend/dist/services/agentAccessService.js +58 -0
- package/backend/dist/services/agentAccessService.js.map +1 -0
- package/backend/dist/services/agentCliService.d.ts.map +1 -1
- package/backend/dist/services/agentCliService.js +2 -1
- package/backend/dist/services/agentCliService.js.map +1 -1
- package/backend/dist/services/authService.d.ts +1 -0
- package/backend/dist/services/authService.d.ts.map +1 -1
- package/backend/dist/services/authService.js +2 -0
- package/backend/dist/services/authService.js.map +1 -1
- package/backend/dist/services/documentService.d.ts +2 -0
- package/backend/dist/services/documentService.d.ts.map +1 -1
- package/backend/dist/services/documentService.js +51 -23
- package/backend/dist/services/documentService.js.map +1 -1
- package/backend/dist/services/modelAccessService.d.ts +12 -0
- package/backend/dist/services/modelAccessService.d.ts.map +1 -0
- package/backend/dist/services/modelAccessService.js +66 -0
- package/backend/dist/services/modelAccessService.js.map +1 -0
- package/backend/dist/services/webSearchService.d.ts +64 -0
- package/backend/dist/services/webSearchService.d.ts.map +1 -0
- package/backend/dist/services/webSearchService.js +230 -0
- package/backend/dist/services/webSearchService.js.map +1 -0
- package/backend/dist/services/workAgentGuidance.d.ts +1 -0
- package/backend/dist/services/workAgentGuidance.d.ts.map +1 -1
- package/backend/dist/services/workAgentGuidance.js +3 -0
- package/backend/dist/services/workAgentGuidance.js.map +1 -1
- package/backend/dist/services/workAgentService.d.ts +6 -0
- package/backend/dist/services/workAgentService.d.ts.map +1 -1
- package/backend/dist/services/workAgentService.js +199 -11
- package/backend/dist/services/workAgentService.js.map +1 -1
- package/backend/dist/services/workPolicyService.d.ts.map +1 -1
- package/backend/dist/services/workPolicyService.js +61 -11
- package/backend/dist/services/workPolicyService.js.map +1 -1
- package/backend/dist/storage.d.ts.map +1 -1
- package/backend/dist/storage.js +3 -3
- package/backend/dist/storage.js.map +1 -1
- package/backend/dist/storageMappers.d.ts +1 -0
- package/backend/dist/storageMappers.d.ts.map +1 -1
- package/backend/dist/storageMappers.js +1 -0
- package/backend/dist/storageMappers.js.map +1 -1
- package/backend/dist/types/index.d.ts +1 -0
- package/backend/dist/types/index.d.ts.map +1 -1
- package/backend/dist/types/index.js.map +1 -1
- package/backend/dist/utils/chatDocumentContext.d.ts +7 -0
- package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
- package/backend/dist/utils/chatDocumentContext.js +38 -30
- package/backend/dist/utils/chatDocumentContext.js.map +1 -1
- package/backend/dist/websocketServer.d.ts.map +1 -1
- package/backend/dist/websocketServer.js +79 -15
- package/backend/dist/websocketServer.js.map +1 -1
- package/backend/package.json +1 -1
- package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
- package/frontend/dist/assets/index-0wfi-EpC.js +40 -0
- package/frontend/dist/assets/index-D7m1zWWg.css +1 -0
- package/frontend/dist/index.html +12 -12
- package/frontend/dist/js/ArtifactContainer-DPRPcuES.js +1 -0
- package/frontend/dist/js/{ArtifactDemoPage-BOlB1lS-.js → ArtifactDemoPage-DzwPlz9J.js} +1 -1
- package/frontend/dist/js/{ArtifactSandboxFrame-DqU5GDrI.js → ArtifactSandboxFrame-CKi4mSTH.js} +2 -2
- package/frontend/dist/js/{ArtifactSlideOutPanel-B0I-hCux.js → ArtifactSlideOutPanel-BskRCzP0.js} +1 -1
- package/frontend/dist/js/ChatPage-DRutCyJ5.js +16 -0
- package/frontend/dist/js/FirstTimeSetup-DU50YWrE.js +1 -0
- package/frontend/dist/js/GalleryPage-DxXIUesf.js +1 -0
- package/frontend/dist/js/HomePage-Bew9kMKx.js +1 -0
- package/frontend/dist/js/{LibreClawPage-DUd3tjQE.js → LibreClawPage-Cy9y3YlF.js} +1 -1
- package/frontend/dist/js/{ModelManagerModals-D9j9RECi.js → ModelManagerModals-D-NxeVvV.js} +1 -1
- package/frontend/dist/js/ModelSelector-DXDcDmDR.js +1 -0
- package/frontend/dist/js/ModelsPage-Bj2reiF9.js +2 -0
- package/frontend/dist/js/{NotesPage-CTutDN50.js → NotesPage-CJKEbj3J.js} +1 -1
- package/frontend/dist/js/{PersonasPage-C6iTMI3N.js → PersonasPage-bgWF9S7b.js} +2 -2
- package/frontend/dist/js/{PluginUsagePage-BQQ0-_PP.js → PluginUsagePage-CRkD1Tz6.js} +1 -1
- package/frontend/dist/js/{RichMessageContent-DPqeqxkQ.js → RichMessageContent-DeNjCiyN.js} +1 -1
- package/frontend/dist/js/SettingsModal-BUUlkZcv.js +1 -0
- package/frontend/dist/js/SystemPage-DcO51urv.js +1 -0
- package/frontend/dist/js/UserManagementPage-DLiYw6xG.js +1 -0
- package/frontend/dist/js/WorkPage-MMQ0Y0U5.js +5 -0
- package/frontend/dist/js/{api-iCpOEijB.js → api-CZURQtuc.js} +4 -4
- package/frontend/dist/js/{appStore-DTd6dWg2.js → appStore-sGaWMSDr.js} +2 -2
- package/frontend/dist/js/ar-Bm27TCMQ.js +2 -0
- package/frontend/dist/js/{authStore-CuK4HUVh.js → authStore-uaj0hswV.js} +3 -3
- package/frontend/dist/js/bn-C7CHC42H.js +2 -0
- package/frontend/dist/js/chatStore-tRx0Ulq0.js +2 -0
- package/frontend/dist/js/{config--Xp9rZtJ.js → config-D-jxRG52.js} +1 -1
- package/frontend/dist/js/cs-zGEtwQn_.js +2 -0
- package/frontend/dist/js/da-B-0sHNTu.js +2 -0
- package/frontend/dist/js/de-UZNE6t2h.js +2 -0
- package/frontend/dist/js/es-CPLnFYIw.js +2 -0
- package/frontend/dist/js/{estree-BAQkciRX.js → estree-CRuerC6a.js} +1 -1
- package/frontend/dist/js/fr-Br1o0WVz.js +2 -0
- package/frontend/dist/js/hi-BcEvNEpb.js +2 -0
- package/frontend/dist/js/{html-DxA-Bcvi.js → html-BYKlz3Jp.js} +1 -1
- package/frontend/dist/js/i18n-DJTg6xWw.js +2 -0
- package/frontend/dist/js/id-C30L2DoT.js +2 -0
- package/frontend/dist/js/is-BAEhfi5Z.js +2 -0
- package/frontend/dist/js/it-HWnfQXZY.js +2 -0
- package/frontend/dist/js/ja-Dvux1mon.js +2 -0
- package/frontend/dist/js/ko-agDQ__KP.js +2 -0
- package/frontend/dist/js/{markdown-IsZpJ5ke.js → markdown-DFy8bILU.js} +1 -1
- package/frontend/dist/js/{messageCodeStyles-CsV3zwyK.js → messageCodeStyles-_gGirbe-.js} +2 -2
- package/frontend/dist/js/ms-aNRa2QX7.js +2 -0
- package/frontend/dist/js/nl-BtkiDUn-.js +2 -0
- package/frontend/dist/js/pl-SEO0lBnX.js +2 -0
- package/frontend/dist/js/pt-BYtjmvq1.js +2 -0
- package/frontend/dist/js/ru-B-ynj2hU.js +2 -0
- package/frontend/dist/js/{standalone-CK3b5Ktq.js → standalone-jOqWoOmp.js} +1 -1
- package/frontend/dist/js/sv-CliKR15U.js +2 -0
- package/frontend/dist/js/th-DJQyDr9J.js +2 -0
- package/frontend/dist/js/tr-BBGKZ1la.js +2 -0
- package/frontend/dist/js/{typescript-BMYmVohw.js → typescript-TwGXpgeF.js} +1 -1
- package/frontend/dist/js/{ui-C2kNYwfG.js → ui-DSFUl2vT.js} +2 -2
- package/frontend/dist/js/{ui-vendor-D_pYT79f.js → ui-vendor-xUzKqAXI.js} +1 -1
- package/frontend/dist/js/uk-C1GcFWxE.js +2 -0
- package/frontend/dist/js/vi-CIxgICOQ.js +2 -0
- package/frontend/dist/js/{workApi-C33ZrPws.js → workApi-kClHFVkJ.js} +2 -2
- package/frontend/dist/js/zh-Cm7vsQ9s.js +2 -0
- package/package.json +2 -2
- package/frontend/dist/assets/index-Z4-oLWDC.js +0 -2
- package/frontend/dist/assets/index-tBUyDbHh.css +0 -1
- package/frontend/dist/js/ArtifactContainer-DQRF4rh-.js +0 -1
- package/frontend/dist/js/ChatPage-DyV0_oCt.js +0 -16
- package/frontend/dist/js/FirstTimeSetup-ButTmjp0.js +0 -1
- package/frontend/dist/js/GalleryPage-CaT63b-s.js +0 -1
- package/frontend/dist/js/HomePage-BWyg3nHu.js +0 -1
- package/frontend/dist/js/ModelSelector-COFcJnMJ.js +0 -1
- package/frontend/dist/js/ModelsPage-BRbTEXNL.js +0 -2
- package/frontend/dist/js/SettingsModal-C_1k75Kv.js +0 -1
- package/frontend/dist/js/SystemPage-CUecyRPh.js +0 -1
- package/frontend/dist/js/UserManagementPage-CZ4KMOY9.js +0 -1
- package/frontend/dist/js/WorkPage-BqDcfXV6.js +0 -5
- package/frontend/dist/js/ar-C8YV99Y-.js +0 -2
- package/frontend/dist/js/bn-C9dcls7_.js +0 -2
- package/frontend/dist/js/chatStore-VNT0eHRB.js +0 -2
- package/frontend/dist/js/cs-DtL8VeeX.js +0 -2
- package/frontend/dist/js/da-U3aNn1aF.js +0 -2
- package/frontend/dist/js/de-DzuyeVpX.js +0 -2
- package/frontend/dist/js/es-OiU8plii.js +0 -2
- package/frontend/dist/js/fr-DDa3R8bs.js +0 -2
- package/frontend/dist/js/hi-D8Y0hl34.js +0 -2
- package/frontend/dist/js/i18n-BqWNFeNo.js +0 -2
- package/frontend/dist/js/id-BwcWtkBv.js +0 -2
- package/frontend/dist/js/is-DzfQHQih.js +0 -2
- package/frontend/dist/js/it-C5VU3_Je.js +0 -2
- package/frontend/dist/js/ja-DLhOhxvf.js +0 -2
- package/frontend/dist/js/ko-CCKiyQD6.js +0 -2
- package/frontend/dist/js/ms-CXXkPvWw.js +0 -2
- package/frontend/dist/js/nl-CA_e0Epl.js +0 -2
- package/frontend/dist/js/pl-BG32wdpx.js +0 -2
- package/frontend/dist/js/pt-BzRGWQXO.js +0 -2
- package/frontend/dist/js/ru-DBjn_Q-Q.js +0 -2
- package/frontend/dist/js/sv-Hz_iRGdF.js +0 -2
- package/frontend/dist/js/th-MsyXlGH9.js +0 -2
- package/frontend/dist/js/tr-Cofmy_ef.js +0 -2
- package/frontend/dist/js/uk-B2o05LCR.js +0 -2
- package/frontend/dist/js/vi-CZh8uJgV.js +0 -2
- package/frontend/dist/js/zh-DhKXi8ZA.js +0 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Libre WebUI
|
|
3
|
+
* Copyright (C) 2025 Kroonen AI, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at:
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Web search through a SearXNG instance. The connection is an
|
|
19
|
+
* administrator-managed persisted setting (the SEARXNG_URL environment
|
|
20
|
+
* variable seeds the default, so the bundled deploy stack works out of the
|
|
21
|
+
* box once an admin flips the toggle). Search stays off until enabled.
|
|
22
|
+
*
|
|
23
|
+
* The backend queries SearXNG's JSON API server-side; result text is
|
|
24
|
+
* bounded before it reaches any model context. The URL is admin-supplied
|
|
25
|
+
* configuration — treated like the Ollama endpoint, not user input.
|
|
26
|
+
*/
|
|
27
|
+
import { getDatabase } from '../db.js';
|
|
28
|
+
import { createLogger } from '../utils/logger.js';
|
|
29
|
+
const logger = createLogger('services:web-search');
|
|
30
|
+
export const WEB_SEARCH_ENABLED_KEY = 'web_search_enabled';
|
|
31
|
+
export const WEB_SEARCH_URL_KEY = 'web_search_url';
|
|
32
|
+
export const WEB_SEARCH_ACCESS_KEY = 'web_search_access';
|
|
33
|
+
export const WEB_SEARCH_MAX_RESULTS_KEY = 'web_search_max_results';
|
|
34
|
+
export const WEB_SEARCH_SAFE_SEARCH_KEY = 'web_search_safe_search';
|
|
35
|
+
export const WEB_SEARCH_DEFAULT_MAX_RESULTS = 6;
|
|
36
|
+
export const WEB_SEARCH_RESULTS_CEILING = 10;
|
|
37
|
+
export function isWebSearchAccessMode(value) {
|
|
38
|
+
return value === 'admins' || value === 'all-users';
|
|
39
|
+
}
|
|
40
|
+
const SEARCH_TIMEOUT_MS = 12000;
|
|
41
|
+
const RESULT_TEXT_MAX_CHARS = 500;
|
|
42
|
+
const QUERY_MAX_CHARS = 400;
|
|
43
|
+
export function normalizeWebSearchMaxResults(value) {
|
|
44
|
+
const parsed = Number(value);
|
|
45
|
+
if (!Number.isInteger(parsed))
|
|
46
|
+
return WEB_SEARCH_DEFAULT_MAX_RESULTS;
|
|
47
|
+
return Math.min(Math.max(parsed, 1), WEB_SEARCH_RESULTS_CEILING);
|
|
48
|
+
}
|
|
49
|
+
function readSetting(key) {
|
|
50
|
+
try {
|
|
51
|
+
const row = getDatabase()
|
|
52
|
+
.prepare('SELECT value FROM system_settings WHERE key = ?')
|
|
53
|
+
.get(key);
|
|
54
|
+
return row?.value;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function writeSetting(key, value) {
|
|
61
|
+
getDatabase()
|
|
62
|
+
.prepare(`INSERT INTO system_settings (key, value, updated_at)
|
|
63
|
+
VALUES (?, ?, ?)
|
|
64
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
65
|
+
value = excluded.value,
|
|
66
|
+
updated_at = excluded.updated_at`)
|
|
67
|
+
.run(key, value, Date.now());
|
|
68
|
+
}
|
|
69
|
+
export function normalizeWebSearchUrl(value) {
|
|
70
|
+
if (typeof value !== 'string')
|
|
71
|
+
return '';
|
|
72
|
+
const trimmed = value.trim().replace(/\/+$/, '');
|
|
73
|
+
if (!trimmed)
|
|
74
|
+
return '';
|
|
75
|
+
let parsed;
|
|
76
|
+
try {
|
|
77
|
+
parsed = new URL(trimmed);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
throw new Error('Search URL must be a valid http(s) URL.');
|
|
81
|
+
}
|
|
82
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
83
|
+
throw new Error('Search URL must use http or https.');
|
|
84
|
+
}
|
|
85
|
+
return trimmed;
|
|
86
|
+
}
|
|
87
|
+
export function getWebSearchConfig() {
|
|
88
|
+
const enabled = readSetting(WEB_SEARCH_ENABLED_KEY) === 'true';
|
|
89
|
+
const storedUrl = readSetting(WEB_SEARCH_URL_KEY);
|
|
90
|
+
const url = storedUrl !== undefined ? storedUrl : (process.env.SEARXNG_URL ?? '');
|
|
91
|
+
const storedMax = readSetting(WEB_SEARCH_MAX_RESULTS_KEY);
|
|
92
|
+
return {
|
|
93
|
+
enabled,
|
|
94
|
+
url,
|
|
95
|
+
available: enabled && url.length > 0,
|
|
96
|
+
maxResults: storedMax !== undefined
|
|
97
|
+
? normalizeWebSearchMaxResults(storedMax)
|
|
98
|
+
: WEB_SEARCH_DEFAULT_MAX_RESULTS,
|
|
99
|
+
// Safe search is on unless an administrator turned it off.
|
|
100
|
+
safeSearch: readSetting(WEB_SEARCH_SAFE_SEARCH_KEY) !== 'false',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function setWebSearchConfig(input) {
|
|
104
|
+
const url = normalizeWebSearchUrl(input.url);
|
|
105
|
+
if (input.enabled && !url) {
|
|
106
|
+
throw new Error('Enable web search only with a SearXNG URL configured.');
|
|
107
|
+
}
|
|
108
|
+
writeSetting(WEB_SEARCH_ENABLED_KEY, input.enabled ? 'true' : 'false');
|
|
109
|
+
writeSetting(WEB_SEARCH_URL_KEY, url);
|
|
110
|
+
if (input.maxResults !== undefined) {
|
|
111
|
+
writeSetting(WEB_SEARCH_MAX_RESULTS_KEY, String(normalizeWebSearchMaxResults(input.maxResults)));
|
|
112
|
+
}
|
|
113
|
+
if (input.safeSearch !== undefined) {
|
|
114
|
+
writeSetting(WEB_SEARCH_SAFE_SEARCH_KEY, input.safeSearch ? 'true' : 'false');
|
|
115
|
+
}
|
|
116
|
+
return getWebSearchConfig();
|
|
117
|
+
}
|
|
118
|
+
export function isWebSearchAvailable() {
|
|
119
|
+
return getWebSearchConfig().available;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Who may use web search, mirroring the Work access mode: administrators
|
|
123
|
+
* always may; other active users only when an administrator opens it up in
|
|
124
|
+
* User Management. Defaults to admins-only and fails closed.
|
|
125
|
+
*/
|
|
126
|
+
export function getWebSearchAccessMode() {
|
|
127
|
+
const value = readSetting(WEB_SEARCH_ACCESS_KEY);
|
|
128
|
+
return isWebSearchAccessMode(value) ? value : 'admins';
|
|
129
|
+
}
|
|
130
|
+
export function setWebSearchAccessMode(mode) {
|
|
131
|
+
if (!isWebSearchAccessMode(mode)) {
|
|
132
|
+
throw new Error(`Invalid web search access mode "${String(mode)}".`);
|
|
133
|
+
}
|
|
134
|
+
writeSetting(WEB_SEARCH_ACCESS_KEY, mode);
|
|
135
|
+
}
|
|
136
|
+
/** Whether this user may run web searches right now (search must also be available). */
|
|
137
|
+
export function userCanUseWebSearch(user) {
|
|
138
|
+
if (!user)
|
|
139
|
+
return false;
|
|
140
|
+
if (user.status !== undefined && user.status !== 'active')
|
|
141
|
+
return false;
|
|
142
|
+
if (user.role === 'admin')
|
|
143
|
+
return true;
|
|
144
|
+
return getWebSearchAccessMode() === 'all-users';
|
|
145
|
+
}
|
|
146
|
+
const bounded = (value, max) => typeof value === 'string'
|
|
147
|
+
? value.replace(/\s+/g, ' ').trim().slice(0, max)
|
|
148
|
+
: '';
|
|
149
|
+
export async function webSearch(query, maxResults) {
|
|
150
|
+
const config = getWebSearchConfig();
|
|
151
|
+
if (!config.available) {
|
|
152
|
+
throw new Error('Web search is not enabled on this server.');
|
|
153
|
+
}
|
|
154
|
+
const trimmedQuery = query.trim().slice(0, QUERY_MAX_CHARS);
|
|
155
|
+
if (!trimmedQuery) {
|
|
156
|
+
throw new Error('A search query is required.');
|
|
157
|
+
}
|
|
158
|
+
// Callers (including model tool calls) can request fewer results, never
|
|
159
|
+
// more than the administrator's ceiling.
|
|
160
|
+
const requested = maxResults === undefined
|
|
161
|
+
? config.maxResults
|
|
162
|
+
: Math.max(1, Math.trunc(maxResults));
|
|
163
|
+
const limit = Math.min(requested, config.maxResults);
|
|
164
|
+
const target = new URL(`${config.url}/search`);
|
|
165
|
+
target.searchParams.set('q', trimmedQuery);
|
|
166
|
+
target.searchParams.set('format', 'json');
|
|
167
|
+
target.searchParams.set('safesearch', config.safeSearch ? '1' : '0');
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timer = setTimeout(() => controller.abort(), SEARCH_TIMEOUT_MS);
|
|
170
|
+
let response;
|
|
171
|
+
try {
|
|
172
|
+
response = await fetch(target, {
|
|
173
|
+
signal: controller.signal,
|
|
174
|
+
headers: { Accept: 'application/json' },
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.warn('Web search request failed:', error);
|
|
179
|
+
throw new Error('The search service could not be reached. Check the SearXNG URL.');
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
clearTimeout(timer);
|
|
183
|
+
}
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
throw new Error(`The search service answered with HTTP ${response.status}. Confirm the instance allows the JSON format.`);
|
|
186
|
+
}
|
|
187
|
+
let payload;
|
|
188
|
+
try {
|
|
189
|
+
payload = (await response.json());
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
throw new Error('The search service did not return JSON. Enable the json format in the SearXNG settings.');
|
|
193
|
+
}
|
|
194
|
+
const results = [];
|
|
195
|
+
for (const raw of payload.results ?? []) {
|
|
196
|
+
const url = typeof raw.url === 'string' ? raw.url.trim() : '';
|
|
197
|
+
if (!/^https?:\/\//i.test(url))
|
|
198
|
+
continue;
|
|
199
|
+
results.push({
|
|
200
|
+
title: bounded(raw.title, 200) || url,
|
|
201
|
+
url: url.slice(0, 1000),
|
|
202
|
+
content: bounded(raw.content, RESULT_TEXT_MAX_CHARS),
|
|
203
|
+
...(typeof raw.engine === 'string' ? { engine: raw.engine } : {}),
|
|
204
|
+
});
|
|
205
|
+
if (results.length >= limit)
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The latest user message enhanced with search context, mirroring the
|
|
212
|
+
* document-RAG enhancement shape so every provider path benefits.
|
|
213
|
+
*/
|
|
214
|
+
export function buildWebSearchEnhancedContent(content, results, query) {
|
|
215
|
+
if (results.length === 0)
|
|
216
|
+
return content;
|
|
217
|
+
const blocks = results.map((result, index) => `[${index + 1}] ${result.title}\n${result.url}${result.content ? `\n${result.content}` : ''}`);
|
|
218
|
+
return `Web search results for "${query}":\n\n${blocks.join('\n\n')}\n\n---\n\nUsing the search results above when they are relevant, answer the user's message. Cite sources inline with bracketed numbers like [1] where you rely on them.\n\nUser message: ${content}`;
|
|
219
|
+
}
|
|
220
|
+
export default {
|
|
221
|
+
getWebSearchConfig,
|
|
222
|
+
setWebSearchConfig,
|
|
223
|
+
isWebSearchAvailable,
|
|
224
|
+
getWebSearchAccessMode,
|
|
225
|
+
setWebSearchAccessMode,
|
|
226
|
+
userCanUseWebSearch,
|
|
227
|
+
webSearch,
|
|
228
|
+
buildWebSearchEnhancedContent,
|
|
229
|
+
};
|
|
230
|
+
//# sourceMappingURL=webSearchService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webSearchService.js","sourceRoot":"","sources":["../../src/services/webSearchService.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;;;;GASG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,MAAM,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAEnD,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAC3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,CAAC;AACnD,MAAM,CAAC,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AACzD,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AACnE,MAAM,CAAC,MAAM,0BAA0B,GAAG,wBAAwB,CAAC;AAEnE,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC;AAChD,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAI7C,MAAM,UAAU,qBAAqB,CACnC,KAAc;IAEd,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,WAAW,CAAC;AACrD,CAAC;AAED,MAAM,iBAAiB,GAAG,KAAM,CAAC;AACjC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,eAAe,GAAG,GAAG,CAAC;AAmB5B,MAAM,UAAU,4BAA4B,CAAC,KAAc;IACzD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;QAAE,OAAO,8BAA8B,CAAC;IACrE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,0BAA0B,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,WAAW,CAAC,GAAW;IAC9B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,WAAW,EAAE;aACtB,OAAO,CAAC,iDAAiD,CAAC;aAC1D,GAAG,CAAC,GAAG,CAAmC,CAAC;QAC9C,OAAO,GAAG,EAAE,KAAK,CAAC;IACpB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAAW,EAAE,KAAa;IAC9C,WAAW,EAAE;SACV,OAAO,CACN;;;;0CAIoC,CACrC;SACA,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACxB,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,MAAM,OAAO,GAAG,WAAW,CAAC,sBAAsB,CAAC,KAAK,MAAM,CAAC;IAC/D,MAAM,SAAS,GAAG,WAAW,CAAC,kBAAkB,CAAC,CAAC;IAClD,MAAM,GAAG,GACP,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,WAAW,CAAC,0BAA0B,CAAC,CAAC;IAC1D,OAAO;QACL,OAAO;QACP,GAAG;QACH,SAAS,EAAE,OAAO,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QACpC,UAAU,EACR,SAAS,KAAK,SAAS;YACrB,CAAC,CAAC,4BAA4B,CAAC,SAAS,CAAC;YACzC,CAAC,CAAC,8BAA8B;QACpC,2DAA2D;QAC3D,UAAU,EAAE,WAAW,CAAC,0BAA0B,CAAC,KAAK,OAAO;KAChE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAKlC;IACC,MAAM,GAAG,GAAG,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC3E,CAAC;IACD,YAAY,CAAC,sBAAsB,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACvE,YAAY,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACnC,YAAY,CACV,0BAA0B,EAC1B,MAAM,CAAC,4BAA4B,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CACvD,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;QACnC,YAAY,CACV,0BAA0B,EAC1B,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CACpC,CAAC;IACJ,CAAC;IACD,OAAO,kBAAkB,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,kBAAkB,EAAE,CAAC,SAAS,CAAC;AACxC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IACpC,MAAM,KAAK,GAAG,WAAW,CAAC,qBAAqB,CAAC,CAAC;IACjD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAyB;IAC9D,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,mCAAmC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,CAAC;IACD,YAAY,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,mBAAmB,CACjC,IAA2D;IAE3D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACxE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,sBAAsB,EAAE,KAAK,WAAW,CAAC;AAClD,CAAC;AASD,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,GAAW,EAAU,EAAE,CACtD,OAAO,KAAK,KAAK,QAAQ;IACvB,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;IACjD,CAAC,CAAC,EAAE,CAAC;AAET,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAa,EACb,UAAmB;IAEnB,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;IACpC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC;IAC5D,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IACD,wEAAwE;IACxE,yCAAyC;IACzC,MAAM,SAAS,GACb,UAAU,KAAK,SAAS;QACtB,CAAC,CAAC,MAAM,CAAC,UAAU;QACnB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC;IAC/C,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC3C,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAErE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;IACtE,IAAI,QAA6B,CAAC;IAClC,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC7B,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACjD,MAAM,IAAI,KAAK,CACb,iEAAiE,CAClE,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,yCAAyC,QAAQ,CAAC,MAAM,gDAAgD,CACzG,CAAC;IACJ,CAAC;IACD,IAAI,OAAsC,CAAC;IAC3C,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAkC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,SAAS;QACzC,OAAO,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG;YACrC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,IAAK,CAAC;YACxB,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,qBAAqB,CAAC;YACpD,GAAG,CAAC,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAClE,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK;YAAE,MAAM;IACrC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,6BAA6B,CAC3C,OAAe,EACf,OAAmC,EACnC,KAAa;IAEb,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CACxB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAChB,IAAI,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,GAAG,GAC3C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAC3C,EAAE,CACL,CAAC;IACF,OAAO,2BAA2B,KAAK,SAAS,MAAM,CAAC,IAAI,CACzD,MAAM,CACP,6LAA6L,OAAO,EAAE,CAAC;AAC1M,CAAC;AAED,eAAe;IACb,kBAAkB;IAClB,kBAAkB;IAClB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,mBAAmB;IACnB,SAAS;IACT,6BAA6B;CAC9B,CAAC"}
|
|
@@ -20,5 +20,6 @@ export declare const WORK_WRITE_FILE_RECOMMENDED_CHARS = 8000;
|
|
|
20
20
|
export declare const WORK_AGENT_SKILLS: readonly WorkAgentSkill[];
|
|
21
21
|
export declare function workToolCallBudget(roundBudget: number): number;
|
|
22
22
|
export declare function buildWorkAgentSystemPrompt(context: WorkAgentGuidanceContext): string;
|
|
23
|
+
export declare function buildWorkEmptyRoundNudgePrompt(): string;
|
|
23
24
|
export declare function buildWorkBudgetExhaustionPrompt(): string;
|
|
24
25
|
//# sourceMappingURL=workAgentGuidance.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workAgentGuidance.d.ts","sourceRoot":"","sources":["../../src/services/workAgentGuidance.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAID,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAiD7C,CAAC;AAEX,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,wBAAwB,GAChC,MAAM,CA4BR;AAED,wBAAgB,+BAA+B,IAAI,MAAM,CAQxD"}
|
|
1
|
+
{"version":3,"file":"workAgentGuidance.d.ts","sourceRoot":"","sources":["../../src/services/workAgentGuidance.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAID,eAAO,MAAM,iCAAiC,OAAQ,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,SAAS,cAAc,EAiD7C,CAAC;AAEX,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,wBAAwB,GAChC,MAAM,CA4BR;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,wBAAgB,+BAA+B,IAAI,MAAM,CAQxD"}
|
|
@@ -101,6 +101,9 @@ ${skills}
|
|
|
101
101
|
|
|
102
102
|
Finish with a concise summary of what changed and the checks that actually ran.`;
|
|
103
103
|
}
|
|
104
|
+
export function buildWorkEmptyRoundNudgePrompt() {
|
|
105
|
+
return `Your last turn contained no reply and no tool calls, so nothing happened. Continue the task now: either call the tools you need, or reply with your findings and the next step.`;
|
|
106
|
+
}
|
|
104
107
|
export function buildWorkBudgetExhaustionPrompt() {
|
|
105
108
|
return `The autonomous execution budget is exhausted. Do not call any more tools.
|
|
106
109
|
Return a concise final response that states:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workAgentGuidance.js","sourceRoot":"","sources":["../../src/services/workAgentGuidance.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"workAgentGuidance.js","sourceRoot":"","sources":["../../src/services/workAgentGuidance.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { OllamaChatMessage, OllamaChatResponse } from '../types/index.js';
|
|
2
2
|
import { WorkMessage, WorkRun, WorkTaskDetail, WorkToolCall } from '../types/work.js';
|
|
3
3
|
export declare const WORK_PROVIDER_STATE_METADATA_KEY = "workProviderState";
|
|
4
|
+
export declare const WORK_EMPTY_MODEL_RESPONSE_METADATA_KEY = "emptyModelResponse";
|
|
4
5
|
export declare const WORK_TOOL_SCHEMAS: Record<string, unknown>[];
|
|
6
|
+
export declare const WORK_WEB_SEARCH_TOOL_SCHEMA: Record<string, unknown>;
|
|
7
|
+
export declare function workToolSchemasForTask(task: {
|
|
8
|
+
networkEnabled: boolean;
|
|
9
|
+
userId: string;
|
|
10
|
+
}): Record<string, unknown>[];
|
|
5
11
|
export declare class WorkAgentService {
|
|
6
12
|
private controllers;
|
|
7
13
|
private executions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workAgentService.d.ts","sourceRoot":"","sources":["../../src/services/workAgentService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"workAgentService.d.ts","sourceRoot":"","sources":["../../src/services/workAgentService.ts"],"names":[],"mappings":"AA0CA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EACL,WAAW,EACX,OAAO,EACP,cAAc,EAEd,YAAY,EACb,MAAM,kBAAkB,CAAC;AAU1B,eAAO,MAAM,gCAAgC,sBAAsB,CAAC;AACpE,eAAO,MAAM,sCAAsC,uBAAuB,CAAC;AAS3E,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EA8EtD,CAAC;AAKF,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAa7D,CAAC;AAEJ,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAM5B;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,UAAU,CAAoC;IAEtD,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAazD;IAEK,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAGpE;IAEK,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9D;YAEa,UAAU;IA2BlB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtD;IAEK,uBAAuB,CAAC,CAAC,EAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAmEZ;IAED,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1C;YAEa,kBAAkB;IA2B1B,QAAQ,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAc7D;YAEa,OAAO;YAimBP,yBAAyB;IA6BvC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,eAAe;YA0BT,WAAW;IAiJzB,OAAO,CAAC,gBAAgB;CAYzB;AA+CD,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAKxD;CACF;AAsJD,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,YAAY,GAAG,OAAO,CAAC,EAChE,kBAAkB,CAAC,EAAE,MAAM,GAC1B,iBAAiB,EAAE,CAwLrB;AA4ED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,kBAAkB,GAC3B,YAAY,EAAE,CA+GhB;AA4LD,eAAO,MAAM,gBAAgB,kBAAyB,CAAC;eACxC,gBAAgB"}
|
|
@@ -16,13 +16,23 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import workModelProviderService, { WORK_TOOL_ARGUMENTS_ERROR_METADATA_KEY, WORK_TOOL_ARGUMENTS_ERROR_MESSAGE, } from './workModelProviderService.js';
|
|
18
18
|
import workEventService from './workEventService.js';
|
|
19
|
-
import { buildWorkAgentSystemPrompt, buildWorkBudgetExhaustionPrompt, WORK_AGENT_SKILLS, WORK_WRITE_FILE_RECOMMENDED_CHARS, workToolCallBudget, } from './workAgentGuidance.js';
|
|
19
|
+
import { buildWorkAgentSystemPrompt, buildWorkBudgetExhaustionPrompt, buildWorkEmptyRoundNudgePrompt, WORK_AGENT_SKILLS, WORK_WRITE_FILE_RECOMMENDED_CHARS, workToolCallBudget, } from './workAgentGuidance.js';
|
|
20
20
|
import workRuntimeService from './workRuntimeService.js';
|
|
21
|
+
import { isWebSearchAvailable, userCanUseWebSearch, webSearch, } from './webSearchService.js';
|
|
22
|
+
import { userModel } from '../models/userModel.js';
|
|
21
23
|
import workTaskService, { WORK_MESSAGE_METADATA_MAX_BYTES, WorkConflictError, WorkNotFoundError, } from './workTaskService.js';
|
|
22
24
|
import { createLogger } from '../utils/logger.js';
|
|
23
25
|
import { boundedOpenAIResponsesOutputItems, OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY, OPENAI_RESPONSES_STATE_DROPPED_METADATA_KEY, OPENAI_RESPONSES_STATE_SCOPE_METADATA_KEY, } from '../utils/openAIResponsesAdapter.js';
|
|
24
26
|
const logger = createLogger('services:work-agent');
|
|
25
27
|
export const WORK_PROVIDER_STATE_METADATA_KEY = 'workProviderState';
|
|
28
|
+
export const WORK_EMPTY_MODEL_RESPONSE_METADATA_KEY = 'emptyModelResponse';
|
|
29
|
+
// A reasoning-heavy model occasionally ends a round with neither text nor
|
|
30
|
+
// tool calls. Treating that as the final answer strands the run mid-task,
|
|
31
|
+
// so nudge it back to work a bounded number of times per run first.
|
|
32
|
+
const WORK_EMPTY_ROUND_NUDGE_LIMIT = 2;
|
|
33
|
+
// Cross-run replay only needs the shape of past tool calls, not full
|
|
34
|
+
// write_file payloads; bound each persisted argument set.
|
|
35
|
+
const WORK_CHAT_TOOL_CALL_ARGUMENTS_MAX_BYTES = 4096;
|
|
26
36
|
export const WORK_TOOL_SCHEMAS = [
|
|
27
37
|
functionTool('list_files', 'List direct children of a workspace directory.', {
|
|
28
38
|
path: stringProperty('Relative directory path. Defaults to workspace root.'),
|
|
@@ -59,6 +69,23 @@ export const WORK_TOOL_SCHEMAS = [
|
|
|
59
69
|
}),
|
|
60
70
|
functionTool('stop_preview', 'Stop the running workspace preview.', {}),
|
|
61
71
|
];
|
|
72
|
+
// Offered per run, only when an administrator enabled web search and the
|
|
73
|
+
// task has network access: an "offline" task should stay offline even
|
|
74
|
+
// though the search request itself egresses from the backend.
|
|
75
|
+
export const WORK_WEB_SEARCH_TOOL_SCHEMA = functionTool('web_search', 'Search the web through the server-configured search engine. Returns titles, URLs, and snippets.', {
|
|
76
|
+
query: stringProperty('Search query.'),
|
|
77
|
+
max_results: {
|
|
78
|
+
type: 'integer',
|
|
79
|
+
description: "Optional result count. Defaults to 5, capped by the server's configured limit.",
|
|
80
|
+
},
|
|
81
|
+
}, ['query']);
|
|
82
|
+
export function workToolSchemasForTask(task) {
|
|
83
|
+
return task.networkEnabled &&
|
|
84
|
+
isWebSearchAvailable() &&
|
|
85
|
+
userCanUseWebSearch(userModel.getUserById(task.userId))
|
|
86
|
+
? [...WORK_TOOL_SCHEMAS, WORK_WEB_SEARCH_TOOL_SCHEMA]
|
|
87
|
+
: WORK_TOOL_SCHEMAS;
|
|
88
|
+
}
|
|
62
89
|
export class WorkAgentService {
|
|
63
90
|
constructor() {
|
|
64
91
|
this.controllers = new Map();
|
|
@@ -271,6 +298,7 @@ export class WorkAgentService {
|
|
|
271
298
|
let streamedAssistantTotal = '';
|
|
272
299
|
let streamedReasoningTotal = '';
|
|
273
300
|
let budgetReason = 'round';
|
|
301
|
+
let emptyRoundNudges = 0;
|
|
274
302
|
roundLoop: for (let round = 0; round < roundLimit; round++) {
|
|
275
303
|
this.throwIfCancelled(runId, controller);
|
|
276
304
|
workEventService.publish(taskId, runId, 'run_state', {
|
|
@@ -290,7 +318,7 @@ export class WorkAgentService {
|
|
|
290
318
|
response = await workModelProviderService.generateChatStreamResponse({
|
|
291
319
|
model: run.model,
|
|
292
320
|
messages,
|
|
293
|
-
tools:
|
|
321
|
+
tools: workToolSchemasForTask(task),
|
|
294
322
|
stream: true,
|
|
295
323
|
}, providerSelection, userId, {
|
|
296
324
|
onContent: delta => contentStream.push(delta),
|
|
@@ -352,9 +380,39 @@ export class WorkAgentService {
|
|
|
352
380
|
workTaskService.addMessage(taskId, runId, 'assistant', 'reasoning', reasoningContent, { providerExposed: true, round: round + 1 });
|
|
353
381
|
}
|
|
354
382
|
if (toolCalls.length === 0) {
|
|
383
|
+
if (!assistantContent &&
|
|
384
|
+
emptyRoundNudges < WORK_EMPTY_ROUND_NUDGE_LIMIT) {
|
|
385
|
+
// A round with no text and no tool calls carries no result;
|
|
386
|
+
// completing here would strand the run mid-task. Preserve any
|
|
387
|
+
// provider replay state, then send the model back to work.
|
|
388
|
+
emptyRoundNudges += 1;
|
|
389
|
+
if (response.message.providerMetadata) {
|
|
390
|
+
messages.push({
|
|
391
|
+
role: 'assistant',
|
|
392
|
+
content: '',
|
|
393
|
+
providerMetadata: response.message.providerMetadata,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (providerStateMetadata) {
|
|
397
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'provider_state', '', providerStateMetadata);
|
|
398
|
+
}
|
|
399
|
+
messages.push({
|
|
400
|
+
role: 'user',
|
|
401
|
+
content: buildWorkEmptyRoundNudgePrompt(),
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
355
405
|
const finalContent = assistantContent ||
|
|
356
406
|
'The model completed without returning a text response.';
|
|
357
|
-
workTaskService.addMessage(taskId, runId, 'assistant', 'message', finalContent,
|
|
407
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'message', finalContent, assistantContent
|
|
408
|
+
? providerStateMetadata
|
|
409
|
+
: {
|
|
410
|
+
// The placeholder tells the user what happened, but it is
|
|
411
|
+
// not something the model said; keep it out of future
|
|
412
|
+
// model context unless it carries provider replay state.
|
|
413
|
+
[WORK_EMPTY_MODEL_RESPONSE_METADATA_KEY]: true,
|
|
414
|
+
...(providerStateMetadata ?? {}),
|
|
415
|
+
});
|
|
358
416
|
// Keep completion hidden until the disposable container has either
|
|
359
417
|
// stopped or been retained for a verified live preview. Consumers
|
|
360
418
|
// can then refresh files immediately without racing teardown.
|
|
@@ -385,11 +443,15 @@ export class WorkAgentService {
|
|
|
385
443
|
? { providerMetadata: response.message.providerMetadata }
|
|
386
444
|
: {}),
|
|
387
445
|
});
|
|
446
|
+
// Responses-mode providers persist their durable replay state; every
|
|
447
|
+
// other provider persists the tool calls themselves so the next run's
|
|
448
|
+
// restored context keeps the tool history instead of dropping it.
|
|
449
|
+
const persistedAssistantMetadata = providerStateMetadata ?? toPersistedWorkChatToolCalls(run, toolCalls);
|
|
388
450
|
if (assistantContent) {
|
|
389
|
-
workTaskService.addMessage(taskId, runId, 'assistant', 'message', assistantContent,
|
|
451
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'message', assistantContent, persistedAssistantMetadata);
|
|
390
452
|
}
|
|
391
|
-
else if (
|
|
392
|
-
workTaskService.addMessage(taskId, runId, 'assistant', 'provider_state', '',
|
|
453
|
+
else if (persistedAssistantMetadata) {
|
|
454
|
+
workTaskService.addMessage(taskId, runId, 'assistant', 'provider_state', '', persistedAssistantMetadata);
|
|
393
455
|
}
|
|
394
456
|
for (const call of toolCalls) {
|
|
395
457
|
this.throwIfCancelled(runId, controller);
|
|
@@ -715,6 +777,24 @@ export class WorkAgentService {
|
|
|
715
777
|
const result = await workRuntimeService.searchFiles(task, requiredString(args.query, 'query'), optionalString(args.path) || '.');
|
|
716
778
|
return commandResult(result);
|
|
717
779
|
}
|
|
780
|
+
case 'web_search': {
|
|
781
|
+
if (!task.networkEnabled ||
|
|
782
|
+
!isWebSearchAvailable() ||
|
|
783
|
+
!userCanUseWebSearch(userModel.getUserById(task.userId))) {
|
|
784
|
+
throw new WorkAgentHttpError('Web search is not available for this task.', 403, 'WORK_WEB_SEARCH_UNAVAILABLE');
|
|
785
|
+
}
|
|
786
|
+
const query = requiredString(args.query, 'query');
|
|
787
|
+
const results = await webSearch(query, optionalInteger(args.max_results) ?? 5);
|
|
788
|
+
const content = results.length === 0
|
|
789
|
+
? 'No results.'
|
|
790
|
+
: results
|
|
791
|
+
.map((result, index) => `[${index + 1}] ${result.title}\n${result.url}${result.content ? `\n${result.content}` : ''}`)
|
|
792
|
+
.join('\n\n');
|
|
793
|
+
return {
|
|
794
|
+
content: content.slice(0, workRuntimeService.limits.maxOutputChars),
|
|
795
|
+
metadata: { query, results: results.length },
|
|
796
|
+
};
|
|
797
|
+
}
|
|
718
798
|
case 'run_command': {
|
|
719
799
|
const timeout = optionalInteger(args.timeout_ms);
|
|
720
800
|
const result = await workRuntimeService.runCommand(task, requiredString(args.command, 'command'), timeout);
|
|
@@ -824,6 +904,93 @@ function toPersistedWorkProviderState(run, providerMetadata) {
|
|
|
824
904
|
return undefined;
|
|
825
905
|
}
|
|
826
906
|
}
|
|
907
|
+
/**
|
|
908
|
+
* Chat-completions providers (Ollama, OpenRouter-style plugins) have no
|
|
909
|
+
* durable Responses replay state, so persist the tool calls themselves:
|
|
910
|
+
* without them, cross-run context restoration cannot rebuild the
|
|
911
|
+
* assistant/tool message pairs and silently drops every tool result — the
|
|
912
|
+
* model then re-discovers its own work each turn.
|
|
913
|
+
*/
|
|
914
|
+
function toPersistedWorkChatToolCalls(run, toolCalls) {
|
|
915
|
+
if (toolCalls.length === 0)
|
|
916
|
+
return undefined;
|
|
917
|
+
const persisted = {
|
|
918
|
+
[WORK_PROVIDER_STATE_METADATA_KEY]: {
|
|
919
|
+
providerType: run.providerType,
|
|
920
|
+
...(run.providerId ? { providerId: run.providerId } : {}),
|
|
921
|
+
model: run.model,
|
|
922
|
+
toolCalls: toolCalls.map(call => ({
|
|
923
|
+
id: call.id,
|
|
924
|
+
name: call.function.name,
|
|
925
|
+
arguments: boundedToolCallArgumentsJson(call.function.arguments),
|
|
926
|
+
})),
|
|
927
|
+
},
|
|
928
|
+
};
|
|
929
|
+
try {
|
|
930
|
+
return Buffer.byteLength(JSON.stringify(persisted), 'utf8') <=
|
|
931
|
+
WORK_MESSAGE_METADATA_MAX_BYTES
|
|
932
|
+
? persisted
|
|
933
|
+
: undefined;
|
|
934
|
+
}
|
|
935
|
+
catch {
|
|
936
|
+
return undefined;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function boundedToolCallArgumentsJson(args, maxBytes = WORK_CHAT_TOOL_CALL_ARGUMENTS_MAX_BYTES) {
|
|
940
|
+
let serialized;
|
|
941
|
+
try {
|
|
942
|
+
serialized = JSON.stringify(args ?? {});
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
return '{}';
|
|
946
|
+
}
|
|
947
|
+
if (Buffer.byteLength(serialized, 'utf8') <= maxBytes)
|
|
948
|
+
return serialized;
|
|
949
|
+
const truncated = {};
|
|
950
|
+
for (const [key, value] of Object.entries(args)) {
|
|
951
|
+
truncated[key] =
|
|
952
|
+
typeof value === 'string' && value.length > 512
|
|
953
|
+
? `${value.slice(0, 512)}… [truncated for replay]`
|
|
954
|
+
: value;
|
|
955
|
+
}
|
|
956
|
+
try {
|
|
957
|
+
serialized = JSON.stringify(truncated);
|
|
958
|
+
return Buffer.byteLength(serialized, 'utf8') <= maxBytes
|
|
959
|
+
? serialized
|
|
960
|
+
: '{}';
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
return '{}';
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Read back tool calls persisted by toPersistedWorkChatToolCalls. Fails
|
|
968
|
+
* closed: any malformed entry drops the whole batch, so restoration falls
|
|
969
|
+
* back to visible assistant text exactly like malformed Responses state.
|
|
970
|
+
*/
|
|
971
|
+
function persistedChatToolCalls(message) {
|
|
972
|
+
const state = objectValue(message.metadata?.[WORK_PROVIDER_STATE_METADATA_KEY]);
|
|
973
|
+
const rawCalls = state?.toolCalls;
|
|
974
|
+
if (!Array.isArray(rawCalls) || rawCalls.length === 0)
|
|
975
|
+
return undefined;
|
|
976
|
+
const calls = [];
|
|
977
|
+
for (const value of rawCalls) {
|
|
978
|
+
const call = objectValue(value);
|
|
979
|
+
if (!call ||
|
|
980
|
+
typeof call.id !== 'string' ||
|
|
981
|
+
call.id.length === 0 ||
|
|
982
|
+
typeof call.name !== 'string' ||
|
|
983
|
+
call.name.length === 0) {
|
|
984
|
+
return undefined;
|
|
985
|
+
}
|
|
986
|
+
calls.push({
|
|
987
|
+
id: call.id,
|
|
988
|
+
name: call.name,
|
|
989
|
+
arguments: typeof call.arguments === 'string' ? call.arguments : '{}',
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
return calls;
|
|
993
|
+
}
|
|
827
994
|
export function restorePersistedWorkContext(messages, provider, expectedStateScope) {
|
|
828
995
|
const restored = [];
|
|
829
996
|
let pendingGroup;
|
|
@@ -854,14 +1021,31 @@ export function restorePersistedWorkContext(messages, provider, expectedStateSco
|
|
|
854
1021
|
(message.role === 'assistant' && message.kind === 'message')) {
|
|
855
1022
|
flushPendingGroup();
|
|
856
1023
|
const providerMetadata = matchingWorkProviderMetadata(message, provider, expectedStateScope);
|
|
1024
|
+
// Chat-mode rounds persist their tool calls directly (no Responses
|
|
1025
|
+
// replay state exists for them). Restore those only when the current
|
|
1026
|
+
// provider requires no such state either — the strict Responses
|
|
1027
|
+
// invariant stays fail-closed.
|
|
1028
|
+
const chatCalls = !providerMetadata && !expectedStateScope
|
|
1029
|
+
? persistedChatToolCalls(message)
|
|
1030
|
+
: undefined;
|
|
857
1031
|
const responseCalls = providerMetadata
|
|
858
1032
|
? responseFunctionCalls(providerMetadata)
|
|
859
|
-
: [];
|
|
860
|
-
const rawFunctionCallCount =
|
|
861
|
-
? providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY]
|
|
862
|
-
|
|
1033
|
+
: (chatCalls ?? []);
|
|
1034
|
+
const rawFunctionCallCount = providerMetadata
|
|
1035
|
+
? Array.isArray(providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY])
|
|
1036
|
+
? providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY].filter(item => objectValue(item)?.type === 'function_call').length
|
|
1037
|
+
: 0
|
|
1038
|
+
: responseCalls.length;
|
|
863
1039
|
const expectedCallIds = new Set(responseCalls.map(call => call.id));
|
|
864
|
-
if (
|
|
1040
|
+
if (!providerMetadata &&
|
|
1041
|
+
message.metadata?.[WORK_EMPTY_MODEL_RESPONSE_METADATA_KEY] === true) {
|
|
1042
|
+
// The empty-response placeholder informs the user; it is not
|
|
1043
|
+
// something the model said, so keep it out of model context.
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
if (message.kind === 'provider_state' &&
|
|
1047
|
+
!providerMetadata &&
|
|
1048
|
+
!chatCalls) {
|
|
865
1049
|
continue;
|
|
866
1050
|
}
|
|
867
1051
|
if (message.kind !== 'message' && message.kind !== 'provider_state') {
|
|
@@ -1145,6 +1329,7 @@ function validateToolCallArguments(call) {
|
|
|
1145
1329
|
case 'move_file':
|
|
1146
1330
|
return invalidRequiredString('from') || invalidRequiredString('to');
|
|
1147
1331
|
case 'search_files':
|
|
1332
|
+
case 'web_search':
|
|
1148
1333
|
return invalidRequiredString('query');
|
|
1149
1334
|
case 'run_command':
|
|
1150
1335
|
return invalidRequiredString('command');
|
|
@@ -1192,6 +1377,9 @@ function summarizeToolCall(call) {
|
|
|
1192
1377
|
includeString('path', args.path);
|
|
1193
1378
|
includeString('query', args.query, 500);
|
|
1194
1379
|
break;
|
|
1380
|
+
case 'web_search':
|
|
1381
|
+
includeString('query', args.query, 500);
|
|
1382
|
+
break;
|
|
1195
1383
|
case 'run_command':
|
|
1196
1384
|
includeString('command', args.command, 2000);
|
|
1197
1385
|
if (typeof args.timeout_ms === 'number') {
|