reasonix 0.30.2 → 0.30.4
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 +56 -3
- package/README.zh-CN.md +11 -3
- package/dashboard/dist/app.js +1240 -787
- package/dashboard/dist/app.js.map +1 -1
- package/dist/cli/index.js +1450 -1307
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +130 -7
- package/dist/index.js.map +1 -1
- package/package.json +5 -7
package/dist/index.d.ts
CHANGED
|
@@ -590,10 +590,17 @@ declare class SessionStats {
|
|
|
590
590
|
private _carryoverCost;
|
|
591
591
|
/** Turn count from prior runs of a resumed session. */
|
|
592
592
|
private _carryoverTurns;
|
|
593
|
+
private _carryoverCacheHit;
|
|
594
|
+
private _carryoverCacheMiss;
|
|
595
|
+
/** Last turn's promptTokens before exit — surfaced via summary() until the next live turn lands. */
|
|
596
|
+
private _carryoverLastPromptTokens;
|
|
593
597
|
/** Seed totals from a resumed session's persisted meta — only call once at construction. */
|
|
594
598
|
seedCarryover(opts: {
|
|
595
599
|
totalCostUsd?: number;
|
|
596
600
|
turnCount?: number;
|
|
601
|
+
cacheHitTokens?: number;
|
|
602
|
+
cacheMissTokens?: number;
|
|
603
|
+
lastPromptTokens?: number;
|
|
597
604
|
}): void;
|
|
598
605
|
record(turn: number, model: string, usage: Usage): TurnStats;
|
|
599
606
|
get totalCost(): number;
|
|
@@ -1308,9 +1315,15 @@ interface WebFetchOptions {
|
|
|
1308
1315
|
interface WebSearchOptions {
|
|
1309
1316
|
topK?: number;
|
|
1310
1317
|
signal?: AbortSignal;
|
|
1318
|
+
/** Backend engine: "mojeek" (scrapes Mojeek HTML) or "searxng" (self-hosted SearXNG JSON API). */
|
|
1319
|
+
engine?: "mojeek" | "searxng";
|
|
1320
|
+
/** Base URL for SearXNG. Default http://localhost:8080. */
|
|
1321
|
+
endpoint?: string;
|
|
1311
1322
|
}
|
|
1312
1323
|
/** Distinguishes "truly 0 results" from "layout changed / blocked" so callers can tell. */
|
|
1313
1324
|
declare function webSearch(query: string, opts?: WebSearchOptions): Promise<SearchResult[]>;
|
|
1325
|
+
/** Parse SearXNG HTML search results using node-html-parser. */
|
|
1326
|
+
declare function parseSearxngHtmlResults(html: string): SearchResult[];
|
|
1314
1327
|
/** Title-anchor + snippet-paragraph passes paired positionally — robust to attribute reorder. */
|
|
1315
1328
|
declare function parseMojeekResults(html: string): SearchResult[];
|
|
1316
1329
|
declare function webFetch(url: string, opts?: WebFetchOptions): Promise<PageContent>;
|
|
@@ -1320,6 +1333,10 @@ interface WebToolsOptions {
|
|
|
1320
1333
|
defaultTopK?: number;
|
|
1321
1334
|
/** Byte cap for `web_fetch` extracted text. */
|
|
1322
1335
|
maxFetchChars?: number;
|
|
1336
|
+
/** Backend engine: "mojeek" (default, scrapes Mojeek) or "searxng" (self-hosted SearXNG). */
|
|
1337
|
+
webSearchEngine?: "mojeek" | "searxng";
|
|
1338
|
+
/** Base URL for SearXNG (default http://localhost:8080). */
|
|
1339
|
+
webSearchEndpoint?: string;
|
|
1323
1340
|
}
|
|
1324
1341
|
declare function registerWebTools(registry: ToolRegistry, opts?: WebToolsOptions): ToolRegistry;
|
|
1325
1342
|
declare function formatSearchResults(query: string, results: SearchResult[]): string;
|
|
@@ -1343,6 +1360,11 @@ interface SessionMeta {
|
|
|
1343
1360
|
workspace?: string;
|
|
1344
1361
|
/** Wallet currency at last save — used to format `totalCostUsd` in the picker without re-fetching balance. */
|
|
1345
1362
|
balanceCurrency?: string;
|
|
1363
|
+
/** Cumulative cache hit / miss tokens across the session — survives resume so /status cache% isn't 0 on a fresh boot. */
|
|
1364
|
+
cacheHitTokens?: number;
|
|
1365
|
+
cacheMissTokens?: number;
|
|
1366
|
+
/** Last turn's promptTokens — lets /status render the context bar before the next turn fires. */
|
|
1367
|
+
lastPromptTokens?: number;
|
|
1346
1368
|
}
|
|
1347
1369
|
declare function sessionsDir(): string;
|
|
1348
1370
|
declare function sessionPath(name: string): string;
|
|
@@ -2013,6 +2035,10 @@ interface ReasonixConfig {
|
|
|
2013
2035
|
session?: string | null;
|
|
2014
2036
|
setupCompleted?: boolean;
|
|
2015
2037
|
search?: boolean;
|
|
2038
|
+
/** Web search engine backend: "mojeek" (default, scrapes Mojeek) or "searxng" (self-hosted SearXNG). */
|
|
2039
|
+
webSearchEngine?: "mojeek" | "searxng";
|
|
2040
|
+
/** Base URL for SearXNG instance (default http://localhost:8080). */
|
|
2041
|
+
webSearchEndpoint?: string;
|
|
2016
2042
|
projects?: {
|
|
2017
2043
|
[absoluteRootDir: string]: {
|
|
2018
2044
|
shellAllowed?: string[];
|
|
@@ -2169,4 +2195,4 @@ declare function aggregateUsage(records: UsageRecord[], opts?: AggregateOptions)
|
|
|
2169
2195
|
/** File-size helper for the stats header — "1.2 MB" etc. Returns "" if missing. */
|
|
2170
2196
|
declare function formatLogSize(path?: string): string;
|
|
2171
2197
|
|
|
2172
|
-
export { AT_MENTION_PATTERN, AT_PICKER_PREFIX, type AggregateOptions, AppendOnlyLog, type AppendUsageInput, type ApplyResult, type ApplyStatus, type AtMentionExpansion, type AtMentionOptions, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, type ChoiceOption, ChoiceRequestedError, type ChoiceToolOptions, type CodeSystemPromptOptions, DEFAULT_AT_MENTION_MAX_BYTES, DEFAULT_MAX_RESULT_CHARS, DEFAULT_MAX_RESULT_TOKENS, DEFAULT_PICKER_IGNORE_DIRS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FileWithStats, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetLatestVersionOptions, type GetPromptResult, HOOK_EVENTS, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME, type HarvestOptions, type HookConfig, type HookEvent, type HookOutcome, type HookPayload, type HookReport, type HookScope, type HookSettings, type HookSpawnInput, type HookSpawnResult, type HookSpawner, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, LATEST_CACHE_TTL_MS, LATEST_FETCH_TIMEOUT_MS, type ListFilesOptions, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoadHookSettingsOptions, type LoopEvent, MCP_PROTOCOL_VERSION, MEMORY_INDEX_FILE, MEMORY_INDEX_MAX_CHARS, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type MemoryEntry, type MemoryScope, MemoryStore, type MemoryStoreOptions, type MemoryToolsOptions, type MemoryType, type WriteInput as MemoryWriteInput, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, type PickerCandidate, PlanProposedError, PlanRevisionProposedError, type PlanStep, type PlanStepRisk, type PlanToolOptions, type ProgressNotificationParams, type ProjectMemory, type RankPickerOptions, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type ResolvedHook, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type RunHooksOptions, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, type StepCompletion, StormBreaker, type StreamChunk, type StreamableHttpMcpSpec, StreamableHttpTransport, type StreamableHttpTransportOptions, type SubagentEvent, type SubagentSink, type SubagentToolOptions, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, USER_MEMORY_DIR, Usage, type UsageAggregate, type UsageBucket, type UsageRecord, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, aggregateUsage, analyzeSchema, appendSessionMessage, appendUsage, applyEditBlock, applyEditBlocks, applyMemoryStack, applyProjectMemory, applyUserMemory, bridgeMcpTools, bucketCacheHitRatio, bucketSavingsFraction, claudeEquivalentCost, codeSystemPrompt, compareVersions, computeReplayStats, costUsd, decideOutcome, defaultConfigPath, defaultSelector, defaultUsageLogPath, deleteSession, detectAtPicker, detectShellOperator, diffTranscripts, emptyPlanState, expandAtMentions, fetchWithRetry, fixToolCallPairing, flattenMcpResult, flattenSchema, forkRegistryExcluding, formatCommandResult, formatHookOutcomeMessage, formatLogSize, formatLoopError, formatSearchResults, getLatestVersion, globalSettingsPath, harvest, healLoadedMessages, healLoadedMessagesByTokens, htmlToText, injectPowerShellUtf8, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isNpxInstall, isPlanStateEmpty, isPlausibleKey, listFilesSync, listFilesWithStatsAsync, listFilesWithStatsSync, listSessions, loadApiKey, loadDotenv, loadHooks, loadSessionMessages, matchesTool, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseTranscript, prepareSpawn, projectHash, projectSettingsPath, quoteForCmdExe, rankPickerCandidates, readConfig, readProjectMemory, readTranscript, readUsageLog, recordFromLoopEvent, redactKey, registerChoiceTool, registerFilesystemTools, registerMemoryTools, registerPlanTool, registerShellTools, registerSubagentTool, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, resolveExecutable, restoreSnapshots, runBranches, runCommand, runHooks, sanitizeMemoryName, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, truncateForModelByTokens, webFetch, webSearch, withUtf8Codepage, writeConfig, writeMeta, writeRecord };
|
|
2198
|
+
export { AT_MENTION_PATTERN, AT_PICKER_PREFIX, type AggregateOptions, AppendOnlyLog, type AppendUsageInput, type ApplyResult, type ApplyStatus, type AtMentionExpansion, type AtMentionOptions, type BranchOptions, type BranchProgress, type BranchResult, type BranchSample, type BranchSelector, type BranchSummary, type BridgeOptions, type BridgeResult, CODE_SYSTEM_PROMPT, CacheFirstLoop, type CacheFirstLoopOptions, type CallToolResult, type ChatMessage, type ChatResponse, type ChoiceOption, ChoiceRequestedError, type ChoiceToolOptions, type CodeSystemPromptOptions, DEFAULT_AT_MENTION_MAX_BYTES, DEFAULT_MAX_RESULT_CHARS, DEFAULT_MAX_RESULT_TOKENS, DEFAULT_PICKER_IGNORE_DIRS, DeepSeekClient, type DeepSeekClientOptions, type RenderOptions as DiffRenderOptions, type DiffReport, type DiffSide, type EditBlock, type EditSnapshot, type EventRole, type FileWithStats, type FilesystemToolsOptions, type FlattenDecision, type FlattenOptions, type GetLatestVersionOptions, type GetPromptResult, HOOK_EVENTS, HOOK_SETTINGS_DIRNAME, HOOK_SETTINGS_FILENAME, type HarvestOptions, type HookConfig, type HookEvent, type HookOutcome, type HookPayload, type HookReport, type HookScope, type HookSettings, type HookSpawnInput, type HookSpawnResult, type HookSpawner, ImmutablePrefix, type ImmutablePrefixOptions, type InitializeResult, type InspectionReport, type JSONSchema, type JsonRpcMessage, type JsonRpcRequest, type JsonRpcResponse, LATEST_CACHE_TTL_MS, LATEST_FETCH_TIMEOUT_MS, type ListFilesOptions, type ListPromptsResult, type ListResourcesResult, type ListToolsResult, type LoadHookSettingsOptions, type LoopEvent, MCP_PROTOCOL_VERSION, MEMORY_INDEX_FILE, MEMORY_INDEX_MAX_CHARS, McpClient, type McpClientOptions, type McpContentBlock, type McpProgressHandler, type McpProgressInfo, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpPromptResourceBlock, type McpResource, type McpResourceContents, type McpResourceContentsBlob, type McpResourceContentsText, type McpSpec, type McpTool, type McpToolSchema, type McpTransport, type MemoryEntry, type MemoryScope, MemoryStore, type MemoryStoreOptions, type MemoryToolsOptions, type MemoryType, type WriteInput as MemoryWriteInput, NeedsConfirmationError, PROJECT_MEMORY_FILE, PROJECT_MEMORY_MAX_CHARS, type PageContent, type PickerCandidate, PlanProposedError, PlanRevisionProposedError, type PlanStep, type PlanStepRisk, type PlanToolOptions, type ProgressNotificationParams, type ProjectMemory, type RankPickerOptions, type ReadResourceResult, type ReadTranscriptResult, type ReasonixConfig, type ReconfigurableOptions, type RepairReport, type ReplayStats, type ResolvedHook, type RetryInfo, type RetryOptions, type Role, type RunCommandResult, type RunHooksOptions, type ScavengeOptions, type ScavengeResult, type SearchResult, type SectionResult, type SessionInfo, SessionStats, type SessionSummary, type ShellToolsOptions, type SseMcpSpec, SseTransport, type SseTransportOptions, type StdioMcpSpec, StdioTransport, type StdioTransportOptions, type StepCompletion, StormBreaker, type StreamChunk, type StreamableHttpMcpSpec, StreamableHttpTransport, type StreamableHttpTransportOptions, type SubagentEvent, type SubagentSink, type SubagentToolOptions, type ToolCall, type ToolCallContext, ToolCallRepair, type ToolCallRepairOptions, type ToolDefinition, type ToolFunctionSpec, ToolRegistry, type ToolSpec, type TranscriptMeta, type TranscriptRecord, type TruncationRepairResult, type TurnPair, type TurnStats, type TypedPlanState, USER_MEMORY_DIR, Usage, type UsageAggregate, type UsageBucket, type UsageRecord, VERSION, VolatileScratch, type WebFetchOptions, type WebSearchOptions, type WebToolsOptions, aggregateBranchUsage, aggregateUsage, analyzeSchema, appendSessionMessage, appendUsage, applyEditBlock, applyEditBlocks, applyMemoryStack, applyProjectMemory, applyUserMemory, bridgeMcpTools, bucketCacheHitRatio, bucketSavingsFraction, claudeEquivalentCost, codeSystemPrompt, compareVersions, computeReplayStats, costUsd, decideOutcome, defaultConfigPath, defaultSelector, defaultUsageLogPath, deleteSession, detectAtPicker, detectShellOperator, diffTranscripts, emptyPlanState, expandAtMentions, fetchWithRetry, fixToolCallPairing, flattenMcpResult, flattenSchema, forkRegistryExcluding, formatCommandResult, formatHookOutcomeMessage, formatLogSize, formatLoopError, formatSearchResults, getLatestVersion, globalSettingsPath, harvest, healLoadedMessages, healLoadedMessagesByTokens, htmlToText, injectPowerShellUtf8, inputCostUsd, inspectMcpServer, isAllowed, isJsonRpcError, isNpxInstall, isPlanStateEmpty, isPlausibleKey, listFilesSync, listFilesWithStatsAsync, listFilesWithStatsSync, listSessions, loadApiKey, loadDotenv, loadHooks, loadSessionMessages, matchesTool, memoryEnabled, nestArguments, openTranscriptFile, outputCostUsd, parseEditBlocks, parseMcpSpec, parseMojeekResults, parseSearxngHtmlResults, parseTranscript, prepareSpawn, projectHash, projectSettingsPath, quoteForCmdExe, rankPickerCandidates, readConfig, readProjectMemory, readTranscript, readUsageLog, recordFromLoopEvent, redactKey, registerChoiceTool, registerFilesystemTools, registerMemoryTools, registerPlanTool, registerShellTools, registerSubagentTool, registerWebTools, renderMarkdown as renderDiffMarkdown, renderSummaryTable as renderDiffSummary, repairTruncatedJson, replayFromFile, resolveExecutable, restoreSnapshots, runBranches, runCommand, runHooks, sanitizeMemoryName, sanitizeName as sanitizeSessionName, saveApiKey, scavengeToolCalls, sessionPath, sessionsDir, similarity, snapshotBeforeEdits, stripHallucinatedToolMarkup, tokenizeCommand, truncateForModel, truncateForModelByTokens, webFetch, webSearch, withUtf8Codepage, writeConfig, writeMeta, writeRecord };
|
package/dist/index.js
CHANGED
|
@@ -1480,6 +1480,10 @@ var SessionStats = class {
|
|
|
1480
1480
|
_carryoverCost = 0;
|
|
1481
1481
|
/** Turn count from prior runs of a resumed session. */
|
|
1482
1482
|
_carryoverTurns = 0;
|
|
1483
|
+
_carryoverCacheHit = 0;
|
|
1484
|
+
_carryoverCacheMiss = 0;
|
|
1485
|
+
/** Last turn's promptTokens before exit — surfaced via summary() until the next live turn lands. */
|
|
1486
|
+
_carryoverLastPromptTokens = 0;
|
|
1483
1487
|
/** Seed totals from a resumed session's persisted meta — only call once at construction. */
|
|
1484
1488
|
seedCarryover(opts) {
|
|
1485
1489
|
if (typeof opts.totalCostUsd === "number" && opts.totalCostUsd > 0) {
|
|
@@ -1488,6 +1492,15 @@ var SessionStats = class {
|
|
|
1488
1492
|
if (typeof opts.turnCount === "number" && opts.turnCount > 0) {
|
|
1489
1493
|
this._carryoverTurns = opts.turnCount;
|
|
1490
1494
|
}
|
|
1495
|
+
if (typeof opts.cacheHitTokens === "number" && opts.cacheHitTokens > 0) {
|
|
1496
|
+
this._carryoverCacheHit = opts.cacheHitTokens;
|
|
1497
|
+
}
|
|
1498
|
+
if (typeof opts.cacheMissTokens === "number" && opts.cacheMissTokens > 0) {
|
|
1499
|
+
this._carryoverCacheMiss = opts.cacheMissTokens;
|
|
1500
|
+
}
|
|
1501
|
+
if (typeof opts.lastPromptTokens === "number" && opts.lastPromptTokens > 0) {
|
|
1502
|
+
this._carryoverLastPromptTokens = opts.lastPromptTokens;
|
|
1503
|
+
}
|
|
1491
1504
|
}
|
|
1492
1505
|
record(turn, model, usage) {
|
|
1493
1506
|
const cost = costUsd(model, usage);
|
|
@@ -1518,8 +1531,8 @@ var SessionStats = class {
|
|
|
1518
1531
|
return this.turns.reduce((sum, t) => sum + outputCostUsd(t.model, t.usage), 0);
|
|
1519
1532
|
}
|
|
1520
1533
|
get aggregateCacheHitRatio() {
|
|
1521
|
-
let hit =
|
|
1522
|
-
let miss =
|
|
1534
|
+
let hit = this._carryoverCacheHit;
|
|
1535
|
+
let miss = this._carryoverCacheMiss;
|
|
1523
1536
|
for (const t of this.turns) {
|
|
1524
1537
|
hit += t.usage.promptCacheHitTokens;
|
|
1525
1538
|
miss += t.usage.promptCacheMissTokens;
|
|
@@ -1537,7 +1550,7 @@ var SessionStats = class {
|
|
|
1537
1550
|
claudeEquivalentUsd: round(this.totalClaudeEquivalent, 6),
|
|
1538
1551
|
savingsVsClaudePct: round(this.savingsVsClaude * 100, 2),
|
|
1539
1552
|
cacheHitRatio: round(this.aggregateCacheHitRatio, 4),
|
|
1540
|
-
lastPromptTokens: last?.usage.promptTokens ??
|
|
1553
|
+
lastPromptTokens: last?.usage.promptTokens ?? this._carryoverLastPromptTokens,
|
|
1541
1554
|
lastTurnCostUsd: round(last?.cost ?? 0, 6)
|
|
1542
1555
|
};
|
|
1543
1556
|
}
|
|
@@ -2541,7 +2554,10 @@ var CacheFirstLoop = class {
|
|
|
2541
2554
|
const meta = loadSessionMeta(this.sessionName);
|
|
2542
2555
|
this.stats.seedCarryover({
|
|
2543
2556
|
totalCostUsd: meta.totalCostUsd,
|
|
2544
|
-
turnCount: meta.turnCount
|
|
2557
|
+
turnCount: meta.turnCount,
|
|
2558
|
+
cacheHitTokens: meta.cacheHitTokens,
|
|
2559
|
+
cacheMissTokens: meta.cacheMissTokens,
|
|
2560
|
+
lastPromptTokens: meta.lastPromptTokens
|
|
2545
2561
|
});
|
|
2546
2562
|
}
|
|
2547
2563
|
if (healedCount > 0) {
|
|
@@ -4662,7 +4678,9 @@ function registerFilesystemTools(registry, opts) {
|
|
|
4662
4678
|
const normRoot = pathMod3.resolve(rootDir);
|
|
4663
4679
|
const rel = pathMod3.relative(normRoot, resolved);
|
|
4664
4680
|
if (rel.startsWith("..") || pathMod3.isAbsolute(rel)) {
|
|
4665
|
-
throw new Error(
|
|
4681
|
+
throw new Error(
|
|
4682
|
+
`path escapes sandbox root (${normRoot}): ${raw} \u2014 workspace is pinned at launch; quit and relaunch with \`reasonix code --dir <path>\` to work in a different folder`
|
|
4683
|
+
);
|
|
4666
4684
|
}
|
|
4667
4685
|
return resolved;
|
|
4668
4686
|
};
|
|
@@ -5883,6 +5901,16 @@ function loadApiKey(path2 = defaultConfigPath()) {
|
|
|
5883
5901
|
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
5884
5902
|
return readConfig(path2).apiKey;
|
|
5885
5903
|
}
|
|
5904
|
+
function webSearchEngine(path2 = defaultConfigPath()) {
|
|
5905
|
+
const cfg = readConfig(path2).webSearchEngine;
|
|
5906
|
+
if (cfg === "searxng") return "searxng";
|
|
5907
|
+
return "mojeek";
|
|
5908
|
+
}
|
|
5909
|
+
function webSearchEndpoint(path2 = defaultConfigPath()) {
|
|
5910
|
+
const cfg = readConfig(path2).webSearchEndpoint;
|
|
5911
|
+
if (cfg && typeof cfg === "string") return cfg;
|
|
5912
|
+
return "http://localhost:8080";
|
|
5913
|
+
}
|
|
5886
5914
|
function saveApiKey(key, path2 = defaultConfigPath()) {
|
|
5887
5915
|
const cfg = readConfig(path2);
|
|
5888
5916
|
cfg.apiKey = key.trim();
|
|
@@ -7290,6 +7318,12 @@ var FETCH_MAX_BYTES = 10 * 1024 * 1024;
|
|
|
7290
7318
|
var USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
|
7291
7319
|
var MOJEEK_ENDPOINT = "https://www.mojeek.com/search";
|
|
7292
7320
|
async function webSearch(query, opts = {}) {
|
|
7321
|
+
if (opts.engine === "searxng") {
|
|
7322
|
+
return searchSearxng(query, opts);
|
|
7323
|
+
}
|
|
7324
|
+
return searchMojeek(query, opts);
|
|
7325
|
+
}
|
|
7326
|
+
async function searchMojeek(query, opts = {}) {
|
|
7293
7327
|
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
7294
7328
|
const resp = await fetch(`${MOJEEK_ENDPOINT}?q=${encodeURIComponent(query)}`, {
|
|
7295
7329
|
headers: {
|
|
@@ -7314,6 +7348,90 @@ async function webSearch(query, opts = {}) {
|
|
|
7314
7348
|
}
|
|
7315
7349
|
return results;
|
|
7316
7350
|
}
|
|
7351
|
+
function normalizeSearxngEndpoint(raw) {
|
|
7352
|
+
let url;
|
|
7353
|
+
try {
|
|
7354
|
+
url = new URL(raw.includes("://") ? raw : `http://${raw}`);
|
|
7355
|
+
} catch {
|
|
7356
|
+
throw new Error(`web_search: invalid SearXNG endpoint "${raw}"`);
|
|
7357
|
+
}
|
|
7358
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
7359
|
+
throw new Error(`web_search: SearXNG endpoint must be http(s), got ${url.protocol}`);
|
|
7360
|
+
}
|
|
7361
|
+
return url.origin;
|
|
7362
|
+
}
|
|
7363
|
+
async function searchSearxng(query, opts = {}) {
|
|
7364
|
+
const topK = Math.max(1, Math.min(10, opts.topK ?? DEFAULT_TOPK));
|
|
7365
|
+
const baseUrl = normalizeSearxngEndpoint(opts.endpoint ?? "http://localhost:8080");
|
|
7366
|
+
const url = `${baseUrl}/search?format=html&q=${encodeURIComponent(query)}`;
|
|
7367
|
+
let resp;
|
|
7368
|
+
try {
|
|
7369
|
+
resp = await fetch(url, {
|
|
7370
|
+
headers: {
|
|
7371
|
+
"User-Agent": USER_AGENT,
|
|
7372
|
+
Accept: "text/html"
|
|
7373
|
+
},
|
|
7374
|
+
signal: opts.signal
|
|
7375
|
+
});
|
|
7376
|
+
} catch (err) {
|
|
7377
|
+
if (err instanceof TypeError && err.message.includes("fetch")) {
|
|
7378
|
+
throw new Error(
|
|
7379
|
+
`web_search: Cannot reach SearXNG server at ${opts.endpoint ?? "http://localhost:8080"}. Please install SearXNG (https://github.com/searxng/searxng) and start it (e.g. \`docker run -d -p 8080:8080 searxng/searxng\`), or switch to the default engine with /search-engine mojeek.`
|
|
7380
|
+
);
|
|
7381
|
+
}
|
|
7382
|
+
throw err;
|
|
7383
|
+
}
|
|
7384
|
+
if (!resp.ok) throw new Error(`web_search ${resp.status}`);
|
|
7385
|
+
const html = await resp.text();
|
|
7386
|
+
const results = parseSearxngHtmlResults(html).slice(0, topK);
|
|
7387
|
+
if (results.length === 0) {
|
|
7388
|
+
if (/no results found|did not match any documents/i.test(html)) return [];
|
|
7389
|
+
throw new Error(
|
|
7390
|
+
`web_search: 0 results but SearXNG response doesn't look like an empty results page (${html.length} chars)`
|
|
7391
|
+
);
|
|
7392
|
+
}
|
|
7393
|
+
return results;
|
|
7394
|
+
}
|
|
7395
|
+
function parseSearxngHtmlResults(html) {
|
|
7396
|
+
const root = parseHtml(html);
|
|
7397
|
+
const results = [];
|
|
7398
|
+
const articles = root.querySelectorAll("article.result, div.result");
|
|
7399
|
+
if (articles.length > 0) {
|
|
7400
|
+
for (const article of articles) {
|
|
7401
|
+
const link = article.querySelector("h3 a, h4 a, a[href^='http']");
|
|
7402
|
+
if (!link) continue;
|
|
7403
|
+
const href = link.getAttribute("href");
|
|
7404
|
+
if (!href) continue;
|
|
7405
|
+
const title = link.textContent.trim();
|
|
7406
|
+
if (!title) continue;
|
|
7407
|
+
let snippet = "";
|
|
7408
|
+
for (const p of article.querySelectorAll("p")) {
|
|
7409
|
+
const text = p.textContent.trim();
|
|
7410
|
+
if (text.length > 10 && !text.includes(title)) {
|
|
7411
|
+
snippet = text;
|
|
7412
|
+
break;
|
|
7413
|
+
}
|
|
7414
|
+
}
|
|
7415
|
+
if (!snippet) {
|
|
7416
|
+
const cs = article.querySelector(".content, .result-content, [class*='snippet']");
|
|
7417
|
+
if (cs) snippet = cs.textContent.trim();
|
|
7418
|
+
}
|
|
7419
|
+
results.push({ title, url: href, snippet });
|
|
7420
|
+
}
|
|
7421
|
+
return results;
|
|
7422
|
+
}
|
|
7423
|
+
for (const a of root.querySelectorAll("h3 a[href]")) {
|
|
7424
|
+
const href = a.getAttribute("href");
|
|
7425
|
+
if (!href || href.startsWith("#")) continue;
|
|
7426
|
+
const title = a.textContent.trim();
|
|
7427
|
+
if (!title) continue;
|
|
7428
|
+
let snippet = "";
|
|
7429
|
+
const p = a.parentNode?.parentNode?.querySelector("p");
|
|
7430
|
+
if (p) snippet = p.textContent.trim();
|
|
7431
|
+
results.push({ title, url: href, snippet });
|
|
7432
|
+
}
|
|
7433
|
+
return results;
|
|
7434
|
+
}
|
|
7317
7435
|
function parseMojeekResults(html) {
|
|
7318
7436
|
const titles = [];
|
|
7319
7437
|
const titleAnchorRe = /<a\b[^>]*\bclass="title"[^>]*>[\s\S]*?<\/a>/g;
|
|
@@ -7487,7 +7605,7 @@ function registerWebTools(registry, opts = {}) {
|
|
|
7487
7605
|
const maxFetchChars = opts.maxFetchChars ?? DEFAULT_FETCH_MAX_CHARS;
|
|
7488
7606
|
registry.register({
|
|
7489
7607
|
name: "web_search",
|
|
7490
|
-
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this.",
|
|
7608
|
+
description: "Search the public web. Returns ranked results with title, url, and snippet. Call this when the answer's correctness depends on current state \u2014 anything that changes over time (events, prices, releases, status of a thing in the real world). Composing such answers from training memory invents stale numbers; search first, then ground the answer in the results. For evergreen / definitional questions you don't need this. To change the backend, use /web-search-engine mojeek|searxng.",
|
|
7491
7609
|
readOnly: true,
|
|
7492
7610
|
parallelSafe: true,
|
|
7493
7611
|
parameters: {
|
|
@@ -7502,9 +7620,13 @@ function registerWebTools(registry, opts = {}) {
|
|
|
7502
7620
|
required: ["query"]
|
|
7503
7621
|
},
|
|
7504
7622
|
fn: async (args, ctx) => {
|
|
7623
|
+
const engine = opts.webSearchEngine ?? webSearchEngine();
|
|
7624
|
+
const endpoint = opts.webSearchEndpoint ?? webSearchEndpoint();
|
|
7505
7625
|
const results = await webSearch(args.query, {
|
|
7506
7626
|
topK: args.topK ?? defaultTopK,
|
|
7507
|
-
signal: ctx?.signal
|
|
7627
|
+
signal: ctx?.signal,
|
|
7628
|
+
engine,
|
|
7629
|
+
endpoint
|
|
7508
7630
|
});
|
|
7509
7631
|
return formatSearchResults(args.query, results);
|
|
7510
7632
|
}
|
|
@@ -9649,6 +9771,7 @@ export {
|
|
|
9649
9771
|
parseEditBlocks,
|
|
9650
9772
|
parseMcpSpec,
|
|
9651
9773
|
parseMojeekResults,
|
|
9774
|
+
parseSearxngHtmlResults,
|
|
9652
9775
|
parseTranscript,
|
|
9653
9776
|
prepareSpawn,
|
|
9654
9777
|
projectHash,
|