opencandle 0.1.1 → 0.3.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 +111 -79
- package/dist/analysts/contracts.d.ts +31 -0
- package/dist/analysts/contracts.js +158 -0
- package/dist/analysts/contracts.js.map +1 -0
- package/dist/analysts/orchestrator.d.ts +11 -2
- package/dist/analysts/orchestrator.js +156 -9
- package/dist/analysts/orchestrator.js.map +1 -1
- package/dist/cli.js +26 -10
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +29 -5
- package/dist/config.js +18 -8
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infra/cache.d.ts +34 -0
- package/dist/infra/cache.js +44 -3
- package/dist/infra/cache.js.map +1 -1
- package/dist/infra/index.d.ts +1 -1
- package/dist/infra/index.js +1 -1
- package/dist/infra/index.js.map +1 -1
- package/dist/infra/opencandle-paths.d.ts +1 -0
- package/dist/infra/opencandle-paths.js +3 -0
- package/dist/infra/opencandle-paths.js.map +1 -1
- package/dist/infra/rate-limiter.js +7 -0
- package/dist/infra/rate-limiter.js.map +1 -1
- package/dist/memory/index.d.ts +3 -0
- package/dist/memory/index.js +2 -0
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/manager.d.ts +19 -0
- package/dist/memory/manager.js +132 -0
- package/dist/memory/manager.js.map +1 -0
- package/dist/memory/sqlite.js +12 -1
- package/dist/memory/sqlite.js.map +1 -1
- package/dist/memory/types.d.ts +21 -0
- package/dist/memory/types.js +47 -0
- package/dist/memory/types.js.map +1 -0
- package/dist/onboarding/connect.d.ts +23 -0
- package/dist/onboarding/connect.js +107 -0
- package/dist/onboarding/connect.js.map +1 -0
- package/dist/onboarding/credential-interceptor.d.ts +44 -0
- package/dist/onboarding/credential-interceptor.js +72 -0
- package/dist/onboarding/credential-interceptor.js.map +1 -0
- package/dist/onboarding/degradation-accumulator.d.ts +21 -0
- package/dist/onboarding/degradation-accumulator.js +55 -0
- package/dist/onboarding/degradation-accumulator.js.map +1 -0
- package/dist/onboarding/prompt-user.d.ts +23 -0
- package/dist/onboarding/prompt-user.js +61 -0
- package/dist/onboarding/prompt-user.js.map +1 -0
- package/dist/onboarding/providers.d.ts +109 -0
- package/dist/onboarding/providers.js +236 -0
- package/dist/onboarding/providers.js.map +1 -0
- package/dist/onboarding/state.d.ts +31 -2
- package/dist/onboarding/state.js +141 -13
- package/dist/onboarding/state.js.map +1 -1
- package/dist/onboarding/tool-helpers.d.ts +34 -0
- package/dist/onboarding/tool-helpers.js +80 -0
- package/dist/onboarding/tool-helpers.js.map +1 -0
- package/dist/onboarding/tool-tags.d.ts +37 -0
- package/dist/onboarding/tool-tags.js +149 -0
- package/dist/onboarding/tool-tags.js.map +1 -0
- package/dist/onboarding/validation.d.ts +19 -0
- package/dist/onboarding/validation.js +117 -0
- package/dist/onboarding/validation.js.map +1 -0
- package/dist/pi/opencandle-extension.d.ts +5 -1
- package/dist/pi/opencandle-extension.js +345 -143
- package/dist/pi/opencandle-extension.js.map +1 -1
- package/dist/pi/session.d.ts +2 -0
- package/dist/pi/session.js +1 -1
- package/dist/pi/session.js.map +1 -1
- package/dist/pi/setup.d.ts +0 -1
- package/dist/pi/setup.js +66 -119
- package/dist/pi/setup.js.map +1 -1
- package/dist/prompts/context-builder.d.ts +26 -0
- package/dist/prompts/context-builder.js +127 -0
- package/dist/prompts/context-builder.js.map +1 -0
- package/dist/prompts/sections.d.ts +13 -0
- package/dist/prompts/sections.js +35 -0
- package/dist/prompts/sections.js.map +1 -0
- package/dist/providers/alpha-vantage.d.ts +3 -0
- package/dist/providers/alpha-vantage.js +204 -77
- package/dist/providers/alpha-vantage.js.map +1 -1
- package/dist/providers/coingecko.js +53 -37
- package/dist/providers/coingecko.js.map +1 -1
- package/dist/providers/exa-search.d.ts +39 -0
- package/dist/providers/exa-search.js +276 -0
- package/dist/providers/exa-search.js.map +1 -0
- package/dist/providers/fear-greed.js +23 -15
- package/dist/providers/fear-greed.js.map +1 -1
- package/dist/providers/finnhub.d.ts +17 -0
- package/dist/providers/finnhub.js +94 -0
- package/dist/providers/finnhub.js.map +1 -0
- package/dist/providers/fred.js +48 -28
- package/dist/providers/fred.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/provider-credential-error.d.ts +8 -0
- package/dist/providers/provider-credential-error.js +22 -0
- package/dist/providers/provider-credential-error.js.map +1 -0
- package/dist/providers/reddit.d.ts +8 -0
- package/dist/providers/reddit.js +78 -43
- package/dist/providers/reddit.js.map +1 -1
- package/dist/providers/twitter.d.ts +20 -0
- package/dist/providers/twitter.js +137 -0
- package/dist/providers/twitter.js.map +1 -0
- package/dist/providers/web-search.d.ts +17 -0
- package/dist/providers/web-search.js +224 -0
- package/dist/providers/web-search.js.map +1 -0
- package/dist/providers/with-fallback.d.ts +15 -0
- package/dist/providers/with-fallback.js +32 -0
- package/dist/providers/with-fallback.js.map +1 -0
- package/dist/providers/wrap-provider.d.ts +20 -0
- package/dist/providers/wrap-provider.js +58 -0
- package/dist/providers/wrap-provider.js.map +1 -0
- package/dist/providers/yahoo-finance.js +77 -57
- package/dist/providers/yahoo-finance.js.map +1 -1
- package/dist/routing/classify-intent.js +22 -0
- package/dist/routing/classify-intent.js.map +1 -1
- package/dist/runtime/evidence.d.ts +35 -0
- package/dist/runtime/evidence.js +29 -0
- package/dist/runtime/evidence.js.map +1 -0
- package/dist/runtime/index.d.ts +16 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/prompt-step.d.ts +41 -0
- package/dist/runtime/prompt-step.js +42 -0
- package/dist/runtime/prompt-step.js.map +1 -0
- package/dist/runtime/provider-ids.d.ts +14 -0
- package/dist/runtime/provider-ids.js +14 -0
- package/dist/runtime/provider-ids.js.map +1 -0
- package/dist/runtime/provider-tracker.d.ts +20 -0
- package/dist/runtime/provider-tracker.js +36 -0
- package/dist/runtime/provider-tracker.js.map +1 -0
- package/dist/runtime/run-context.d.ts +11 -0
- package/dist/runtime/run-context.js +14 -0
- package/dist/runtime/run-context.js.map +1 -0
- package/dist/runtime/session-coordinator.d.ts +47 -0
- package/dist/runtime/session-coordinator.js +171 -0
- package/dist/runtime/session-coordinator.js.map +1 -0
- package/dist/runtime/validation.d.ts +44 -0
- package/dist/runtime/validation.js +157 -0
- package/dist/runtime/validation.js.map +1 -0
- package/dist/runtime/workflow-events.d.ts +21 -0
- package/dist/runtime/workflow-events.js +31 -0
- package/dist/runtime/workflow-events.js.map +1 -0
- package/dist/runtime/workflow-runner.d.ts +36 -0
- package/dist/runtime/workflow-runner.js +129 -0
- package/dist/runtime/workflow-runner.js.map +1 -0
- package/dist/runtime/workflow-types.d.ts +60 -0
- package/dist/runtime/workflow-types.js +32 -0
- package/dist/runtime/workflow-types.js.map +1 -0
- package/dist/sentiment/adapters/finnhub.d.ts +7 -0
- package/dist/sentiment/adapters/finnhub.js +39 -0
- package/dist/sentiment/adapters/finnhub.js.map +1 -0
- package/dist/sentiment/adapters/reddit.d.ts +11 -0
- package/dist/sentiment/adapters/reddit.js +54 -0
- package/dist/sentiment/adapters/reddit.js.map +1 -0
- package/dist/sentiment/adapters/twitter.d.ts +9 -0
- package/dist/sentiment/adapters/twitter.js +32 -0
- package/dist/sentiment/adapters/twitter.js.map +1 -0
- package/dist/sentiment/adapters/web.d.ts +9 -0
- package/dist/sentiment/adapters/web.js +40 -0
- package/dist/sentiment/adapters/web.js.map +1 -0
- package/dist/sentiment/index.d.ts +16 -0
- package/dist/sentiment/index.js +44 -0
- package/dist/sentiment/index.js.map +1 -0
- package/dist/sentiment/keywords.d.ts +2 -0
- package/dist/sentiment/keywords.js +9 -0
- package/dist/sentiment/keywords.js.map +1 -0
- package/dist/sentiment/pipeline.d.ts +9 -0
- package/dist/sentiment/pipeline.js +57 -0
- package/dist/sentiment/pipeline.js.map +1 -0
- package/dist/sentiment/scorer.d.ts +9 -0
- package/dist/sentiment/scorer.js +64 -0
- package/dist/sentiment/scorer.js.map +1 -0
- package/dist/sentiment/store.d.ts +24 -0
- package/dist/sentiment/store.js +177 -0
- package/dist/sentiment/store.js.map +1 -0
- package/dist/sentiment/trends.d.ts +13 -0
- package/dist/sentiment/trends.js +73 -0
- package/dist/sentiment/trends.js.map +1 -0
- package/dist/sentiment/types.d.ts +66 -0
- package/dist/sentiment/types.js +54 -0
- package/dist/sentiment/types.js.map +1 -0
- package/dist/system-prompt.js +60 -2
- package/dist/system-prompt.js.map +1 -1
- package/dist/tool-kit.d.ts +9 -5
- package/dist/tool-kit.js +29 -6
- package/dist/tool-kit.js.map +1 -1
- package/dist/tools/fundamentals/company-overview.d.ts +3 -1
- package/dist/tools/fundamentals/company-overview.js +28 -17
- package/dist/tools/fundamentals/company-overview.js.map +1 -1
- package/dist/tools/fundamentals/comps.js +47 -39
- package/dist/tools/fundamentals/comps.js.map +1 -1
- package/dist/tools/fundamentals/dcf.js +83 -65
- package/dist/tools/fundamentals/dcf.js.map +1 -1
- package/dist/tools/fundamentals/earnings.d.ts +3 -1
- package/dist/tools/fundamentals/earnings.js +26 -18
- package/dist/tools/fundamentals/earnings.js.map +1 -1
- package/dist/tools/fundamentals/financials.d.ts +3 -1
- package/dist/tools/fundamentals/financials.js +24 -16
- package/dist/tools/fundamentals/financials.js.map +1 -1
- package/dist/tools/fundamentals/sec-filings.js +9 -1
- package/dist/tools/fundamentals/sec-filings.js.map +1 -1
- package/dist/tools/index.js +10 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interaction/ask-user.d.ts +3 -0
- package/dist/tools/interaction/ask-user.js +51 -0
- package/dist/tools/interaction/ask-user.js.map +1 -0
- package/dist/tools/interaction/twitter-login.d.ts +8 -0
- package/dist/tools/interaction/twitter-login.js +77 -0
- package/dist/tools/interaction/twitter-login.js.map +1 -0
- package/dist/tools/macro/fear-greed.js +9 -1
- package/dist/tools/macro/fear-greed.js.map +1 -1
- package/dist/tools/macro/fred-data.d.ts +3 -1
- package/dist/tools/macro/fred-data.js +27 -16
- package/dist/tools/macro/fred-data.js.map +1 -1
- package/dist/tools/market/crypto-history.js +9 -1
- package/dist/tools/market/crypto-history.js.map +1 -1
- package/dist/tools/market/crypto-price.js +9 -1
- package/dist/tools/market/crypto-price.js.map +1 -1
- package/dist/tools/market/stock-history.js +28 -1
- package/dist/tools/market/stock-history.js.map +1 -1
- package/dist/tools/market/stock-quote.js +29 -4
- package/dist/tools/market/stock-quote.js.map +1 -1
- package/dist/tools/options/option-chain.js +9 -1
- package/dist/tools/options/option-chain.js.map +1 -1
- package/dist/tools/portfolio/correlation.js +15 -3
- package/dist/tools/portfolio/correlation.js.map +1 -1
- package/dist/tools/portfolio/predictions.js +6 -5
- package/dist/tools/portfolio/predictions.js.map +1 -1
- package/dist/tools/portfolio/risk-analysis.js +9 -1
- package/dist/tools/portfolio/risk-analysis.js.map +1 -1
- package/dist/tools/portfolio/tracker.js +6 -3
- package/dist/tools/portfolio/tracker.js.map +1 -1
- package/dist/tools/portfolio/watchlist.js +6 -1
- package/dist/tools/portfolio/watchlist.js.map +1 -1
- package/dist/tools/sentiment/reddit-sentiment.d.ts +3 -1
- package/dist/tools/sentiment/reddit-sentiment.js +112 -19
- package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
- package/dist/tools/sentiment/sentiment-summary.d.ts +7 -0
- package/dist/tools/sentiment/sentiment-summary.js +230 -0
- package/dist/tools/sentiment/sentiment-summary.js.map +1 -0
- package/dist/tools/sentiment/sentiment-trend.d.ts +22 -0
- package/dist/tools/sentiment/sentiment-trend.js +39 -0
- package/dist/tools/sentiment/sentiment-trend.js.map +1 -0
- package/dist/tools/sentiment/twitter-sentiment.d.ts +9 -0
- package/dist/tools/sentiment/twitter-sentiment.js +75 -0
- package/dist/tools/sentiment/twitter-sentiment.js.map +1 -0
- package/dist/tools/sentiment/web-search.d.ts +11 -0
- package/dist/tools/sentiment/web-search.js +115 -0
- package/dist/tools/sentiment/web-search.js.map +1 -0
- package/dist/tools/sentiment/web-sentiment.d.ts +8 -0
- package/dist/tools/sentiment/web-sentiment.js +66 -0
- package/dist/tools/sentiment/web-sentiment.js.map +1 -0
- package/dist/tools/technical/backtest.js +9 -1
- package/dist/tools/technical/backtest.js.map +1 -1
- package/dist/tools/technical/indicators.js +9 -1
- package/dist/tools/technical/indicators.js.map +1 -1
- package/dist/types/index.d.ts +16 -1
- package/dist/types/sentiment.d.ts +41 -0
- package/dist/workflows/compare-assets.d.ts +3 -0
- package/dist/workflows/compare-assets.js +21 -5
- package/dist/workflows/compare-assets.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -3
- package/dist/workflows/index.js +3 -3
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/options-screener.d.ts +3 -0
- package/dist/workflows/options-screener.js +24 -7
- package/dist/workflows/options-screener.js.map +1 -1
- package/dist/workflows/portfolio-builder.d.ts +3 -0
- package/dist/workflows/portfolio-builder.js +30 -9
- package/dist/workflows/portfolio-builder.js.map +1 -1
- package/package.json +27 -7
- package/dist/tools/sentiment/news-sentiment.d.ts +0 -7
- package/dist/tools/sentiment/news-sentiment.js +0 -57
- package/dist/tools/sentiment/news-sentiment.js.map +0 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { getSubredditPosts, getPostComments } from "../../providers/reddit.js";
|
|
3
|
+
import { getTwitterSentiment } from "../../providers/twitter.js";
|
|
4
|
+
import { searchWeb } from "../../providers/web-search.js";
|
|
5
|
+
import { getCompanyNews, finnhubDateRange } from "../../providers/finnhub.js";
|
|
6
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
7
|
+
import { getConfig } from "../../config.js";
|
|
8
|
+
import { TwitterAdapter } from "../../sentiment/adapters/twitter.js";
|
|
9
|
+
import { RedditAdapter } from "../../sentiment/adapters/reddit.js";
|
|
10
|
+
import { WebAdapter } from "../../sentiment/adapters/web.js";
|
|
11
|
+
import { FinnhubAdapter, extractTickersFromQuery } from "../../sentiment/adapters/finnhub.js";
|
|
12
|
+
import { getSentimentPipeline } from "../../sentiment/index.js";
|
|
13
|
+
import { hasCredential } from "../../onboarding/providers.js";
|
|
14
|
+
import { buildSoftDegradedTag } from "../../onboarding/tool-tags.js";
|
|
15
|
+
const params = Type.Object({
|
|
16
|
+
query: Type.String({ description: "Ticker or topic for cross-source sentiment summary" }),
|
|
17
|
+
hours: Type.Optional(Type.Number({ description: "Lookback window in hours for live fetching. Default: 24" })),
|
|
18
|
+
});
|
|
19
|
+
export const sentimentSummaryTool = {
|
|
20
|
+
name: "get_sentiment_summary",
|
|
21
|
+
label: "Sentiment Summary",
|
|
22
|
+
description: "Cross-source sentiment summary combining Twitter, Reddit, and web/news. Returns per-source scores, aggregate sentiment, and divergence detection.",
|
|
23
|
+
parameters: params,
|
|
24
|
+
async execute(toolCallId, args) {
|
|
25
|
+
const hours = args.hours ?? 24;
|
|
26
|
+
const config = getConfig();
|
|
27
|
+
const warnings = [];
|
|
28
|
+
const allRecords = [];
|
|
29
|
+
const twitterAdapter = new TwitterAdapter();
|
|
30
|
+
const redditAdapter = new RedditAdapter();
|
|
31
|
+
const webAdapter = new WebAdapter();
|
|
32
|
+
const finnhubAdapter = new FinnhubAdapter();
|
|
33
|
+
// Determine if Finnhub should be included (key configured + ticker in
|
|
34
|
+
// query). `candidateTickers` is extracted unconditionally so we can tell
|
|
35
|
+
// a "no finnhub-mappable ticker in the query" case apart from a "query
|
|
36
|
+
// has tickers but user has no Finnhub key" case — the latter warrants a
|
|
37
|
+
// soft-degraded tag so the LLM surfaces it in the Data gaps section.
|
|
38
|
+
const candidateTickers = extractTickersFromQuery(args.query);
|
|
39
|
+
const finnhubTickers = config.finnhubApiKey ? candidateTickers : [];
|
|
40
|
+
const includeFinnhub = finnhubTickers.length > 0 && Boolean(config.finnhubApiKey);
|
|
41
|
+
const finnhubSoftDegraded = candidateTickers.length > 0 && !hasCredential("finnhub");
|
|
42
|
+
// Finnhub fetch (built separately to avoid mixing promise types in allSettled)
|
|
43
|
+
const finnhubFetch = includeFinnhub
|
|
44
|
+
? (async () => {
|
|
45
|
+
const { from, to } = finnhubDateRange("day");
|
|
46
|
+
const arrays = await Promise.all(finnhubTickers.map((sym) => getCompanyNews(sym, from, to, config.finnhubApiKey)));
|
|
47
|
+
return arrays.flat();
|
|
48
|
+
})()
|
|
49
|
+
: Promise.resolve([]);
|
|
50
|
+
// Fetch all sources in parallel
|
|
51
|
+
const [twitterResult, redditResults, webResult, finnhubResult] = await Promise.allSettled([
|
|
52
|
+
// Twitter
|
|
53
|
+
wrapProvider("twitter", () => getTwitterSentiment(args.query, 50, hours)),
|
|
54
|
+
// Reddit — cross-subreddit
|
|
55
|
+
fetchRedditCrossSubreddit(args.query, config.sentiment?.defaultSubreddits ?? ["wallstreetbets", "stocks", "investing", "options"]),
|
|
56
|
+
// Web
|
|
57
|
+
searchWeb(args.query, { freshness: "day", limit: 10, category: "news" }),
|
|
58
|
+
// Finnhub — only when includeFinnhub; otherwise resolves to []
|
|
59
|
+
finnhubFetch,
|
|
60
|
+
]);
|
|
61
|
+
// Process Twitter
|
|
62
|
+
if (twitterResult.status === "fulfilled" && twitterResult.value.status === "ok") {
|
|
63
|
+
const records = twitterAdapter.mapToRecords(twitterResult.value.data, args.query);
|
|
64
|
+
allRecords.push(...records);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const reason = twitterResult.status === "rejected"
|
|
68
|
+
? twitterResult.reason?.message ?? "unknown error"
|
|
69
|
+
: twitterResult.value.reason ?? "unavailable";
|
|
70
|
+
warnings.push(`Twitter: ${reason}`);
|
|
71
|
+
}
|
|
72
|
+
// Process Reddit
|
|
73
|
+
if (redditResults.status === "fulfilled") {
|
|
74
|
+
const { records: redditRecords, warnings: redditWarnings } = redditResults.value;
|
|
75
|
+
allRecords.push(...redditRecords);
|
|
76
|
+
warnings.push(...redditWarnings);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
warnings.push(`Reddit: ${redditResults.reason?.message ?? "unknown error"}`);
|
|
80
|
+
}
|
|
81
|
+
// Process Web
|
|
82
|
+
if (webResult.status === "fulfilled" && webResult.value.status === "ok") {
|
|
83
|
+
const records = webAdapter.mapToRecords(webResult.value.data, args.query);
|
|
84
|
+
allRecords.push(...records);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const reason = webResult.status === "rejected"
|
|
88
|
+
? webResult.reason?.message ?? "unknown error"
|
|
89
|
+
: webResult.value.reason ?? "unavailable";
|
|
90
|
+
warnings.push(`Web: ${reason}`);
|
|
91
|
+
}
|
|
92
|
+
// Process Finnhub (only when included — otherwise resolves to empty array anyway)
|
|
93
|
+
if (includeFinnhub) {
|
|
94
|
+
if (finnhubResult.status === "fulfilled") {
|
|
95
|
+
const articles = finnhubResult.value;
|
|
96
|
+
if (articles.length > 0) {
|
|
97
|
+
const records = finnhubAdapter.mapToRecords(articles, args.query);
|
|
98
|
+
allRecords.push(...records);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
warnings.push(`Finnhub: ${finnhubResult.reason?.message ?? "unknown error"}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const softDegradedPrefix = finnhubSoftDegraded
|
|
106
|
+
? `${buildSoftDegradedTag({
|
|
107
|
+
provider: "finnhub",
|
|
108
|
+
fallback: "other-sentiment-sources",
|
|
109
|
+
remediation: "run /connect news to enable Finnhub company news",
|
|
110
|
+
})}\n\n`
|
|
111
|
+
: "";
|
|
112
|
+
if (allRecords.length === 0) {
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: `${softDegradedPrefix}⚠ Sentiment summary unavailable for "${args.query}" — no sources returned data.\n${warnings.join("\n")}`,
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
details: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Score and index through pipeline
|
|
124
|
+
const pipeline = getSentimentPipeline();
|
|
125
|
+
const result = await pipeline.processRecords(allRecords, args.query);
|
|
126
|
+
// Group by source (exclude comments from per-source averages)
|
|
127
|
+
const bySource = {};
|
|
128
|
+
for (const rec of result.fresh) {
|
|
129
|
+
if (rec.metadata.isComment)
|
|
130
|
+
continue;
|
|
131
|
+
if (!bySource[rec.source])
|
|
132
|
+
bySource[rec.source] = { total: 0, count: 0 };
|
|
133
|
+
bySource[rec.source].total += rec.sentiment.score;
|
|
134
|
+
bySource[rec.source].count++;
|
|
135
|
+
}
|
|
136
|
+
const lines = [];
|
|
137
|
+
lines.push(`**Sentiment summary for "${args.query}"** (last ${hours}h):`);
|
|
138
|
+
lines.push("");
|
|
139
|
+
lines.push("| Source | Score | Count | Signal |");
|
|
140
|
+
lines.push("|--------|-------|-------|--------|");
|
|
141
|
+
let totalScore = 0;
|
|
142
|
+
let totalCount = 0;
|
|
143
|
+
for (const [source, stats] of Object.entries(bySource)) {
|
|
144
|
+
const avg = stats.count > 0 ? stats.total / stats.count : 0;
|
|
145
|
+
const label = sentimentLabel(avg);
|
|
146
|
+
const sourceName = source === "web" ? "Web/News" : source.charAt(0).toUpperCase() + source.slice(1);
|
|
147
|
+
lines.push(`| ${sourceName} | ${avg >= 0 ? "+" : ""}${avg.toFixed(2)} | ${stats.count} | ${label} |`);
|
|
148
|
+
totalScore += stats.total;
|
|
149
|
+
totalCount += stats.count;
|
|
150
|
+
}
|
|
151
|
+
const aggregate = totalCount > 0 ? totalScore / totalCount : 0;
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(`**Aggregate:** ${aggregate >= 0 ? "+" : ""}${aggregate.toFixed(2)} (${sentimentLabel(aggregate)})`);
|
|
154
|
+
// Divergence
|
|
155
|
+
if (result.divergence && result.divergence.detected) {
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push(result.divergence.message);
|
|
158
|
+
}
|
|
159
|
+
else if (result.divergence && !result.divergence.detected) {
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push(result.divergence.message);
|
|
162
|
+
}
|
|
163
|
+
// Trend
|
|
164
|
+
if (result.trend && result.trend.length > 0) {
|
|
165
|
+
const t = result.trend[0];
|
|
166
|
+
lines.push("");
|
|
167
|
+
lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.count} records)`);
|
|
168
|
+
}
|
|
169
|
+
if (warnings.length > 0) {
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push(warnings.map((w) => `⚠ ${w}`).join("\n"));
|
|
172
|
+
}
|
|
173
|
+
const output = softDegradedPrefix + lines.join("\n");
|
|
174
|
+
return { content: [{ type: "text", text: output }], details: result };
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
async function fetchRedditCrossSubreddit(query, subreddits) {
|
|
178
|
+
const adapter = new RedditAdapter();
|
|
179
|
+
const records = [];
|
|
180
|
+
const warnings = [];
|
|
181
|
+
const config = getConfig();
|
|
182
|
+
const commentsPerPost = config.sentiment?.commentsPerPost ?? 5;
|
|
183
|
+
for (const sub of subreddits) {
|
|
184
|
+
const result = await wrapProvider("reddit", () => getSubredditPosts(sub, 25));
|
|
185
|
+
if (result.status === "unavailable") {
|
|
186
|
+
warnings.push(`Reddit r/${sub}: ${result.reason}`);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const postRecords = adapter.mapPostsToRecords(result.data, query);
|
|
190
|
+
// Topic filter
|
|
191
|
+
const queryLower = query.toLowerCase();
|
|
192
|
+
const filtered = postRecords.filter((r) => r.text.toLowerCase().includes(queryLower) ||
|
|
193
|
+
(r.title?.toLowerCase().includes(queryLower) ?? false));
|
|
194
|
+
records.push(...filtered);
|
|
195
|
+
// Fetch comments for top posts
|
|
196
|
+
const topPosts = [...filtered]
|
|
197
|
+
.sort((a, b) => b.engagement.score - a.engagement.score)
|
|
198
|
+
.slice(0, 3); // fewer per sub since we're searching multiple
|
|
199
|
+
for (const post of topPosts) {
|
|
200
|
+
if ((post.engagement.replies ?? 0) === 0)
|
|
201
|
+
continue;
|
|
202
|
+
try {
|
|
203
|
+
const comments = await getPostComments(sub, post.sourceId, commentsPerPost);
|
|
204
|
+
records.push(...adapter.mapCommentsToRecords(comments, post.sourceId, sub, query));
|
|
205
|
+
}
|
|
206
|
+
catch { /* non-fatal */ }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Deduplicate
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
const deduped = records.filter((r) => {
|
|
212
|
+
if (seen.has(r.sourceId))
|
|
213
|
+
return false;
|
|
214
|
+
seen.add(r.sourceId);
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
return { records: deduped, warnings };
|
|
218
|
+
}
|
|
219
|
+
function sentimentLabel(score) {
|
|
220
|
+
if (score > 0.3)
|
|
221
|
+
return "Bullish";
|
|
222
|
+
if (score < -0.3)
|
|
223
|
+
return "Bearish";
|
|
224
|
+
if (score > 0)
|
|
225
|
+
return "Leaning Bullish";
|
|
226
|
+
if (score < 0)
|
|
227
|
+
return "Leaning Bearish";
|
|
228
|
+
return "Neutral";
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=sentiment-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentiment-summary.js","sourceRoot":"","sources":["../../../src/tools/sentiment/sentiment-summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import { SentimentStore } from "../../sentiment/store.js";
|
|
3
|
+
declare const params: import("@sinclair/typebox").TObject<{
|
|
4
|
+
query: import("@sinclair/typebox").TString;
|
|
5
|
+
days: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
6
|
+
source: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"twitter">, import("@sinclair/typebox").TLiteral<"reddit">, import("@sinclair/typebox").TLiteral<"web">, import("@sinclair/typebox").TLiteral<"finnhub">]>>;
|
|
7
|
+
}>;
|
|
8
|
+
interface TrendToolResult {
|
|
9
|
+
content: Array<{
|
|
10
|
+
type: "text";
|
|
11
|
+
text: string;
|
|
12
|
+
}>;
|
|
13
|
+
details: any;
|
|
14
|
+
}
|
|
15
|
+
export declare const sentimentTrendTool: AgentTool<typeof params> & {
|
|
16
|
+
executeWithStore: (toolCallId: string, args: {
|
|
17
|
+
query: string;
|
|
18
|
+
days?: number;
|
|
19
|
+
source?: string;
|
|
20
|
+
}, store: SentimentStore) => Promise<TrendToolResult>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { getSentimentStore } from "../../sentiment/index.js";
|
|
3
|
+
import { computeTrend } from "../../sentiment/trends.js";
|
|
4
|
+
const params = Type.Object({
|
|
5
|
+
query: Type.String({ description: "Ticker or topic to look up sentiment history" }),
|
|
6
|
+
days: Type.Optional(Type.Number({ description: "Number of days of history. Default: 7, max: 30" })),
|
|
7
|
+
source: Type.Optional(Type.Union([Type.Literal("twitter"), Type.Literal("reddit"), Type.Literal("web"), Type.Literal("finnhub")], {
|
|
8
|
+
description: "Filter to a single source. Default: all sources.",
|
|
9
|
+
})),
|
|
10
|
+
});
|
|
11
|
+
export const sentimentTrendTool = {
|
|
12
|
+
name: "get_sentiment_trend",
|
|
13
|
+
label: "Sentiment Trend",
|
|
14
|
+
description: "Query historical sentiment data from the local store. No live API calls — returns trends from previously fetched data. Run a sentiment query first to populate the store.",
|
|
15
|
+
parameters: params,
|
|
16
|
+
async execute(toolCallId, args) {
|
|
17
|
+
const store = getSentimentStore();
|
|
18
|
+
return sentimentTrendTool.executeWithStore(toolCallId, args, store);
|
|
19
|
+
},
|
|
20
|
+
async executeWithStore(_toolCallId, args, store) {
|
|
21
|
+
const days = Math.min(args.days ?? 7, 30);
|
|
22
|
+
const series = store.getTimeSeries(args.query, { days, bucketHours: 24 });
|
|
23
|
+
if (series.length === 0) {
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: `No historical sentiment data for "${args.query}". Run a sentiment query first to populate the store.` }],
|
|
26
|
+
details: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const trend = computeTrend(series, args.source ?? "aggregate");
|
|
30
|
+
const lines = [
|
|
31
|
+
`**Sentiment trend for "${args.query}"** (${days}d):`,
|
|
32
|
+
"",
|
|
33
|
+
`${trend.sparkline} ${trend.direction} (${trend.delta >= 0 ? "+" : ""}${trend.delta.toFixed(2)})`,
|
|
34
|
+
`Avg: ${trend.avgScore.toFixed(2)} | Records: ${trend.count}`,
|
|
35
|
+
];
|
|
36
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: { trend, series } };
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=sentiment-trend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentiment-trend.js","sourceRoot":"","sources":["../../../src/tools/sentiment/sentiment-trend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,8CAA8C,EAAE,CAAC;IACnF,IAAI,EAAE,IAAI,CAAC,QAAQ,CACjB,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,gDAAgD,EAAE,CAAC,CAC/E;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACnB,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE;QAC1G,WAAW,EAAE,kDAAkD;KAChE,CAAC,CACH;CACF,CAAC,CAAC;AAOH,MAAM,CAAC,MAAM,kBAAkB,GAE3B;IACF,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,iBAAiB;IACxB,WAAW,EACT,2KAA2K;IAC7K,UAAU,EAAE,MAAM;IAClB,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI;QAC5B,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;QAClC,OAAO,kBAAkB,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IACtE,CAAC;IACD,KAAK,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,EAAE,KAAK;QAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC;QAE1E,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,qCAAqC,IAAI,CAAC,KAAK,uDAAuD,EAAE,CAAC;gBACzI,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,EAAG,IAAI,CAAC,MAAc,IAAI,WAAW,CAAC,CAAC;QAExE,MAAM,KAAK,GAAG;YACZ,0BAA0B,IAAI,CAAC,KAAK,QAAQ,IAAI,KAAK;YACrD,EAAE;YACF,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;YAClG,QAAQ,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,KAAK,CAAC,KAAK,EAAE;SAC9D,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC;IAC7F,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import type { TwitterSentimentResult } from "../../types/sentiment.js";
|
|
3
|
+
declare const params: import("@sinclair/typebox").TObject<{
|
|
4
|
+
query: import("@sinclair/typebox").TString;
|
|
5
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
6
|
+
hours: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
7
|
+
}>;
|
|
8
|
+
export declare const twitterSentimentTool: AgentTool<typeof params, TwitterSentimentResult>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { getTwitterSentiment } from "../../providers/twitter.js";
|
|
3
|
+
import { wrapProvider } from "../../providers/wrap-provider.js";
|
|
4
|
+
import { TwitterAdapter } from "../../sentiment/adapters/twitter.js";
|
|
5
|
+
import { getSentimentPipeline } from "../../sentiment/index.js";
|
|
6
|
+
const params = Type.Object({
|
|
7
|
+
query: Type.String({
|
|
8
|
+
description: "Stock ticker (e.g. AAPL) or search term (e.g. 'AAPL earnings call')",
|
|
9
|
+
}),
|
|
10
|
+
limit: Type.Optional(Type.Number({ description: "Max tweets to fetch. Default: 50, max: 200" })),
|
|
11
|
+
hours: Type.Optional(Type.Number({ description: "Lookback window in hours. Default: 24" })),
|
|
12
|
+
});
|
|
13
|
+
export const twitterSentimentTool = {
|
|
14
|
+
name: "get_twitter_sentiment",
|
|
15
|
+
label: "Twitter Sentiment",
|
|
16
|
+
description: "Fetch recent tweets for a stock ticker or search query and compute engagement-weighted sentiment. Returns tweet data, sentiment score, and co-mentioned tickers. Requires a Twitter session via trigger_twitter_login.",
|
|
17
|
+
parameters: params,
|
|
18
|
+
async execute(toolCallId, args) {
|
|
19
|
+
const limit = Math.min(args.limit ?? 50, 200);
|
|
20
|
+
const hours = args.hours ?? 24;
|
|
21
|
+
const providerResult = await wrapProvider("twitter", () => getTwitterSentiment(args.query, limit, hours));
|
|
22
|
+
if (providerResult.status === "unavailable") {
|
|
23
|
+
const isLoginIssue = providerResult.reason.includes("No Twitter session") ||
|
|
24
|
+
providerResult.reason.includes("session expired");
|
|
25
|
+
const text = isLoginIssue
|
|
26
|
+
? `⚠ Twitter sentiment unavailable: ${providerResult.reason}\n[LOGIN_NEEDED] Use ask_user to confirm, then call trigger_twitter_login. After success, retry this tool.`
|
|
27
|
+
: `⚠ Twitter sentiment unavailable (${providerResult.reason}).`;
|
|
28
|
+
return {
|
|
29
|
+
content: [{ type: "text", text }],
|
|
30
|
+
details: null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const result = providerResult.data;
|
|
34
|
+
const sentimentLabel = result.sentimentScore > 0.3 ? "Bullish" :
|
|
35
|
+
result.sentimentScore < -0.3 ? "Bearish" :
|
|
36
|
+
result.sentimentScore > 0 ? "Leaning Bullish" :
|
|
37
|
+
result.sentimentScore < 0 ? "Leaning Bearish" : "Neutral";
|
|
38
|
+
const lines = [
|
|
39
|
+
`**Twitter: ${result.query}** — ${result.tweetCount} tweets (last ${hours}h, ${result.fetchedAt})`,
|
|
40
|
+
`Sentiment: ${result.sentimentScore.toFixed(2)} (${sentimentLabel}) | Bullish: ${result.bullishCount} | Bearish: ${result.bearishCount}`,
|
|
41
|
+
];
|
|
42
|
+
if (result.topMentions.length > 0) {
|
|
43
|
+
lines.push(`Co-mentions: ${result.topMentions.map((t) => `$${t}`).join(", ")}`);
|
|
44
|
+
}
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push("| Author | Tweet | ❤️ | 🔁 | 💬 |");
|
|
47
|
+
lines.push("|--------|-------|----|----|----|");
|
|
48
|
+
const top = result.tweets.slice(0, 15);
|
|
49
|
+
for (const tweet of top) {
|
|
50
|
+
const text = tweet.text.replace(/\|/g, "\\|").replace(/\n/g, " ").slice(0, 100);
|
|
51
|
+
lines.push(`| @${tweet.author} | ${text} | ${tweet.likes} | ${tweet.retweets} | ${tweet.replies} |`);
|
|
52
|
+
}
|
|
53
|
+
if (providerResult.stale) {
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push(`⚠ Stale data (cached at ${providerResult.timestamp})`);
|
|
56
|
+
}
|
|
57
|
+
// Index in sentiment store and append trend context
|
|
58
|
+
try {
|
|
59
|
+
const adapter = new TwitterAdapter();
|
|
60
|
+
const records = adapter.mapToRecords(result, args.query);
|
|
61
|
+
const pipeline = getSentimentPipeline();
|
|
62
|
+
const pipelineResult = await pipeline.processRecords(records, args.query);
|
|
63
|
+
if (pipelineResult.trend && pipelineResult.trend.length > 0) {
|
|
64
|
+
const t = pipelineResult.trend[0];
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.delta >= 0 ? "+" : ""}${t.delta.toFixed(2)}, ${t.count} records)`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Sentiment indexing is best-effort — don't fail the tool
|
|
71
|
+
}
|
|
72
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: result };
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=twitter-sentiment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"twitter-sentiment.js","sourceRoot":"","sources":["../../../src/tools/sentiment/twitter-sentiment.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import type { WebSearchEnvelope } from "../../types/sentiment.js";
|
|
3
|
+
declare const params: import("@sinclair/typebox").TObject<{
|
|
4
|
+
query: import("@sinclair/typebox").TString;
|
|
5
|
+
category: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"news">, import("@sinclair/typebox").TLiteral<"general">]>>;
|
|
6
|
+
freshness: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"hours">, import("@sinclair/typebox").TLiteral<"day">, import("@sinclair/typebox").TLiteral<"week">, import("@sinclair/typebox").TLiteral<"month">]>>;
|
|
7
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
8
|
+
provider: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"exa">, import("@sinclair/typebox").TLiteral<"brave">, import("@sinclair/typebox").TLiteral<"ddg">]>>;
|
|
9
|
+
}>;
|
|
10
|
+
export declare const webSearchTool: AgentTool<typeof params, WebSearchEnvelope>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { searchWeb } from "../../providers/web-search.js";
|
|
3
|
+
import { hasCredential } from "../../onboarding/providers.js";
|
|
4
|
+
import { buildSoftDegradedTag } from "../../onboarding/tool-tags.js";
|
|
5
|
+
const params = Type.Object({
|
|
6
|
+
query: Type.String({ description: "Search query — ticker, topic, or question" }),
|
|
7
|
+
category: Type.Optional(Type.Union([Type.Literal("news"), Type.Literal("general")], {
|
|
8
|
+
description: 'Search category. "news" for recent articles, "general" for broader web. Default: "news"',
|
|
9
|
+
})),
|
|
10
|
+
freshness: Type.Optional(Type.Union([Type.Literal("hours"), Type.Literal("day"), Type.Literal("week"), Type.Literal("month")], { description: 'Time range filter. Default: "day"' })),
|
|
11
|
+
limit: Type.Optional(Type.Number({ description: "Number of results (1-20). Default: 10", minimum: 1, maximum: 20 })),
|
|
12
|
+
provider: Type.Optional(Type.Union([Type.Literal("exa"), Type.Literal("brave"), Type.Literal("ddg")], {
|
|
13
|
+
description: "Override search provider (skip cascade). Default: auto (Exa → Brave → DDG)",
|
|
14
|
+
})),
|
|
15
|
+
});
|
|
16
|
+
function escapeMd(text) {
|
|
17
|
+
return text.replace(/([[\]|])/g, "\\$1");
|
|
18
|
+
}
|
|
19
|
+
function safeUrl(url) {
|
|
20
|
+
if (url.startsWith("https://") || url.startsWith("http://"))
|
|
21
|
+
return url;
|
|
22
|
+
return `https://${url}`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build soft-degradation tags for search providers whose credentials are
|
|
26
|
+
* missing at call time. Returns an empty string when nothing is degraded, or
|
|
27
|
+
* a newline-terminated block of `[OPENCANDLE_SOFT_DEGRADED ...]` tags ready to
|
|
28
|
+
* prepend to the tool result content. The extension's `tool_result` handler
|
|
29
|
+
* records these into the per-turn degradation accumulator; the system prompt
|
|
30
|
+
* instructs the LLM to surface them in a `**Data gaps**` section.
|
|
31
|
+
*
|
|
32
|
+
* Emission rules:
|
|
33
|
+
* - Brave: if `hasCredential("brave") === false` AND the envelope's
|
|
34
|
+
* provider is not `"brave"`, the cascade fell back from Brave.
|
|
35
|
+
* - Exa: if `hasCredential("exa") === false` AND the envelope's provider
|
|
36
|
+
* is `"exa"`, the Exa provider used the keyless MCP path instead of the
|
|
37
|
+
* keyed API. Envelopes served by `ddg` are NOT tagged for Exa (Exa was
|
|
38
|
+
* tried first and failed for a reason unrelated to credentials).
|
|
39
|
+
*/
|
|
40
|
+
function buildSoftDegradedPrefix(data) {
|
|
41
|
+
const tags = [];
|
|
42
|
+
if (!hasCredential("brave") && data.provider !== "brave") {
|
|
43
|
+
tags.push(buildSoftDegradedTag({
|
|
44
|
+
provider: "brave",
|
|
45
|
+
fallback: data.provider === "exa" ? "exa" : "ddg",
|
|
46
|
+
remediation: "run /connect search to enable Brave",
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
if (!hasCredential("exa") && data.provider === "exa") {
|
|
50
|
+
tags.push(buildSoftDegradedTag({
|
|
51
|
+
provider: "exa",
|
|
52
|
+
fallback: "keyless-mcp",
|
|
53
|
+
remediation: "run /connect search to enable keyed Exa",
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
return tags.length === 0 ? "" : `${tags.join("\n")}\n\n`;
|
|
57
|
+
}
|
|
58
|
+
export const webSearchTool = {
|
|
59
|
+
name: "search_web",
|
|
60
|
+
label: "Web Search",
|
|
61
|
+
description: "Search the web for financial news, earnings context, company events, regulatory developments, or general information. " +
|
|
62
|
+
"NOT for real-time prices, historical data, fundamentals, macro data, SEC filings, or social sentiment — those have dedicated tools.",
|
|
63
|
+
parameters: params,
|
|
64
|
+
async execute(toolCallId, args) {
|
|
65
|
+
const query = args.query?.trim();
|
|
66
|
+
if (!query) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: "⚠ Cannot search with an empty query." }],
|
|
69
|
+
details: null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const category = args.category ?? "news";
|
|
73
|
+
const freshness = args.freshness ?? "day";
|
|
74
|
+
const limit = Math.max(1, Math.min(args.limit ?? 10, 20));
|
|
75
|
+
const provider = args.provider;
|
|
76
|
+
const result = await searchWeb(query, { category, freshness, limit, provider });
|
|
77
|
+
if (result.status === "unavailable") {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: `⚠ Web search unavailable (${result.reason}).` }],
|
|
80
|
+
details: null,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const data = result.data;
|
|
84
|
+
if (data.resultCount === 0) {
|
|
85
|
+
const zeroPrefix = buildSoftDegradedPrefix(data);
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: `${zeroPrefix}No results found for "${query}" (${category}, past ${freshness}).`,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
details: data,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const stalePrefix = result.stale
|
|
97
|
+
? `⚠ Using cached data from ${result.timestamp}\n\n`
|
|
98
|
+
: "";
|
|
99
|
+
const softDegradedPrefix = buildSoftDegradedPrefix(data);
|
|
100
|
+
const header = `**Web Search** — ${data.resultCount} results for "${query}" (${category}, past ${freshness}, via ${data.provider})`;
|
|
101
|
+
const items = data.results.map((r) => {
|
|
102
|
+
const title = escapeMd(r.title);
|
|
103
|
+
const snippet = escapeMd(r.snippet);
|
|
104
|
+
const url = safeUrl(r.url);
|
|
105
|
+
const pub = r.published ? `Published: ${r.published}` : "Published: unknown";
|
|
106
|
+
return `• [${title}](${url}) — ${r.source}\n ${snippet}\n ${pub}`;
|
|
107
|
+
});
|
|
108
|
+
const text = `${softDegradedPrefix}${stalePrefix}${header}\n\n${items.join("\n\n")}`;
|
|
109
|
+
return {
|
|
110
|
+
content: [{ type: "text", text }],
|
|
111
|
+
details: data,
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=web-search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-search.js","sourceRoot":"","sources":["../../../src/tools/sentiment/web-search.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
|
+
declare const params: import("@sinclair/typebox").TObject<{
|
|
3
|
+
query: import("@sinclair/typebox").TString;
|
|
4
|
+
freshness: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"day">, import("@sinclair/typebox").TLiteral<"week">, import("@sinclair/typebox").TLiteral<"month">]>>;
|
|
5
|
+
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const webSentimentTool: AgentTool<typeof params>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { searchWeb } from "../../providers/web-search.js";
|
|
3
|
+
import { WebAdapter } from "../../sentiment/adapters/web.js";
|
|
4
|
+
import { getSentimentPipeline } from "../../sentiment/index.js";
|
|
5
|
+
const params = Type.Object({
|
|
6
|
+
query: Type.String({ description: "Ticker or topic to search for web/news sentiment" }),
|
|
7
|
+
freshness: Type.Optional(Type.Union([Type.Literal("day"), Type.Literal("week"), Type.Literal("month")], {
|
|
8
|
+
description: "Time window for results. Default: day",
|
|
9
|
+
})),
|
|
10
|
+
limit: Type.Optional(Type.Number({ description: "Max results. Default: 10, max: 20" })),
|
|
11
|
+
});
|
|
12
|
+
export const webSentimentTool = {
|
|
13
|
+
name: "get_web_sentiment",
|
|
14
|
+
label: "Web/News Sentiment",
|
|
15
|
+
description: "Analyze sentiment from web and news search results for a ticker or topic. Returns scored results with aggregate sentiment.",
|
|
16
|
+
parameters: params,
|
|
17
|
+
async execute(toolCallId, args) {
|
|
18
|
+
const freshness = args.freshness ?? "day";
|
|
19
|
+
const limit = Math.min(args.limit ?? 10, 20);
|
|
20
|
+
const providerResult = await searchWeb(args.query, { freshness, limit, category: "news" });
|
|
21
|
+
if (providerResult.status === "unavailable") {
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: `⚠ Web sentiment unavailable for "${args.query}" (${providerResult.reason}).` }],
|
|
24
|
+
details: null,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const adapter = new WebAdapter();
|
|
28
|
+
const records = adapter.mapToRecords(providerResult.data, args.query);
|
|
29
|
+
const pipeline = getSentimentPipeline();
|
|
30
|
+
const result = await pipeline.processRecords(records, args.query);
|
|
31
|
+
const lines = [];
|
|
32
|
+
if (result.fresh.length === 0) {
|
|
33
|
+
lines.push(`No web results found for "${args.query}".`);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
const avgScore = result.fresh.reduce((s, r) => s + r.sentiment.score, 0) / result.fresh.length;
|
|
37
|
+
const label = sentimentLabel(avgScore);
|
|
38
|
+
lines.push(`**Web sentiment for "${args.query}"** — ${result.fresh.length} results (${label}, ${avgScore.toFixed(2)})`);
|
|
39
|
+
lines.push("");
|
|
40
|
+
for (const rec of result.fresh.slice(0, limit)) {
|
|
41
|
+
const indicator = rec.sentiment.score > 0 ? "🟢" : rec.sentiment.score < 0 ? "🔴" : "⚪";
|
|
42
|
+
lines.push(`${indicator} [${rec.title}](${rec.url}) — *${rec.author}*`);
|
|
43
|
+
lines.push(` ${rec.text.slice(0, 150)}`);
|
|
44
|
+
lines.push(` Score: ${rec.sentiment.score.toFixed(2)} | Confidence: ${rec.sentiment.confidence.toFixed(2)}`);
|
|
45
|
+
}
|
|
46
|
+
if (result.trend) {
|
|
47
|
+
lines.push("");
|
|
48
|
+
const t = result.trend[0];
|
|
49
|
+
lines.push(`Trend: ${t.sparkline} ${t.direction} (${t.count} records)`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { content: [{ type: "text", text: lines.join("\n") }], details: result };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
function sentimentLabel(score) {
|
|
56
|
+
if (score > 0.3)
|
|
57
|
+
return "Bullish";
|
|
58
|
+
if (score < -0.3)
|
|
59
|
+
return "Bearish";
|
|
60
|
+
if (score > 0)
|
|
61
|
+
return "Leaning Bullish";
|
|
62
|
+
if (score < 0)
|
|
63
|
+
return "Leaning Bearish";
|
|
64
|
+
return "Neutral";
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=web-sentiment.js.map
|