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.
- package/.env.example +6 -3
- package/flutter_app/lib/main.dart +1 -0
- package/flutter_app/lib/main_integrations.dart +21 -2
- package/flutter_app/lib/main_models.dart +60 -0
- package/flutter_app/lib/main_theme.dart +31 -2
- package/flutter_app/macos/Runner/AppDelegate.swift +11 -1
- package/flutter_app/macos/Runner/DebugProfile.entitlements +4 -0
- package/flutter_app/macos/Runner/Release.entitlements +4 -0
- package/flutter_app/pubspec.lock +5 -5
- package/lib/manager.js +164 -2
- package/package.json +1 -1
- package/server/db/database.js +85 -0
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/NOTICES +971 -1066
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/assets/shaders/ink_sparkle.frag +1 -1
- package/server/public/assets/shaders/stretch_effect.frag +1 -1
- package/server/public/canvaskit/canvaskit.js +2 -2
- package/server/public/canvaskit/canvaskit.js.symbols +11796 -11733
- package/server/public/canvaskit/canvaskit.wasm +0 -0
- package/server/public/canvaskit/chromium/canvaskit.js +2 -2
- package/server/public/canvaskit/chromium/canvaskit.js.symbols +10706 -10643
- package/server/public/canvaskit/chromium/canvaskit.wasm +0 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.js +171 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.js.symbols +9134 -0
- package/server/public/canvaskit/experimental_webparagraph/canvaskit.wasm +0 -0
- package/server/public/canvaskit/skwasm.js +14 -14
- package/server/public/canvaskit/skwasm.js.symbols +12787 -12676
- package/server/public/canvaskit/skwasm.wasm +0 -0
- package/server/public/canvaskit/skwasm_heavy.js +14 -14
- package/server/public/canvaskit/skwasm_heavy.js.symbols +14400 -14286
- package/server/public/canvaskit/skwasm_heavy.wasm +0 -0
- package/server/public/canvaskit/wimp.js +94 -95
- package/server/public/canvaskit/wimp.js.symbols +11325 -11177
- package/server/public/canvaskit/wimp.wasm +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +83866 -82074
- package/server/routes/integrations.js +2 -2
- package/server/routes/memory.js +73 -0
- package/server/services/ai/engine.js +65 -26
- package/server/services/ai/models.js +21 -0
- package/server/services/ai/preModelCompaction.js +191 -0
- package/server/services/ai/providers/claudeCode.js +273 -0
- package/server/services/ai/providers/openaiCodex.js +226 -41
- package/server/services/ai/settings.js +11 -1
- package/server/services/integrations/google/provider.js +78 -0
- package/server/services/integrations/manager.js +29 -13
- package/server/services/manager.js +25 -0
- package/server/services/memory/ingestion.js +486 -0
- package/server/services/memory/manager.js +422 -0
- package/server/services/memory/openhuman_uplift.test.js +98 -0
- 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.
|
|
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.
|
|
119
|
+
window.close();
|
|
120
120
|
}
|
|
121
121
|
</script>
|
|
122
122
|
<p>Authentication successful. You can close this window.</p>
|
package/server/routes/memory.js
CHANGED
|
@@ -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 (
|
|
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,
|
|
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(/ /gi, ' ')
|
|
15
|
+
.replace(/&/gi, '&')
|
|
16
|
+
.replace(/</gi, '<')
|
|
17
|
+
.replace(/>/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
|
+
};
|