@vinaes/succ 1.3.22 → 1.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/README.md +6 -2
- package/agents/succ-code-reviewer.md +1 -1
- package/agents/succ-diff-reviewer.md +1 -1
- package/agents/succ-general.md +1 -1
- package/dist/cli.js +13 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze-agents.d.ts.map +1 -1
- package/dist/commands/analyze-agents.js +10 -11
- package/dist/commands/analyze-agents.js.map +1 -1
- package/dist/commands/analyze-recursive.d.ts.map +1 -1
- package/dist/commands/analyze-recursive.js +2 -1
- package/dist/commands/analyze-recursive.js.map +1 -1
- package/dist/commands/analyze-utils.d.ts +1 -1
- package/dist/commands/analyze-utils.d.ts.map +1 -1
- package/dist/commands/analyze-utils.js +1 -2
- package/dist/commands/analyze-utils.js.map +1 -1
- package/dist/commands/init-templates.js +2 -2
- package/dist/commands/soul.d.ts.map +1 -1
- package/dist/commands/soul.js +4 -44
- package/dist/commands/soul.js.map +1 -1
- package/dist/daemon/analyzer.js +2 -2
- package/dist/daemon/analyzer.js.map +1 -1
- package/dist/daemon/service.d.ts.map +1 -1
- package/dist/daemon/service.js +37 -3
- package/dist/daemon/service.js.map +1 -1
- package/dist/daemon/session-processor.d.ts.map +1 -1
- package/dist/daemon/session-processor.js +10 -184
- package/dist/daemon/session-processor.js.map +1 -1
- package/dist/lib/compact-briefing.d.ts.map +1 -1
- package/dist/lib/compact-briefing.js +8 -4
- package/dist/lib/compact-briefing.js.map +1 -1
- package/dist/lib/config-types.d.ts +9 -0
- package/dist/lib/config-types.d.ts.map +1 -1
- package/dist/lib/config-validation.d.ts.map +1 -1
- package/dist/lib/config-validation.js +1 -0
- package/dist/lib/config-validation.js.map +1 -1
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +37 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/consolidate.d.ts.map +1 -1
- package/dist/lib/consolidate.js +7 -2
- package/dist/lib/consolidate.js.map +1 -1
- package/dist/lib/db/connection.d.ts.map +1 -1
- package/dist/lib/db/connection.js +8 -1
- package/dist/lib/db/connection.js.map +1 -1
- package/dist/lib/db/index.d.ts +1 -1
- package/dist/lib/db/index.d.ts.map +1 -1
- package/dist/lib/db/index.js +1 -1
- package/dist/lib/db/index.js.map +1 -1
- package/dist/lib/db/memories.d.ts +6 -0
- package/dist/lib/db/memories.d.ts.map +1 -1
- package/dist/lib/db/memories.js +34 -0
- package/dist/lib/db/memories.js.map +1 -1
- package/dist/lib/graph/llm-relations.d.ts.map +1 -1
- package/dist/lib/graph/llm-relations.js +7 -40
- package/dist/lib/graph/llm-relations.js.map +1 -1
- package/dist/lib/graph-export.js +1 -2
- package/dist/lib/graph-export.js.map +1 -1
- package/dist/lib/hook-rules.d.ts +31 -0
- package/dist/lib/hook-rules.d.ts.map +1 -0
- package/dist/lib/hook-rules.js +102 -0
- package/dist/lib/hook-rules.js.map +1 -0
- package/dist/lib/llm.d.ts +14 -0
- package/dist/lib/llm.d.ts.map +1 -1
- package/dist/lib/llm.js +30 -9
- package/dist/lib/llm.js.map +1 -1
- package/dist/lib/ort-session.d.ts.map +1 -1
- package/dist/lib/ort-session.js +0 -1
- package/dist/lib/ort-session.js.map +1 -1
- package/dist/lib/prd/generate.d.ts.map +1 -1
- package/dist/lib/prd/generate.js +2 -1
- package/dist/lib/prd/generate.js.map +1 -1
- package/dist/lib/prd/parse.d.ts.map +1 -1
- package/dist/lib/prd/parse.js +2 -1
- package/dist/lib/prd/parse.js.map +1 -1
- package/dist/lib/prd/prompt-builder.d.ts +9 -2
- package/dist/lib/prd/prompt-builder.d.ts.map +1 -1
- package/dist/lib/prd/prompt-builder.js +7 -8
- package/dist/lib/prd/prompt-builder.js.map +1 -1
- package/dist/lib/prd/runner.js +2 -1
- package/dist/lib/prd/runner.js.map +1 -1
- package/dist/lib/prd/team-runner.js +2 -1
- package/dist/lib/prd/team-runner.js.map +1 -1
- package/dist/lib/precompute-context.d.ts.map +1 -1
- package/dist/lib/precompute-context.js +7 -2
- package/dist/lib/precompute-context.js.map +1 -1
- package/dist/lib/public-api.d.ts +14 -4
- package/dist/lib/public-api.d.ts.map +1 -1
- package/dist/lib/public-api.js +14 -2
- package/dist/lib/public-api.js.map +1 -1
- package/dist/lib/query-expansion.d.ts.map +1 -1
- package/dist/lib/query-expansion.js +2 -12
- package/dist/lib/query-expansion.js.map +1 -1
- package/dist/lib/reflection-synthesizer.d.ts.map +1 -1
- package/dist/lib/reflection-synthesizer.js +2 -16
- package/dist/lib/reflection-synthesizer.js.map +1 -1
- package/dist/lib/session-summary.d.ts +1 -0
- package/dist/lib/session-summary.d.ts.map +1 -1
- package/dist/lib/session-summary.js +10 -2
- package/dist/lib/session-summary.js.map +1 -1
- package/dist/lib/skills.d.ts.map +1 -1
- package/dist/lib/skills.js +5 -5
- package/dist/lib/skills.js.map +1 -1
- package/dist/lib/storage/backends/postgresql.d.ts +1 -0
- package/dist/lib/storage/backends/postgresql.d.ts.map +1 -1
- package/dist/lib/storage/backends/postgresql.js +33 -0
- package/dist/lib/storage/backends/postgresql.js.map +1 -1
- package/dist/lib/storage/dispatcher.d.ts +1 -0
- package/dist/lib/storage/dispatcher.d.ts.map +1 -1
- package/dist/lib/storage/dispatcher.js +6 -0
- package/dist/lib/storage/dispatcher.js.map +1 -1
- package/dist/lib/storage/index.d.ts +1 -0
- package/dist/lib/storage/index.d.ts.map +1 -1
- package/dist/lib/storage/index.js +4 -0
- package/dist/lib/storage/index.js.map +1 -1
- package/dist/lib/supersession.d.ts.map +1 -1
- package/dist/lib/supersession.js +2 -15
- package/dist/lib/supersession.js.map +1 -1
- package/dist/mcp/helpers.d.ts +11 -0
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +35 -0
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/profile.d.ts +16 -0
- package/dist/mcp/profile.d.ts.map +1 -0
- package/dist/mcp/profile.js +64 -0
- package/dist/mcp/profile.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -15
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +119 -18
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/config.d.ts +1 -5
- package/dist/mcp/tools/config.d.ts.map +1 -1
- package/dist/mcp/tools/config.js +224 -180
- package/dist/mcp/tools/config.js.map +1 -1
- package/dist/mcp/tools/dead-end.d.ts.map +1 -1
- package/dist/mcp/tools/dead-end.js +20 -11
- package/dist/mcp/tools/dead-end.js.map +1 -1
- package/dist/mcp/tools/debug.d.ts.map +1 -1
- package/dist/mcp/tools/debug.js +76 -67
- package/dist/mcp/tools/debug.js.map +1 -1
- package/dist/mcp/tools/graph.d.ts +0 -1
- package/dist/mcp/tools/graph.d.ts.map +1 -1
- package/dist/mcp/tools/graph.js +91 -87
- package/dist/mcp/tools/graph.js.map +1 -1
- package/dist/mcp/tools/indexing.d.ts +1 -7
- package/dist/mcp/tools/indexing.d.ts.map +1 -1
- package/dist/mcp/tools/indexing.js +274 -261
- package/dist/mcp/tools/indexing.js.map +1 -1
- package/dist/mcp/tools/memory.d.ts.map +1 -1
- package/dist/mcp/tools/memory.js +124 -63
- package/dist/mcp/tools/memory.js.map +1 -1
- package/dist/mcp/tools/prd.d.ts +1 -7
- package/dist/mcp/tools/prd.d.ts.map +1 -1
- package/dist/mcp/tools/prd.js +240 -246
- package/dist/mcp/tools/prd.js.map +1 -1
- package/dist/mcp/tools/search.d.ts.map +1 -1
- package/dist/mcp/tools/search.js +91 -41
- package/dist/mcp/tools/search.js.map +1 -1
- package/dist/mcp/tools/status.d.ts +1 -5
- package/dist/mcp/tools/status.d.ts.map +1 -1
- package/dist/mcp/tools/status.js +310 -272
- package/dist/mcp/tools/status.js.map +1 -1
- package/dist/mcp/tools/web-fetch.d.ts +2 -1
- package/dist/mcp/tools/web-fetch.d.ts.map +1 -1
- package/dist/mcp/tools/web-fetch.js +68 -43
- package/dist/mcp/tools/web-fetch.js.map +1 -1
- package/dist/mcp/tools/web-search.d.ts +1 -6
- package/dist/mcp/tools/web-search.d.ts.map +1 -1
- package/dist/mcp/tools/web-search.js +266 -265
- package/dist/mcp/tools/web-search.js.map +1 -1
- package/dist/prompts/briefing.d.ts +10 -4
- package/dist/prompts/briefing.d.ts.map +1 -1
- package/dist/prompts/briefing.js +33 -35
- package/dist/prompts/briefing.js.map +1 -1
- package/dist/prompts/daemon.d.ts +5 -10
- package/dist/prompts/daemon.d.ts.map +1 -1
- package/dist/prompts/daemon.js +9 -18
- package/dist/prompts/daemon.js.map +1 -1
- package/dist/prompts/extraction.d.ts +19 -3
- package/dist/prompts/extraction.d.ts.map +1 -1
- package/dist/prompts/extraction.js +36 -45
- package/dist/prompts/extraction.js.map +1 -1
- package/dist/prompts/graph.d.ts +9 -0
- package/dist/prompts/graph.d.ts.map +1 -0
- package/dist/prompts/graph.js +27 -0
- package/dist/prompts/graph.js.map +1 -0
- package/dist/prompts/index.d.ts +16 -7
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +21 -7
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/memory.d.ts +6 -3
- package/dist/prompts/memory.d.ts.map +1 -1
- package/dist/prompts/memory.js +9 -8
- package/dist/prompts/memory.js.map +1 -1
- package/dist/prompts/onboarding.d.ts +1 -1
- package/dist/prompts/onboarding.d.ts.map +1 -1
- package/dist/prompts/onboarding.js +2 -3
- package/dist/prompts/onboarding.js.map +1 -1
- package/dist/prompts/prd.d.ts +15 -6
- package/dist/prompts/prd.d.ts.map +1 -1
- package/dist/prompts/prd.js +44 -38
- package/dist/prompts/prd.js.map +1 -1
- package/dist/prompts/query-expansion.d.ts +8 -0
- package/dist/prompts/query-expansion.d.ts.map +1 -0
- package/dist/prompts/query-expansion.js +17 -0
- package/dist/prompts/query-expansion.js.map +1 -0
- package/dist/prompts/skills.d.ts +5 -10
- package/dist/prompts/skills.d.ts.map +1 -1
- package/dist/prompts/skills.js +9 -17
- package/dist/prompts/skills.js.map +1 -1
- package/dist/prompts/soul.d.ts +9 -0
- package/dist/prompts/soul.d.ts.map +1 -0
- package/dist/prompts/soul.js +47 -0
- package/dist/prompts/soul.js.map +1 -0
- package/dist/prompts/supersession.d.ts +9 -0
- package/dist/prompts/supersession.d.ts.map +1 -0
- package/dist/prompts/supersession.js +21 -0
- package/dist/prompts/supersession.js.map +1 -0
- package/dist/prompts/synthesis.d.ts +9 -0
- package/dist/prompts/synthesis.d.ts.map +1 -0
- package/dist/prompts/synthesis.js +22 -0
- package/dist/prompts/synthesis.js.map +1 -0
- package/hooks/succ-pre-tool.cjs +304 -76
- package/hooks/succ-session-start.cjs +58 -27
- package/package.json +5 -4
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Web Search
|
|
3
|
-
*
|
|
4
|
-
* - succ_quick_search: Fast, cheap search (default: Perplexity Sonar)
|
|
5
|
-
* - succ_web_search: Quality search (default: Perplexity Sonar Pro)
|
|
6
|
-
* - succ_deep_research: Multi-step deep research (default: Perplexity Sonar Deep Research)
|
|
7
|
-
* - succ_web_search_history: Browse and filter past web search history
|
|
2
|
+
* MCP Web Search tool — succ_web with actions: quick, search, deep, history
|
|
8
3
|
*
|
|
9
4
|
* Models are configurable via web_search.* config keys. Any OpenRouter model
|
|
10
5
|
* with :online suffix supports web search (e.g., x-ai/grok-3:online).
|
|
@@ -13,6 +8,7 @@ import { z } from 'zod';
|
|
|
13
8
|
import { getWebSearchConfig } from '../../lib/config.js';
|
|
14
9
|
import { isApiConfigured, callOpenRouterSearch, } from '../../lib/llm.js';
|
|
15
10
|
import { projectPathParam, applyProjectPath } from '../helpers.js';
|
|
11
|
+
import { gateAction } from '../profile.js';
|
|
16
12
|
import { recordWebSearch, getTodayWebSearchSpend, getWebSearchHistory, getWebSearchSummary, } from '../../lib/storage/index.js';
|
|
17
13
|
import { logWarn } from '../../lib/fault-logger.js';
|
|
18
14
|
// Approximate pricing per 1M tokens (USD) — OpenRouter models with web search
|
|
@@ -77,7 +73,7 @@ async function checkBudget(dailyBudget) {
|
|
|
77
73
|
try {
|
|
78
74
|
const spent = await getTodayWebSearchSpend();
|
|
79
75
|
if (spent >= dailyBudget) {
|
|
80
|
-
return `Daily web search budget exceeded ($${spent.toFixed(4)} / $${dailyBudget}). Reset tomorrow or increase with:
|
|
76
|
+
return `Daily web search budget exceeded ($${spent.toFixed(4)} / $${dailyBudget}). Reset tomorrow or increase with: succ_config(action="set", key="web_search.daily_budget_usd", value="...")`;
|
|
81
77
|
}
|
|
82
78
|
}
|
|
83
79
|
catch (err) {
|
|
@@ -146,281 +142,286 @@ async function saveResultToMemory(query, content, citations, toolName, type) {
|
|
|
146
142
|
}
|
|
147
143
|
}
|
|
148
144
|
export function registerWebSearchTools(server) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.describe('
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (budgetError)
|
|
191
|
-
return { content: [{ type: 'text', text: budgetError }], isError: true };
|
|
192
|
-
try {
|
|
193
|
-
const messages = [];
|
|
194
|
-
if (system_prompt) {
|
|
195
|
-
messages.push({ role: 'system', content: system_prompt });
|
|
196
|
-
}
|
|
197
|
-
messages.push({ role: 'user', content: query });
|
|
198
|
-
const effectiveModel = wsConfig.quick_search_model;
|
|
199
|
-
const result = await callOpenRouterSearch(messages, effectiveModel, wsConfig.quick_search_timeout_ms, max_tokens || wsConfig.quick_search_max_tokens, wsConfig.temperature);
|
|
200
|
-
const cost = estimateCost(result.usage, effectiveModel);
|
|
201
|
-
await recordSearchToDb('succ_quick_search', effectiveModel, query, result.usage, cost, result.citations, false, result.content.length);
|
|
202
|
-
const todaySpent = wsConfig.daily_budget_usd > 0 ? await getTodayWebSearchSpend() : 0;
|
|
203
|
-
let text = result.content;
|
|
204
|
-
text += formatCitations(result.citations, result.search_results);
|
|
205
|
-
text += formatUsage(result.usage, cost, wsConfig.daily_budget_usd, todaySpent);
|
|
206
|
-
const shouldSave = save_to_memory ?? wsConfig.save_to_memory;
|
|
207
|
-
if (shouldSave) {
|
|
208
|
-
text += await saveResultToMemory(query, result.content, result.citations, 'succ_quick_search', 'observation');
|
|
209
|
-
}
|
|
210
|
-
return { content: [{ type: 'text', text }] };
|
|
211
|
-
}
|
|
212
|
-
catch (error) {
|
|
213
|
-
return {
|
|
214
|
-
content: [{ type: 'text', text: `Quick search failed: ${error.message}` }],
|
|
215
|
-
isError: true,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
// succ_web_search — fast real-time web search
|
|
220
|
-
server.tool('succ_web_search', 'Web search via OpenRouter (default: Perplexity Sonar Pro). Higher quality than succ_quick_search. Use for complex queries, documentation lookups, multi-faceted questions. Returns answers with citations. Alternatives: x-ai/grok-3:online, google/gemini-2.0-flash-001:online, or any model with :online suffix. Requires OPENROUTER_API_KEY.', {
|
|
221
|
-
query: z
|
|
222
|
-
.string()
|
|
223
|
-
.describe('The search query (e.g., "latest React 19 features", "how to configure nginx reverse proxy")'),
|
|
224
|
-
model: z
|
|
225
|
-
.string()
|
|
226
|
-
.optional()
|
|
227
|
-
.describe('Override search model. Default from config (perplexity/sonar-pro). Perplexity: sonar, sonar-pro, sonar-reasoning-pro. Grok: x-ai/grok-3:online, x-ai/grok-3-mini:online. Any OpenRouter model with :online suffix supports web search.'),
|
|
228
|
-
system_prompt: z
|
|
229
|
-
.string()
|
|
230
|
-
.optional()
|
|
231
|
-
.describe('Optional system prompt to guide the response format or focus'),
|
|
232
|
-
max_tokens: z.number().optional().describe('Max response tokens (default: 4000)'),
|
|
233
|
-
save_to_memory: z
|
|
234
|
-
.boolean()
|
|
235
|
-
.optional()
|
|
236
|
-
.describe('Save result to succ memory (default: from config)'),
|
|
237
|
-
project_path: projectPathParam,
|
|
238
|
-
}, async ({ query, model, system_prompt, max_tokens, save_to_memory, project_path }) => {
|
|
239
|
-
await applyProjectPath(project_path);
|
|
240
|
-
if (!isApiConfigured()) {
|
|
241
|
-
return {
|
|
242
|
-
content: [
|
|
243
|
-
{
|
|
244
|
-
type: 'text',
|
|
245
|
-
text: 'API key not configured. Set OPENROUTER_API_KEY environment variable or run:\nsucc_config_set key="llm.api_key" value="sk-or-..."',
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
isError: true,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
const wsConfig = getWebSearchConfig();
|
|
252
|
-
if (!wsConfig.enabled) {
|
|
253
|
-
return {
|
|
254
|
-
content: [
|
|
255
|
-
{
|
|
256
|
-
type: 'text',
|
|
257
|
-
text: 'Web search is disabled. Enable with: succ_config_set key="web_search.enabled" value="true"',
|
|
258
|
-
},
|
|
259
|
-
],
|
|
260
|
-
isError: true,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
const budgetError = await checkBudget(wsConfig.daily_budget_usd);
|
|
264
|
-
if (budgetError)
|
|
265
|
-
return { content: [{ type: 'text', text: budgetError }], isError: true };
|
|
266
|
-
const effectiveModel = model || wsConfig.model;
|
|
267
|
-
try {
|
|
268
|
-
const messages = [];
|
|
269
|
-
if (system_prompt) {
|
|
270
|
-
messages.push({ role: 'system', content: system_prompt });
|
|
271
|
-
}
|
|
272
|
-
messages.push({ role: 'user', content: query });
|
|
273
|
-
const result = await callOpenRouterSearch(messages, effectiveModel, wsConfig.timeout_ms, max_tokens || wsConfig.max_tokens, wsConfig.temperature);
|
|
274
|
-
const cost = estimateCost(result.usage, effectiveModel);
|
|
275
|
-
await recordSearchToDb('succ_web_search', effectiveModel, query, result.usage, cost, result.citations, false, result.content.length);
|
|
276
|
-
const todaySpent = wsConfig.daily_budget_usd > 0 ? await getTodayWebSearchSpend() : 0;
|
|
277
|
-
let text = result.content;
|
|
278
|
-
text += formatCitations(result.citations, result.search_results);
|
|
279
|
-
text += formatUsage(result.usage, cost, wsConfig.daily_budget_usd, todaySpent);
|
|
280
|
-
const shouldSave = save_to_memory ?? wsConfig.save_to_memory;
|
|
281
|
-
if (shouldSave) {
|
|
282
|
-
text += await saveResultToMemory(query, result.content, result.citations, 'succ_web_search', 'observation');
|
|
283
|
-
}
|
|
284
|
-
return { content: [{ type: 'text', text }] };
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
return {
|
|
288
|
-
content: [{ type: 'text', text: `Web search failed: ${error.message}` }],
|
|
289
|
-
isError: true,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
// succ_deep_research — expensive multi-step research
|
|
294
|
-
server.tool('succ_deep_research', 'Deep multi-step web research via OpenRouter (default: Perplexity Sonar Deep Research). Autonomously searches, reads, and synthesizes multiple sources. WARNING: Significantly more expensive and slower than succ_web_search (30-120s, runs 30+ searches). Configure model with web_search.deep_research_model. Requires OPENROUTER_API_KEY.', {
|
|
295
|
-
query: z
|
|
296
|
-
.string()
|
|
297
|
-
.describe('The research question (e.g., "Compare React Server Components vs Astro Islands for e-commerce")'),
|
|
298
|
-
system_prompt: z
|
|
299
|
-
.string()
|
|
300
|
-
.optional()
|
|
301
|
-
.describe('Optional system prompt to guide research focus or output format'),
|
|
302
|
-
max_tokens: z.number().optional().describe('Max response tokens (default: 8000)'),
|
|
303
|
-
include_reasoning: z
|
|
304
|
-
.boolean()
|
|
305
|
-
.optional()
|
|
306
|
-
.describe("Include the model's internal reasoning process (default: false)"),
|
|
307
|
-
save_to_memory: z
|
|
308
|
-
.boolean()
|
|
309
|
-
.optional()
|
|
310
|
-
.describe('Save result to succ memory (default: from config)'),
|
|
311
|
-
project_path: projectPathParam,
|
|
312
|
-
}, async ({ query, system_prompt, max_tokens, include_reasoning, save_to_memory, project_path, }) => {
|
|
145
|
+
server.registerTool('succ_web', {
|
|
146
|
+
description: 'Web search via OpenRouter (Perplexity, Grok, Gemini). Supports quick lookups, quality search, deep multi-step research, and search history.\n\nExamples:\n- Quick: succ_web(action="quick", query="Node.js 22 LTS release date")\n- Search: succ_web(query="React 19 best practices")\n- Deep: succ_web(action="deep", query="Compare React vs Astro for e-commerce")\n- History: succ_web(action="history", limit=5)',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
action: z
|
|
149
|
+
.enum(['quick', 'search', 'deep', 'history'])
|
|
150
|
+
.optional()
|
|
151
|
+
.default('search')
|
|
152
|
+
.describe('quick = fast factual lookup (~$1/MTok), search = quality search (Sonar Pro), deep = multi-step research (30-120s, expensive), history = past searches'),
|
|
153
|
+
query: z.string().optional().describe('Search query (required for quick, search, deep)'),
|
|
154
|
+
system_prompt: z.string().optional().describe('Optional system prompt to guide response'),
|
|
155
|
+
max_tokens: z.number().optional().describe('Max response tokens'),
|
|
156
|
+
save_to_memory: z
|
|
157
|
+
.boolean()
|
|
158
|
+
.optional()
|
|
159
|
+
.describe('Save result to succ memory (default: from config)'),
|
|
160
|
+
model: z
|
|
161
|
+
.string()
|
|
162
|
+
.optional()
|
|
163
|
+
.describe('Override search model (for search) or filter by model (for history)'),
|
|
164
|
+
include_reasoning: z
|
|
165
|
+
.boolean()
|
|
166
|
+
.optional()
|
|
167
|
+
.describe("Include model's reasoning process (for deep)"),
|
|
168
|
+
// history filters
|
|
169
|
+
tool_name: z
|
|
170
|
+
.enum(['succ_quick_search', 'succ_web_search', 'succ_deep_research'])
|
|
171
|
+
.optional()
|
|
172
|
+
.describe('Filter by original tool name (for history, uses DB column values)'),
|
|
173
|
+
query_text: z.string().optional().describe('Filter by query substring (for history)'),
|
|
174
|
+
date_from: z.string().optional().describe('Start date ISO (for history)'),
|
|
175
|
+
date_to: z.string().optional().describe('End date ISO (for history)'),
|
|
176
|
+
limit: z.number().optional().describe('Max records (for history, default: 20)'),
|
|
177
|
+
project_path: projectPathParam,
|
|
178
|
+
},
|
|
179
|
+
annotations: {
|
|
180
|
+
readOnlyHint: false,
|
|
181
|
+
destructiveHint: false,
|
|
182
|
+
idempotentHint: false,
|
|
183
|
+
openWorldHint: true,
|
|
184
|
+
},
|
|
185
|
+
}, async ({ action = 'search', query, system_prompt, max_tokens, save_to_memory, model, include_reasoning, tool_name, query_text, date_from, date_to, limit, project_path, }) => {
|
|
313
186
|
await applyProjectPath(project_path);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
text: 'API key not configured. Set OPENROUTER_API_KEY environment variable or run:\nsucc_config_set key="llm.api_key" value="sk-or-..."',
|
|
320
|
-
},
|
|
321
|
-
],
|
|
322
|
-
isError: true,
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const wsConfig = getWebSearchConfig();
|
|
326
|
-
if (!wsConfig.enabled) {
|
|
187
|
+
const gated = gateAction('succ_web', action);
|
|
188
|
+
if (gated)
|
|
189
|
+
return gated;
|
|
190
|
+
// Shared validation for search actions
|
|
191
|
+
if (['quick', 'search', 'deep'].includes(action) && !query) {
|
|
327
192
|
return {
|
|
328
193
|
content: [
|
|
329
194
|
{
|
|
330
195
|
type: 'text',
|
|
331
|
-
text:
|
|
196
|
+
text: `"query" is required for action="${action}"`,
|
|
332
197
|
},
|
|
333
198
|
],
|
|
334
199
|
isError: true,
|
|
335
200
|
};
|
|
336
201
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
202
|
+
switch (action) {
|
|
203
|
+
case 'quick': {
|
|
204
|
+
if (!isApiConfigured()) {
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'text',
|
|
209
|
+
text: 'API key not configured. Set OPENROUTER_API_KEY environment variable or run:\nsucc_config(action="set", key="llm.api_key", value="sk-or-...")',
|
|
210
|
+
},
|
|
211
|
+
],
|
|
212
|
+
isError: true,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const wsConfig = getWebSearchConfig();
|
|
216
|
+
if (!wsConfig.enabled) {
|
|
217
|
+
return {
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: 'text',
|
|
221
|
+
text: 'Web search is disabled. Enable with: succ_config(action="set", key="web_search.enabled", value="true")',
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
isError: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const budgetError = await checkBudget(wsConfig.daily_budget_usd);
|
|
228
|
+
if (budgetError)
|
|
229
|
+
return { content: [{ type: 'text', text: budgetError }], isError: true };
|
|
230
|
+
try {
|
|
231
|
+
const messages = [];
|
|
232
|
+
if (system_prompt) {
|
|
233
|
+
messages.push({ role: 'system', content: system_prompt });
|
|
234
|
+
}
|
|
235
|
+
messages.push({ role: 'user', content: query });
|
|
236
|
+
const effectiveModel = wsConfig.quick_search_model;
|
|
237
|
+
const result = await callOpenRouterSearch(messages, effectiveModel, wsConfig.quick_search_timeout_ms, max_tokens || wsConfig.quick_search_max_tokens, wsConfig.temperature);
|
|
238
|
+
const cost = estimateCost(result.usage, effectiveModel);
|
|
239
|
+
await recordSearchToDb('succ_quick_search', effectiveModel, query, result.usage, cost, result.citations, false, result.content.length);
|
|
240
|
+
const todaySpent = wsConfig.daily_budget_usd > 0 ? await getTodayWebSearchSpend() : 0;
|
|
241
|
+
let text = result.content;
|
|
242
|
+
text += formatCitations(result.citations, result.search_results);
|
|
243
|
+
text += formatUsage(result.usage, cost, wsConfig.daily_budget_usd, todaySpent);
|
|
244
|
+
const shouldSave = save_to_memory ?? wsConfig.save_to_memory;
|
|
245
|
+
if (shouldSave) {
|
|
246
|
+
text += await saveResultToMemory(query, result.content, result.citations, 'succ_quick_search', 'observation');
|
|
247
|
+
}
|
|
248
|
+
return { content: [{ type: 'text', text }] };
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
return {
|
|
252
|
+
content: [{ type: 'text', text: `Quick search failed: ${error.message}` }],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
353
256
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
257
|
+
case 'search': {
|
|
258
|
+
if (!isApiConfigured()) {
|
|
259
|
+
return {
|
|
260
|
+
content: [
|
|
261
|
+
{
|
|
262
|
+
type: 'text',
|
|
263
|
+
text: 'API key not configured. Set OPENROUTER_API_KEY environment variable or run:\nsucc_config(action="set", key="llm.api_key", value="sk-or-...")',
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
isError: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
const wsConfig = getWebSearchConfig();
|
|
270
|
+
if (!wsConfig.enabled) {
|
|
271
|
+
return {
|
|
272
|
+
content: [
|
|
273
|
+
{
|
|
274
|
+
type: 'text',
|
|
275
|
+
text: 'Web search is disabled. Enable with: succ_config(action="set", key="web_search.enabled", value="true")',
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
isError: true,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
const budgetError = await checkBudget(wsConfig.daily_budget_usd);
|
|
282
|
+
if (budgetError)
|
|
283
|
+
return { content: [{ type: 'text', text: budgetError }], isError: true };
|
|
284
|
+
const effectiveModel = model || wsConfig.model;
|
|
285
|
+
try {
|
|
286
|
+
const messages = [];
|
|
287
|
+
if (system_prompt) {
|
|
288
|
+
messages.push({ role: 'system', content: system_prompt });
|
|
289
|
+
}
|
|
290
|
+
messages.push({ role: 'user', content: query });
|
|
291
|
+
const result = await callOpenRouterSearch(messages, effectiveModel, wsConfig.timeout_ms, max_tokens || wsConfig.max_tokens, wsConfig.temperature);
|
|
292
|
+
const cost = estimateCost(result.usage, effectiveModel);
|
|
293
|
+
await recordSearchToDb('succ_web_search', effectiveModel, query, result.usage, cost, result.citations, false, result.content.length);
|
|
294
|
+
const todaySpent = wsConfig.daily_budget_usd > 0 ? await getTodayWebSearchSpend() : 0;
|
|
295
|
+
let text = result.content;
|
|
296
|
+
text += formatCitations(result.citations, result.search_results);
|
|
297
|
+
text += formatUsage(result.usage, cost, wsConfig.daily_budget_usd, todaySpent);
|
|
298
|
+
const shouldSave = save_to_memory ?? wsConfig.save_to_memory;
|
|
299
|
+
if (shouldSave) {
|
|
300
|
+
text += await saveResultToMemory(query, result.content, result.citations, 'succ_web_search', 'observation');
|
|
301
|
+
}
|
|
302
|
+
return { content: [{ type: 'text', text }] };
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
content: [{ type: 'text', text: `Web search failed: ${error.message}` }],
|
|
307
|
+
isError: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
360
310
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
311
|
+
case 'deep': {
|
|
312
|
+
if (!isApiConfigured()) {
|
|
313
|
+
return {
|
|
314
|
+
content: [
|
|
315
|
+
{
|
|
316
|
+
type: 'text',
|
|
317
|
+
text: 'API key not configured. Set OPENROUTER_API_KEY environment variable or run:\nsucc_config(action="set", key="llm.api_key", value="sk-or-...")',
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
isError: true,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const wsConfig = getWebSearchConfig();
|
|
324
|
+
if (!wsConfig.enabled) {
|
|
325
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: 'text',
|
|
329
|
+
text: 'Web search is disabled. Enable with: succ_config(action="set", key="web_search.enabled", value="true")',
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const budgetError = await checkBudget(wsConfig.daily_budget_usd);
|
|
336
|
+
if (budgetError)
|
|
337
|
+
return { content: [{ type: 'text', text: budgetError }], isError: true };
|
|
338
|
+
try {
|
|
339
|
+
const messages = [];
|
|
340
|
+
if (system_prompt) {
|
|
341
|
+
messages.push({ role: 'system', content: system_prompt });
|
|
342
|
+
}
|
|
343
|
+
messages.push({ role: 'user', content: query });
|
|
344
|
+
const result = await callOpenRouterSearch(messages, wsConfig.deep_research_model, wsConfig.deep_research_timeout_ms, max_tokens || wsConfig.deep_research_max_tokens, wsConfig.temperature);
|
|
345
|
+
const cost = estimateCost(result.usage, wsConfig.deep_research_model);
|
|
346
|
+
await recordSearchToDb('succ_deep_research', wsConfig.deep_research_model, query, result.usage, cost, result.citations, !!result.reasoning, result.content.length);
|
|
347
|
+
const todaySpent = wsConfig.daily_budget_usd > 0 ? await getTodayWebSearchSpend() : 0;
|
|
348
|
+
let text = '';
|
|
349
|
+
if (include_reasoning && result.reasoning) {
|
|
350
|
+
text += `**Reasoning Process:**\n${result.reasoning}\n\n---\n\n`;
|
|
351
|
+
}
|
|
352
|
+
text += result.content;
|
|
353
|
+
text += formatCitations(result.citations, result.search_results);
|
|
354
|
+
text += formatUsage(result.usage, cost, wsConfig.daily_budget_usd, todaySpent);
|
|
355
|
+
const shouldSave = save_to_memory ?? wsConfig.save_to_memory;
|
|
356
|
+
if (shouldSave) {
|
|
357
|
+
text += await saveResultToMemory(query, result.content, result.citations, 'succ_deep_research', 'learning');
|
|
358
|
+
}
|
|
359
|
+
return { content: [{ type: 'text', text }] };
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
return {
|
|
363
|
+
content: [{ type: 'text', text: `Deep research failed: ${error.message}` }],
|
|
364
|
+
isError: true,
|
|
365
|
+
};
|
|
399
366
|
}
|
|
400
367
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
lines.push(
|
|
368
|
+
case 'history': {
|
|
369
|
+
try {
|
|
370
|
+
const [records, summary] = await Promise.all([
|
|
371
|
+
getWebSearchHistory({ tool_name, model, query_text, date_from, date_to, limit }),
|
|
372
|
+
getWebSearchSummary(),
|
|
373
|
+
]);
|
|
374
|
+
const lines = [];
|
|
375
|
+
// Summary section
|
|
376
|
+
lines.push('## Web Search Summary');
|
|
377
|
+
lines.push(`Total: ${summary.total_searches} searches, $${summary.total_cost_usd.toFixed(4)}`);
|
|
378
|
+
lines.push(`Today: ${summary.today_searches} searches, $${summary.today_cost_usd.toFixed(4)}`);
|
|
379
|
+
if (Object.keys(summary.by_tool).length > 0) {
|
|
380
|
+
lines.push('');
|
|
381
|
+
lines.push('**By tool:**');
|
|
382
|
+
for (const [tool, stats] of Object.entries(summary.by_tool)) {
|
|
383
|
+
lines.push(` ${tool}: ${stats.count} queries, $${stats.cost.toFixed(4)}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Records section
|
|
387
|
+
if (records.length > 0) {
|
|
388
|
+
lines.push('');
|
|
389
|
+
lines.push(`## Recent Searches (${records.length})`);
|
|
390
|
+
for (const r of records) {
|
|
391
|
+
const date = r.created_at.slice(0, 16).replace('T', ' ');
|
|
392
|
+
const tokens = r.prompt_tokens + r.completion_tokens;
|
|
393
|
+
lines.push(`- **[${date}]** \`${r.tool_name}\` — "${r.query.slice(0, 80)}${r.query.length > 80 ? '...' : ''}"`);
|
|
394
|
+
lines.push(` Model: ${r.model} | Tokens: ${tokens.toLocaleString()} | Cost: $${r.estimated_cost_usd.toFixed(4)}${r.citations_count > 0 ? ` | Citations: ${r.citations_count}` : ''}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
lines.push('\n_No search records found matching filters._');
|
|
399
|
+
}
|
|
400
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
return {
|
|
404
|
+
content: [
|
|
405
|
+
{
|
|
406
|
+
type: 'text',
|
|
407
|
+
text: `Failed to retrieve search history: ${error.message}`,
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
isError: true,
|
|
411
|
+
};
|
|
410
412
|
}
|
|
411
413
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
+
default: {
|
|
415
|
+
return {
|
|
416
|
+
content: [
|
|
417
|
+
{
|
|
418
|
+
type: 'text',
|
|
419
|
+
text: `Unknown action "${action}". Valid actions: quick, search, deep, history`,
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
isError: true,
|
|
423
|
+
};
|
|
414
424
|
}
|
|
415
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
416
|
-
}
|
|
417
|
-
catch (error) {
|
|
418
|
-
return {
|
|
419
|
-
content: [
|
|
420
|
-
{ type: 'text', text: `Failed to retrieve search history: ${error.message}` },
|
|
421
|
-
],
|
|
422
|
-
isError: true,
|
|
423
|
-
};
|
|
424
425
|
}
|
|
425
426
|
});
|
|
426
427
|
}
|