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.
Files changed (175) hide show
  1. package/README.md +31 -21
  2. package/backend/dist/db.d.ts.map +1 -1
  3. package/backend/dist/db.js +2 -0
  4. package/backend/dist/db.js.map +1 -1
  5. package/backend/dist/index.d.ts.map +1 -1
  6. package/backend/dist/index.js +2 -0
  7. package/backend/dist/index.js.map +1 -1
  8. package/backend/dist/routes/chat.d.ts.map +1 -1
  9. package/backend/dist/routes/chat.js +97 -10
  10. package/backend/dist/routes/chat.js.map +1 -1
  11. package/backend/dist/routes/libreClaw.d.ts.map +1 -1
  12. package/backend/dist/routes/libreClaw.js +41 -0
  13. package/backend/dist/routes/libreClaw.js.map +1 -1
  14. package/backend/dist/routes/ollama.d.ts.map +1 -1
  15. package/backend/dist/routes/ollama.js +52 -2
  16. package/backend/dist/routes/ollama.js.map +1 -1
  17. package/backend/dist/routes/search.d.ts +3 -0
  18. package/backend/dist/routes/search.d.ts.map +1 -0
  19. package/backend/dist/routes/search.js +143 -0
  20. package/backend/dist/routes/search.js.map +1 -0
  21. package/backend/dist/services/agentAccessService.d.ts +6 -0
  22. package/backend/dist/services/agentAccessService.d.ts.map +1 -0
  23. package/backend/dist/services/agentAccessService.js +58 -0
  24. package/backend/dist/services/agentAccessService.js.map +1 -0
  25. package/backend/dist/services/agentCliService.d.ts.map +1 -1
  26. package/backend/dist/services/agentCliService.js +2 -1
  27. package/backend/dist/services/agentCliService.js.map +1 -1
  28. package/backend/dist/services/authService.d.ts +1 -0
  29. package/backend/dist/services/authService.d.ts.map +1 -1
  30. package/backend/dist/services/authService.js +2 -0
  31. package/backend/dist/services/authService.js.map +1 -1
  32. package/backend/dist/services/documentService.d.ts +2 -0
  33. package/backend/dist/services/documentService.d.ts.map +1 -1
  34. package/backend/dist/services/documentService.js +51 -23
  35. package/backend/dist/services/documentService.js.map +1 -1
  36. package/backend/dist/services/modelAccessService.d.ts +12 -0
  37. package/backend/dist/services/modelAccessService.d.ts.map +1 -0
  38. package/backend/dist/services/modelAccessService.js +66 -0
  39. package/backend/dist/services/modelAccessService.js.map +1 -0
  40. package/backend/dist/services/webSearchService.d.ts +64 -0
  41. package/backend/dist/services/webSearchService.d.ts.map +1 -0
  42. package/backend/dist/services/webSearchService.js +230 -0
  43. package/backend/dist/services/webSearchService.js.map +1 -0
  44. package/backend/dist/services/workAgentGuidance.d.ts +1 -0
  45. package/backend/dist/services/workAgentGuidance.d.ts.map +1 -1
  46. package/backend/dist/services/workAgentGuidance.js +3 -0
  47. package/backend/dist/services/workAgentGuidance.js.map +1 -1
  48. package/backend/dist/services/workAgentService.d.ts +6 -0
  49. package/backend/dist/services/workAgentService.d.ts.map +1 -1
  50. package/backend/dist/services/workAgentService.js +199 -11
  51. package/backend/dist/services/workAgentService.js.map +1 -1
  52. package/backend/dist/services/workPolicyService.d.ts.map +1 -1
  53. package/backend/dist/services/workPolicyService.js +61 -11
  54. package/backend/dist/services/workPolicyService.js.map +1 -1
  55. package/backend/dist/storage.d.ts.map +1 -1
  56. package/backend/dist/storage.js +3 -3
  57. package/backend/dist/storage.js.map +1 -1
  58. package/backend/dist/storageMappers.d.ts +1 -0
  59. package/backend/dist/storageMappers.d.ts.map +1 -1
  60. package/backend/dist/storageMappers.js +1 -0
  61. package/backend/dist/storageMappers.js.map +1 -1
  62. package/backend/dist/types/index.d.ts +1 -0
  63. package/backend/dist/types/index.d.ts.map +1 -1
  64. package/backend/dist/types/index.js.map +1 -1
  65. package/backend/dist/utils/chatDocumentContext.d.ts +7 -0
  66. package/backend/dist/utils/chatDocumentContext.d.ts.map +1 -1
  67. package/backend/dist/utils/chatDocumentContext.js +38 -30
  68. package/backend/dist/utils/chatDocumentContext.js.map +1 -1
  69. package/backend/dist/websocketServer.d.ts.map +1 -1
  70. package/backend/dist/websocketServer.js +79 -15
  71. package/backend/dist/websocketServer.js.map +1 -1
  72. package/backend/package.json +1 -1
  73. package/frontend/dist/artifact-runtime/.build-fingerprint +1 -1
  74. package/frontend/dist/assets/index-0wfi-EpC.js +40 -0
  75. package/frontend/dist/assets/index-D7m1zWWg.css +1 -0
  76. package/frontend/dist/index.html +12 -12
  77. package/frontend/dist/js/ArtifactContainer-DPRPcuES.js +1 -0
  78. package/frontend/dist/js/{ArtifactDemoPage-BOlB1lS-.js → ArtifactDemoPage-DzwPlz9J.js} +1 -1
  79. package/frontend/dist/js/{ArtifactSandboxFrame-DqU5GDrI.js → ArtifactSandboxFrame-CKi4mSTH.js} +2 -2
  80. package/frontend/dist/js/{ArtifactSlideOutPanel-B0I-hCux.js → ArtifactSlideOutPanel-BskRCzP0.js} +1 -1
  81. package/frontend/dist/js/ChatPage-DRutCyJ5.js +16 -0
  82. package/frontend/dist/js/FirstTimeSetup-DU50YWrE.js +1 -0
  83. package/frontend/dist/js/GalleryPage-DxXIUesf.js +1 -0
  84. package/frontend/dist/js/HomePage-Bew9kMKx.js +1 -0
  85. package/frontend/dist/js/{LibreClawPage-DUd3tjQE.js → LibreClawPage-Cy9y3YlF.js} +1 -1
  86. package/frontend/dist/js/{ModelManagerModals-D9j9RECi.js → ModelManagerModals-D-NxeVvV.js} +1 -1
  87. package/frontend/dist/js/ModelSelector-DXDcDmDR.js +1 -0
  88. package/frontend/dist/js/ModelsPage-Bj2reiF9.js +2 -0
  89. package/frontend/dist/js/{NotesPage-CTutDN50.js → NotesPage-CJKEbj3J.js} +1 -1
  90. package/frontend/dist/js/{PersonasPage-C6iTMI3N.js → PersonasPage-bgWF9S7b.js} +2 -2
  91. package/frontend/dist/js/{PluginUsagePage-BQQ0-_PP.js → PluginUsagePage-CRkD1Tz6.js} +1 -1
  92. package/frontend/dist/js/{RichMessageContent-DPqeqxkQ.js → RichMessageContent-DeNjCiyN.js} +1 -1
  93. package/frontend/dist/js/SettingsModal-BUUlkZcv.js +1 -0
  94. package/frontend/dist/js/SystemPage-DcO51urv.js +1 -0
  95. package/frontend/dist/js/UserManagementPage-DLiYw6xG.js +1 -0
  96. package/frontend/dist/js/WorkPage-MMQ0Y0U5.js +5 -0
  97. package/frontend/dist/js/{api-iCpOEijB.js → api-CZURQtuc.js} +4 -4
  98. package/frontend/dist/js/{appStore-DTd6dWg2.js → appStore-sGaWMSDr.js} +2 -2
  99. package/frontend/dist/js/ar-Bm27TCMQ.js +2 -0
  100. package/frontend/dist/js/{authStore-CuK4HUVh.js → authStore-uaj0hswV.js} +3 -3
  101. package/frontend/dist/js/bn-C7CHC42H.js +2 -0
  102. package/frontend/dist/js/chatStore-tRx0Ulq0.js +2 -0
  103. package/frontend/dist/js/{config--Xp9rZtJ.js → config-D-jxRG52.js} +1 -1
  104. package/frontend/dist/js/cs-zGEtwQn_.js +2 -0
  105. package/frontend/dist/js/da-B-0sHNTu.js +2 -0
  106. package/frontend/dist/js/de-UZNE6t2h.js +2 -0
  107. package/frontend/dist/js/es-CPLnFYIw.js +2 -0
  108. package/frontend/dist/js/{estree-BAQkciRX.js → estree-CRuerC6a.js} +1 -1
  109. package/frontend/dist/js/fr-Br1o0WVz.js +2 -0
  110. package/frontend/dist/js/hi-BcEvNEpb.js +2 -0
  111. package/frontend/dist/js/{html-DxA-Bcvi.js → html-BYKlz3Jp.js} +1 -1
  112. package/frontend/dist/js/i18n-DJTg6xWw.js +2 -0
  113. package/frontend/dist/js/id-C30L2DoT.js +2 -0
  114. package/frontend/dist/js/is-BAEhfi5Z.js +2 -0
  115. package/frontend/dist/js/it-HWnfQXZY.js +2 -0
  116. package/frontend/dist/js/ja-Dvux1mon.js +2 -0
  117. package/frontend/dist/js/ko-agDQ__KP.js +2 -0
  118. package/frontend/dist/js/{markdown-IsZpJ5ke.js → markdown-DFy8bILU.js} +1 -1
  119. package/frontend/dist/js/{messageCodeStyles-CsV3zwyK.js → messageCodeStyles-_gGirbe-.js} +2 -2
  120. package/frontend/dist/js/ms-aNRa2QX7.js +2 -0
  121. package/frontend/dist/js/nl-BtkiDUn-.js +2 -0
  122. package/frontend/dist/js/pl-SEO0lBnX.js +2 -0
  123. package/frontend/dist/js/pt-BYtjmvq1.js +2 -0
  124. package/frontend/dist/js/ru-B-ynj2hU.js +2 -0
  125. package/frontend/dist/js/{standalone-CK3b5Ktq.js → standalone-jOqWoOmp.js} +1 -1
  126. package/frontend/dist/js/sv-CliKR15U.js +2 -0
  127. package/frontend/dist/js/th-DJQyDr9J.js +2 -0
  128. package/frontend/dist/js/tr-BBGKZ1la.js +2 -0
  129. package/frontend/dist/js/{typescript-BMYmVohw.js → typescript-TwGXpgeF.js} +1 -1
  130. package/frontend/dist/js/{ui-C2kNYwfG.js → ui-DSFUl2vT.js} +2 -2
  131. package/frontend/dist/js/{ui-vendor-D_pYT79f.js → ui-vendor-xUzKqAXI.js} +1 -1
  132. package/frontend/dist/js/uk-C1GcFWxE.js +2 -0
  133. package/frontend/dist/js/vi-CIxgICOQ.js +2 -0
  134. package/frontend/dist/js/{workApi-C33ZrPws.js → workApi-kClHFVkJ.js} +2 -2
  135. package/frontend/dist/js/zh-Cm7vsQ9s.js +2 -0
  136. package/package.json +2 -2
  137. package/frontend/dist/assets/index-Z4-oLWDC.js +0 -2
  138. package/frontend/dist/assets/index-tBUyDbHh.css +0 -1
  139. package/frontend/dist/js/ArtifactContainer-DQRF4rh-.js +0 -1
  140. package/frontend/dist/js/ChatPage-DyV0_oCt.js +0 -16
  141. package/frontend/dist/js/FirstTimeSetup-ButTmjp0.js +0 -1
  142. package/frontend/dist/js/GalleryPage-CaT63b-s.js +0 -1
  143. package/frontend/dist/js/HomePage-BWyg3nHu.js +0 -1
  144. package/frontend/dist/js/ModelSelector-COFcJnMJ.js +0 -1
  145. package/frontend/dist/js/ModelsPage-BRbTEXNL.js +0 -2
  146. package/frontend/dist/js/SettingsModal-C_1k75Kv.js +0 -1
  147. package/frontend/dist/js/SystemPage-CUecyRPh.js +0 -1
  148. package/frontend/dist/js/UserManagementPage-CZ4KMOY9.js +0 -1
  149. package/frontend/dist/js/WorkPage-BqDcfXV6.js +0 -5
  150. package/frontend/dist/js/ar-C8YV99Y-.js +0 -2
  151. package/frontend/dist/js/bn-C9dcls7_.js +0 -2
  152. package/frontend/dist/js/chatStore-VNT0eHRB.js +0 -2
  153. package/frontend/dist/js/cs-DtL8VeeX.js +0 -2
  154. package/frontend/dist/js/da-U3aNn1aF.js +0 -2
  155. package/frontend/dist/js/de-DzuyeVpX.js +0 -2
  156. package/frontend/dist/js/es-OiU8plii.js +0 -2
  157. package/frontend/dist/js/fr-DDa3R8bs.js +0 -2
  158. package/frontend/dist/js/hi-D8Y0hl34.js +0 -2
  159. package/frontend/dist/js/i18n-BqWNFeNo.js +0 -2
  160. package/frontend/dist/js/id-BwcWtkBv.js +0 -2
  161. package/frontend/dist/js/is-DzfQHQih.js +0 -2
  162. package/frontend/dist/js/it-C5VU3_Je.js +0 -2
  163. package/frontend/dist/js/ja-DLhOhxvf.js +0 -2
  164. package/frontend/dist/js/ko-CCKiyQD6.js +0 -2
  165. package/frontend/dist/js/ms-CXXkPvWw.js +0 -2
  166. package/frontend/dist/js/nl-CA_e0Epl.js +0 -2
  167. package/frontend/dist/js/pl-BG32wdpx.js +0 -2
  168. package/frontend/dist/js/pt-BzRGWQXO.js +0 -2
  169. package/frontend/dist/js/ru-DBjn_Q-Q.js +0 -2
  170. package/frontend/dist/js/sv-Hz_iRGdF.js +0 -2
  171. package/frontend/dist/js/th-MsyXlGH9.js +0 -2
  172. package/frontend/dist/js/tr-Cofmy_ef.js +0 -2
  173. package/frontend/dist/js/uk-B2o05LCR.js +0 -2
  174. package/frontend/dist/js/vi-CZh8uJgV.js +0 -2
  175. 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":"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"}
@@ -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":"AAmCA,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;AAEpE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EA8EtD,CAAC;AAEF,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;YAojBP,yBAAyB;IA6BvC,OAAO,CAAC,2BAA2B;IAsBnC,OAAO,CAAC,eAAe;YA0BT,WAAW;IAgHzB,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;AA8CD,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,CAkKrB;AA4ED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,kBAAkB,GAC3B,YAAY,EAAE,CA+GhB;AAwLD,eAAO,MAAM,gBAAgB,kBAAyB,CAAC;eACxC,gBAAgB"}
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: WORK_TOOL_SCHEMAS,
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, providerStateMetadata);
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, providerStateMetadata);
451
+ workTaskService.addMessage(taskId, runId, 'assistant', 'message', assistantContent, persistedAssistantMetadata);
390
452
  }
391
- else if (providerStateMetadata) {
392
- workTaskService.addMessage(taskId, runId, 'assistant', 'provider_state', '', providerStateMetadata);
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 = Array.isArray(providerMetadata?.[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY])
861
- ? providerMetadata[OPENAI_RESPONSES_OUTPUT_ITEMS_METADATA_KEY].filter(item => objectValue(item)?.type === 'function_call').length
862
- : 0;
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 (message.kind === 'provider_state' && !providerMetadata) {
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') {