neoagent 2.3.1-beta.98 → 2.4.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 (52) hide show
  1. package/.env.example +6 -3
  2. package/flutter_app/lib/main.dart +1 -0
  3. package/flutter_app/lib/main_integrations.dart +21 -2
  4. package/flutter_app/lib/main_models.dart +60 -0
  5. package/flutter_app/lib/main_theme.dart +31 -2
  6. package/flutter_app/macos/Runner/AppDelegate.swift +11 -1
  7. package/flutter_app/macos/Runner/DebugProfile.entitlements +4 -0
  8. package/flutter_app/macos/Runner/Release.entitlements +4 -0
  9. package/flutter_app/pubspec.lock +5 -5
  10. package/lib/manager.js +164 -2
  11. package/package.json +1 -1
  12. package/server/db/database.js +85 -0
  13. package/server/public/.last_build_id +1 -1
  14. package/server/public/assets/NOTICES +971 -1066
  15. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  16. package/server/public/assets/shaders/ink_sparkle.frag +1 -1
  17. package/server/public/assets/shaders/stretch_effect.frag +1 -1
  18. package/server/public/canvaskit/canvaskit.js +2 -2
  19. package/server/public/canvaskit/canvaskit.js.symbols +11796 -11733
  20. package/server/public/canvaskit/canvaskit.wasm +0 -0
  21. package/server/public/canvaskit/chromium/canvaskit.js +2 -2
  22. package/server/public/canvaskit/chromium/canvaskit.js.symbols +10706 -10643
  23. package/server/public/canvaskit/chromium/canvaskit.wasm +0 -0
  24. package/server/public/canvaskit/experimental_webparagraph/canvaskit.js +171 -0
  25. package/server/public/canvaskit/experimental_webparagraph/canvaskit.js.symbols +9134 -0
  26. package/server/public/canvaskit/experimental_webparagraph/canvaskit.wasm +0 -0
  27. package/server/public/canvaskit/skwasm.js +14 -14
  28. package/server/public/canvaskit/skwasm.js.symbols +12787 -12676
  29. package/server/public/canvaskit/skwasm.wasm +0 -0
  30. package/server/public/canvaskit/skwasm_heavy.js +14 -14
  31. package/server/public/canvaskit/skwasm_heavy.js.symbols +14400 -14286
  32. package/server/public/canvaskit/skwasm_heavy.wasm +0 -0
  33. package/server/public/canvaskit/wimp.js +94 -95
  34. package/server/public/canvaskit/wimp.js.symbols +11325 -11177
  35. package/server/public/canvaskit/wimp.wasm +0 -0
  36. package/server/public/flutter_bootstrap.js +2 -2
  37. package/server/public/main.dart.js +83866 -82074
  38. package/server/routes/integrations.js +2 -2
  39. package/server/routes/memory.js +73 -0
  40. package/server/services/ai/engine.js +65 -26
  41. package/server/services/ai/models.js +21 -0
  42. package/server/services/ai/preModelCompaction.js +191 -0
  43. package/server/services/ai/providers/claudeCode.js +273 -0
  44. package/server/services/ai/providers/openaiCodex.js +226 -41
  45. package/server/services/ai/settings.js +11 -1
  46. package/server/services/integrations/google/provider.js +78 -0
  47. package/server/services/integrations/manager.js +29 -13
  48. package/server/services/manager.js +25 -0
  49. package/server/services/memory/ingestion.js +486 -0
  50. package/server/services/memory/manager.js +422 -0
  51. package/server/services/memory/openhuman_uplift.test.js +98 -0
  52. package/server/services/widgets/focus_widget.js +45 -4
@@ -89,7 +89,7 @@ router.get('/oauth/callback', async (req, res) => {
89
89
  window.opener.postMessage(${payload}, ${trustedOrigin});
90
90
  window.close();
91
91
  } else {
92
- window.location.href = '/';
92
+ window.close();
93
93
  }
94
94
  </script>
95
95
  <p>Authentication successful. You can close this window.</p>
@@ -116,7 +116,7 @@ router.get('/oauth/callback', async (req, res) => {
116
116
  window.opener.postMessage(${payload}, ${trustedOrigin});
117
117
  window.close();
118
118
  } else {
119
- window.location.href = '/?page=skills';
119
+ window.close();
120
120
  }
121
121
  </script>
122
122
  <p>Authentication successful. You can close this window.</p>
@@ -145,6 +145,79 @@ router.get('/memories', (req, res) => {
145
145
  }
146
146
  });
147
147
 
148
+ router.get('/ingestion/status', (req, res) => {
149
+ const mm = req.app.locals.memoryManager;
150
+ const ingestionService = req.app.locals.memoryIngestionService;
151
+ const userId = req.session.userId;
152
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
153
+ try {
154
+ res.json({
155
+ agentId,
156
+ overview: mm.getIngestionOverview(userId, { agentId }),
157
+ jobs: mm.listIngestionJobs(userId, { agentId }),
158
+ recentChanges: mm.listRecentKnowledgeChanges(userId, { agentId }),
159
+ connections: ingestionService?.listConnectionStatuses?.(userId, { agentId }) || [],
160
+ });
161
+ } catch (err) {
162
+ res.status(500).json({ error: sanitizeError(err) });
163
+ }
164
+ });
165
+
166
+ router.post('/ingestion/documents', async (req, res) => {
167
+ const ingestionService = req.app.locals.memoryIngestionService;
168
+ const userId = req.session.userId;
169
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
170
+ if (!ingestionService) {
171
+ return res.status(503).json({ error: 'Memory ingestion service is unavailable.' });
172
+ }
173
+ const documents = Array.isArray(req.body?.documents) ? req.body.documents : [];
174
+ if (!documents.length) {
175
+ return res.status(400).json({ error: 'documents must contain at least one item.' });
176
+ }
177
+ try {
178
+ const result = await ingestionService.ingestDocuments(userId, documents, {
179
+ agentId,
180
+ sourceType: req.body.sourceType,
181
+ providerKey: req.body.providerKey,
182
+ connectionId: req.body.connectionId,
183
+ sourceAccount: req.body.sourceAccount,
184
+ metadata: normalizeMetadata(req.body.metadata),
185
+ });
186
+ res.json(result);
187
+ } catch (err) {
188
+ res.status(400).json({ error: sanitizeError(err) });
189
+ }
190
+ });
191
+
192
+ router.get('/knowledge-views', (req, res) => {
193
+ const mm = req.app.locals.memoryManager;
194
+ const userId = req.session.userId;
195
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
196
+ try {
197
+ res.json(mm.listKnowledgeViews(userId, {
198
+ agentId,
199
+ viewType: req.query.viewType || null,
200
+ limit: req.query.limit,
201
+ }));
202
+ } catch (err) {
203
+ res.status(500).json({ error: sanitizeError(err) });
204
+ }
205
+ });
206
+
207
+ router.post('/knowledge-views/materialize', (req, res) => {
208
+ const mm = req.app.locals.memoryManager;
209
+ const userId = req.session.userId;
210
+ const agentId = resolveAgentId(userId, getAgentIdFromRequest(req));
211
+ try {
212
+ res.json({
213
+ agentId,
214
+ views: mm.materializeKnowledgeViews(userId, { agentId }),
215
+ });
216
+ } catch (err) {
217
+ res.status(500).json({ error: sanitizeError(err) });
218
+ }
219
+ });
220
+
148
221
  // Save a new memory
149
222
  router.post('/memories', async (req, res) => {
150
223
  const mm = req.app.locals.memoryManager;
@@ -2,6 +2,7 @@ const { v4: uuidv4 } = require('uuid');
2
2
  const fs = require('fs');
3
3
  const db = require('../../db/database');
4
4
  const { compact } = require('./compaction');
5
+ const { compactPayloadForModel } = require('./preModelCompaction');
5
6
  const {
6
7
  getConversationContext,
7
8
  buildSummaryCarrier,
@@ -1563,6 +1564,37 @@ class AgentEngine {
1563
1564
  let provider = selectedProvider.provider;
1564
1565
  let model = selectedProvider.model;
1565
1566
  let providerName = selectedProvider.providerName;
1567
+ const switchToFallbackModel = async (failedModel, error, phase) => {
1568
+ const fallbackModelId = await getFailureFallbackModelId(userId, agentId, failedModel, aiSettings.fallback_model_id);
1569
+ if (!fallbackModelId || fallbackModelId === failedModel) return false;
1570
+ console.log(`[Engine] ${phase} failed on ${failedModel}; attempting fallback to: ${fallbackModelId}`);
1571
+ this.emit(userId, 'run:interim', {
1572
+ runId,
1573
+ message: `Model service failed on ${failedModel}; retrying with ${fallbackModelId}.`,
1574
+ phase: 'model_fallback'
1575
+ });
1576
+ const fallback = await getProviderForUser(
1577
+ userId,
1578
+ userMessage,
1579
+ triggerType === 'subagent',
1580
+ fallbackModelId,
1581
+ providerStatusConfig
1582
+ );
1583
+ provider = fallback.provider;
1584
+ model = fallback.model;
1585
+ providerName = fallback.providerName;
1586
+ return true;
1587
+ };
1588
+ const runWithModelFallback = async (phase, fn) => {
1589
+ try {
1590
+ return await fn();
1591
+ } catch (err) {
1592
+ const failedModel = model;
1593
+ const switched = await switchToFallbackModel(failedModel, err, phase);
1594
+ if (!switched) throw err;
1595
+ return await fn();
1596
+ }
1597
+ };
1566
1598
 
1567
1599
  const runTitle = generateTitle(userMessage);
1568
1600
  db.prepare(`INSERT OR REPLACE INTO agent_runs(id, user_id, agent_id, title, status, trigger_type, trigger_source, model)
@@ -1683,6 +1715,7 @@ class AgentEngine {
1683
1715
  let modelFailureRecoveries = 0;
1684
1716
  let promptMetrics = {};
1685
1717
  let toolExecutions = [];
1718
+ let compactionMetrics = [];
1686
1719
  let analysis = null;
1687
1720
  let plan = null;
1688
1721
  let verification = null;
@@ -1697,7 +1730,7 @@ class AgentEngine {
1697
1730
  if (options.skipTaskAnalysis === true) {
1698
1731
  analysis = buildSkipTaskAnalysisResult(options.forceMode);
1699
1732
  } else {
1700
- const analysisResult = await this.analyzeTask({
1733
+ const analysisResult = await runWithModelFallback('task analysis', () => this.analyzeTask({
1701
1734
  provider,
1702
1735
  providerName,
1703
1736
  model,
@@ -1707,7 +1740,7 @@ class AgentEngine {
1707
1740
  forceMode: options.forceMode || null,
1708
1741
  userMessage,
1709
1742
  options: { ...options, triggerSource },
1710
- });
1743
+ }));
1711
1744
  analysisUsage = analysisResult.usage || 0;
1712
1745
  totalTokens += analysisUsage;
1713
1746
  analysis = applyForcedAnalysisMode({ ...analysisResult.analysis }, options.forceMode);
@@ -1803,7 +1836,7 @@ class AgentEngine {
1803
1836
  }
1804
1837
 
1805
1838
  if (analysis.mode === 'plan_execute') {
1806
- const planResult = await this.createExecutionPlan({
1839
+ const planResult = await runWithModelFallback('execution planning', () => this.createExecutionPlan({
1807
1840
  provider,
1808
1841
  providerName,
1809
1842
  model,
@@ -1811,7 +1844,7 @@ class AgentEngine {
1811
1844
  analysis,
1812
1845
  capabilitySummary,
1813
1846
  options,
1814
- });
1847
+ }));
1815
1848
  totalTokens += planResult.usage || 0;
1816
1849
  plan = planResult.plan;
1817
1850
  stepIndex += 1;
@@ -1978,26 +2011,13 @@ class AgentEngine {
1978
2011
  await tryModelCall();
1979
2012
  } catch (err) {
1980
2013
  const modelError = String(err?.message || 'Model call failed');
1981
- const isFatalModelError = /no ai providers? are currently available|missing an api key|disabled in settings|unauthorized|forbidden|authentication failed/i
1982
- .test(modelError);
1983
2014
 
1984
- if (!isFatalModelError && modelFailureRecoveries < loopPolicy.maxModelFailureRecoveries) {
2015
+ if (modelFailureRecoveries < loopPolicy.maxModelFailureRecoveries) {
2016
+ const failedModel = model;
2017
+ const switched = await switchToFallbackModel(failedModel, err, 'model turn');
2018
+ if (!switched) throw err;
1985
2019
  modelFailureRecoveries += 1;
1986
2020
  failedStepCount += 1;
1987
- const failedModel = model;
1988
- const fallbackModelId = await getFailureFallbackModelId(userId, agentId, model, aiSettings.fallback_model_id);
1989
- if (fallbackModelId && fallbackModelId !== model) {
1990
- const fallback = await getProviderForUser(
1991
- userId,
1992
- userMessage,
1993
- triggerType === 'subagent',
1994
- fallbackModelId,
1995
- providerStatusConfig
1996
- );
1997
- provider = fallback.provider;
1998
- model = fallback.model;
1999
- providerName = fallback.providerName;
2000
- }
2001
2021
  messages.push({
2002
2022
  role: 'system',
2003
2023
  content: buildModelFailureLoopPrompt({
@@ -2082,7 +2102,7 @@ class AgentEngine {
2082
2102
  }
2083
2103
  if (iteration < maxIterations) {
2084
2104
  const fallbackStatus = (toolExecutions.length > 0 || failedStepCount > 0 || messagingSent) ? 'continue' : 'complete';
2085
- const loopState = await this.decideLoopState({
2105
+ const loopState = await runWithModelFallback('loop decision', () => this.decideLoopState({
2086
2106
  provider,
2087
2107
  providerName,
2088
2108
  model,
@@ -2098,7 +2118,7 @@ class AgentEngine {
2098
2118
  maxIterations,
2099
2119
  options,
2100
2120
  fallbackStatus,
2101
- });
2121
+ }));
2102
2122
  totalTokens += loopState.usage || 0;
2103
2123
  if (loopState.decision.status === 'continue') {
2104
2124
  messages.push({
@@ -2278,14 +2298,33 @@ class AgentEngine {
2278
2298
  evidenceSources: [...new Set(toolExecutions.map((item) => item.evidenceSource).filter(Boolean))],
2279
2299
  subagentState: this.listSubagents(runId),
2280
2300
  deliverableArtifacts,
2301
+ compactionMetrics: compactionMetrics.slice(-20),
2281
2302
  });
2282
2303
 
2304
+ const modelPayload = compactPayloadForModel(toolName, toolResult);
2305
+ if (modelPayload.metrics?.applied) {
2306
+ const metric = {
2307
+ toolName,
2308
+ stepId,
2309
+ ...modelPayload.metrics,
2310
+ createdAt: new Date().toISOString(),
2311
+ };
2312
+ compactionMetrics.push(metric);
2313
+ this.persistRunMetadata(runId, {
2314
+ compactionMetrics: compactionMetrics.slice(-20),
2315
+ });
2316
+ this.recordRunEvent(userId, runId, 'pre_model_compaction_applied', {
2317
+ toolName,
2318
+ metrics: modelPayload.metrics,
2319
+ }, { agentId, stepId });
2320
+ }
2321
+
2283
2322
  const toolResultLimits = resolveToolResultLimits(toolName, loopPolicy);
2284
2323
  const toolMessage = {
2285
2324
  role: 'tool',
2286
2325
  name: toolName,
2287
2326
  tool_call_id: toolCall.id,
2288
- content: compactToolResult(toolName, toolArgs, toolResult, {
2327
+ content: compactToolResult(toolName, toolArgs, modelPayload.result, {
2289
2328
  softLimit: toolResultLimits.softLimit,
2290
2329
  hardLimit: toolResultLimits.hardLimit,
2291
2330
  })
@@ -2456,7 +2495,7 @@ class AgentEngine {
2456
2495
  toolExecutions,
2457
2496
  finalReply: finalResponseText,
2458
2497
  })) {
2459
- const verificationResult = await this.verifyFinalResponse({
2498
+ const verificationResult = await runWithModelFallback('final verification', () => this.verifyFinalResponse({
2460
2499
  provider,
2461
2500
  providerName,
2462
2501
  model,
@@ -2465,7 +2504,7 @@ class AgentEngine {
2465
2504
  toolExecutions,
2466
2505
  finalReply: finalResponseText,
2467
2506
  options,
2468
- });
2507
+ }));
2469
2508
  totalTokens += verificationResult.usage || 0;
2470
2509
  verification = verificationResult.verification;
2471
2510
  if (verification.final_reply) {
@@ -5,6 +5,7 @@ const { OllamaProvider } = require('./providers/ollama');
5
5
  const { OpenAIProvider } = require('./providers/openai');
6
6
  const { GithubCopilotProvider } = require('./providers/githubCopilot');
7
7
  const { OpenAICodexProvider } = require('./providers/openaiCodex');
8
+ const { ClaudeCodeProvider } = require('./providers/claudeCode');
8
9
  const {
9
10
  AI_PROVIDER_DEFINITIONS,
10
11
  getProviderConfigs,
@@ -72,6 +73,24 @@ const STATIC_MODELS = [
72
73
  provider: 'anthropic',
73
74
  purpose: 'fast'
74
75
  },
76
+ {
77
+ id: 'claude-opus-4-7',
78
+ label: 'Claude Opus 4.7 (Claude Code / Default)',
79
+ provider: 'claude-code',
80
+ purpose: 'general'
81
+ },
82
+ {
83
+ id: 'claude-sonnet-4-6',
84
+ label: 'Claude Sonnet 4.6 (Claude Code / Fast)',
85
+ provider: 'claude-code',
86
+ purpose: 'coding'
87
+ },
88
+ {
89
+ id: 'claude-haiku-4-5-20251001',
90
+ label: 'Claude Haiku 4.5 (Claude Code / Subagents)',
91
+ provider: 'claude-code',
92
+ purpose: 'fast'
93
+ },
75
94
  {
76
95
  id: 'gemini-3.1-flash-lite-preview',
77
96
  label: 'Gemini 3.1 Flash Lite (Preview)',
@@ -338,6 +357,8 @@ function createProviderInstance(providerStr, userId = null, configOverrides = {}
338
357
  return new GithubCopilotProvider({ apiKey: runtime.apiKey, ...providerOverrides });
339
358
  } else if (providerStr === 'openai-codex') {
340
359
  return new OpenAICodexProvider({ apiKey: runtime.apiKey, ...providerOverrides });
360
+ } else if (providerStr === 'claude-code') {
361
+ return new ClaudeCodeProvider({ apiKey: runtime.apiKey, ...providerOverrides });
341
362
  }
342
363
  throw new Error(`Unknown provider: ${providerStr}`);
343
364
  }
@@ -0,0 +1,191 @@
1
+ 'use strict';
2
+
3
+ function clampText(value, maxLength = 2000) {
4
+ const text = String(value || '').trim();
5
+ if (text.length <= maxLength) return text;
6
+ return `${text.slice(0, Math.max(0, maxLength - 1)).trimEnd()}...`;
7
+ }
8
+
9
+ function stripHtmlTags(input) {
10
+ return String(input || '')
11
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
12
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
13
+ .replace(/<[^>]+>/g, ' ')
14
+ .replace(/&nbsp;/gi, ' ')
15
+ .replace(/&amp;/gi, '&')
16
+ .replace(/&lt;/gi, '<')
17
+ .replace(/&gt;/gi, '>')
18
+ .replace(/\s+/g, ' ')
19
+ .trim();
20
+ }
21
+
22
+ function shortenUrlText(input) {
23
+ return String(input || '').replace(/https?:\/\/[^\s)]+/gi, (match) => {
24
+ try {
25
+ const url = new URL(match);
26
+ const path = url.pathname && url.pathname !== '/' ? clampText(url.pathname, 40) : '';
27
+ return `${url.hostname}${path}`;
28
+ } catch {
29
+ return match;
30
+ }
31
+ });
32
+ }
33
+
34
+ function dedupeLines(input, maxLines = 30) {
35
+ const seen = new Set();
36
+ const result = [];
37
+ for (const rawLine of String(input || '').split('\n')) {
38
+ const normalized = rawLine.replace(/\s+/g, ' ').trim();
39
+ if (!normalized) continue;
40
+ if (seen.has(normalized)) continue;
41
+ seen.add(normalized);
42
+ result.push(rawLine.trim());
43
+ if (result.length >= maxLines) break;
44
+ }
45
+ return result.join('\n');
46
+ }
47
+
48
+ function compactTextPayload(input, options = {}) {
49
+ const original = String(input || '');
50
+ let normalized = original;
51
+ const strategies = [];
52
+
53
+ if (/<[a-z!/][^>]*>/i.test(normalized)) {
54
+ normalized = stripHtmlTags(normalized);
55
+ strategies.push('html_to_text');
56
+ }
57
+
58
+ const shortenedUrls = shortenUrlText(normalized);
59
+ if (shortenedUrls !== normalized) {
60
+ normalized = shortenedUrls;
61
+ strategies.push('url_shortening');
62
+ }
63
+
64
+ const deduped = dedupeLines(normalized, options.maxLines || 30);
65
+ if (deduped && deduped !== normalized) {
66
+ normalized = deduped;
67
+ strategies.push('line_deduplication');
68
+ }
69
+
70
+ normalized = normalized.replace(/\s+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
71
+ const finalText = clampText(normalized, options.maxChars || 1800);
72
+ if (finalText.length < normalized.length) {
73
+ strategies.push('length_clamp');
74
+ }
75
+
76
+ return {
77
+ text: finalText,
78
+ metrics: {
79
+ inputChars: original.length,
80
+ outputChars: finalText.length,
81
+ reducedChars: Math.max(0, original.length - finalText.length),
82
+ applied: finalText !== original,
83
+ strategies,
84
+ },
85
+ };
86
+ }
87
+
88
+ function compactHttpResult(result) {
89
+ const source = result && typeof result === 'object' ? result : {};
90
+ const compactedBody = compactTextPayload(source.body || '', {
91
+ maxChars: 2200,
92
+ maxLines: 40,
93
+ });
94
+ return {
95
+ result: {
96
+ ...source,
97
+ body: compactedBody.text,
98
+ },
99
+ metrics: compactedBody.metrics,
100
+ };
101
+ }
102
+
103
+ function compactExtractResult(result) {
104
+ const source = result && typeof result === 'object' ? result : {};
105
+ const content = source.result ?? source.content ?? '';
106
+ const compacted = compactTextPayload(content, {
107
+ maxChars: 1800,
108
+ maxLines: 35,
109
+ });
110
+ const next = { ...source };
111
+ if (Object.prototype.hasOwnProperty.call(next, 'result')) next.result = compacted.text;
112
+ if (Object.prototype.hasOwnProperty.call(next, 'content')) next.content = compacted.text;
113
+ if (!Object.prototype.hasOwnProperty.call(next, 'result') && !Object.prototype.hasOwnProperty.call(next, 'content')) {
114
+ next.result = compacted.text;
115
+ }
116
+ return {
117
+ result: next,
118
+ metrics: compacted.metrics,
119
+ };
120
+ }
121
+
122
+ function compactSearchResult(result) {
123
+ const source = result && typeof result === 'object' ? result : {};
124
+ const results = Array.isArray(source.results)
125
+ ? source.results.slice(0, 8).map((item) => ({
126
+ ...item,
127
+ description: clampText(shortenUrlText(item?.description || ''), 220),
128
+ }))
129
+ : [];
130
+ const rawLength = JSON.stringify(source).length;
131
+ const compactedLength = JSON.stringify({ ...source, results }).length;
132
+ return {
133
+ result: {
134
+ ...source,
135
+ results,
136
+ },
137
+ metrics: {
138
+ inputChars: rawLength,
139
+ outputChars: compactedLength,
140
+ reducedChars: Math.max(0, rawLength - compactedLength),
141
+ applied: compactedLength !== rawLength,
142
+ strategies: compactedLength !== rawLength ? ['result_truncation'] : [],
143
+ },
144
+ };
145
+ }
146
+
147
+ function compactReadFileResult(result) {
148
+ const source = result && typeof result === 'object' ? result : {};
149
+ const compacted = compactTextPayload(source.content || '', {
150
+ maxChars: 2200,
151
+ maxLines: 40,
152
+ });
153
+ return {
154
+ result: {
155
+ ...source,
156
+ content: compacted.text,
157
+ },
158
+ metrics: compacted.metrics,
159
+ };
160
+ }
161
+
162
+ function compactPayloadForModel(toolName, result) {
163
+ switch (String(toolName || '').trim()) {
164
+ case 'http_request':
165
+ return compactHttpResult(result);
166
+ case 'browser_extract':
167
+ case 'session_search':
168
+ return compactExtractResult(result);
169
+ case 'web_search':
170
+ return compactSearchResult(result);
171
+ case 'read_file':
172
+ return compactReadFileResult(result);
173
+ default:
174
+ return {
175
+ result,
176
+ metrics: {
177
+ inputChars: 0,
178
+ outputChars: 0,
179
+ reducedChars: 0,
180
+ applied: false,
181
+ strategies: [],
182
+ },
183
+ };
184
+ }
185
+ }
186
+
187
+ module.exports = {
188
+ compactPayloadForModel,
189
+ compactTextPayload,
190
+ stripHtmlTags,
191
+ };