vibesurf 0.1.23__py3-none-any.whl → 0.1.24__py3-none-any.whl
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.
Potentially problematic release.
This version of vibesurf might be problematic. Click here for more details.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/vibe_surf_agent.py +1 -1
- vibe_surf/backend/api/config.py +3 -1
- vibe_surf/backend/utils/llm_factory.py +2 -1
- vibe_surf/llm/openai_compatible.py +35 -10
- vibe_surf/tools/file_system.py +2 -2
- vibe_surf/tools/finance_tools.py +586 -0
- vibe_surf/tools/report_writer_tools.py +2 -1
- vibe_surf/tools/vibesurf_tools.py +91 -2
- vibe_surf/tools/views.py +33 -0
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/METADATA +2 -1
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/RECORD +16 -15
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.23.dist-info → vibesurf-0.1.24.dist-info}/top_level.txt +0 -0
vibe_surf/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.24'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 24)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -479,7 +479,7 @@ async def _vibesurf_agent_node_impl(state: VibeSurfState) -> VibeSurfState:
|
|
|
479
479
|
llm=vibesurf_agent.llm,
|
|
480
480
|
file_system=vibesurf_agent.file_system,
|
|
481
481
|
)
|
|
482
|
-
if "skill_search"
|
|
482
|
+
if action_name in ["skill_search", "skill_crawl", "skill_summary", "skill_finance"]:
|
|
483
483
|
state.current_step = "END"
|
|
484
484
|
# Format final response
|
|
485
485
|
final_response = f"{result.extracted_content}"
|
vibe_surf/backend/api/config.py
CHANGED
|
@@ -234,7 +234,9 @@ async def update_llm_profile(
|
|
|
234
234
|
|
|
235
235
|
# Return updated profile
|
|
236
236
|
updated_profile = await LLMProfileQueries.get_profile(db, profile_name)
|
|
237
|
-
|
|
237
|
+
from ..shared_state import current_llm_profile_name
|
|
238
|
+
if current_llm_profile_name != profile_name:
|
|
239
|
+
current_llm_profile_name = None
|
|
238
240
|
# Use safe extraction to avoid greenlet issues
|
|
239
241
|
return LLMProfileResponse(**_profile_to_response_dict(updated_profile))
|
|
240
242
|
|
|
@@ -142,8 +142,9 @@ def create_llm_from_profile(llm_profile) -> BaseChatModel:
|
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
elif provider == "deepseek":
|
|
145
|
-
return
|
|
145
|
+
return ChatOpenAICompatible(
|
|
146
146
|
model=model,
|
|
147
|
+
base_url="https://api.deepseek.com",
|
|
147
148
|
api_key=api_key,
|
|
148
149
|
**common_params
|
|
149
150
|
)
|
|
@@ -51,6 +51,8 @@ from browser_use.llm.openai.serializer import OpenAIMessageSerializer
|
|
|
51
51
|
from browser_use.llm.schema import SchemaOptimizer
|
|
52
52
|
from browser_use.llm.views import ChatInvokeCompletion, ChatInvokeUsage
|
|
53
53
|
|
|
54
|
+
from json_repair import repair_json
|
|
55
|
+
|
|
54
56
|
T = TypeVar('T', bound=BaseModel)
|
|
55
57
|
|
|
56
58
|
from vibe_surf.logger import get_logger
|
|
@@ -74,6 +76,8 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
74
76
|
The class automatically detects the model type and applies appropriate fixes.
|
|
75
77
|
"""
|
|
76
78
|
|
|
79
|
+
max_completion_tokens: int | None = 16000
|
|
80
|
+
|
|
77
81
|
def _is_gemini_model(self) -> bool:
|
|
78
82
|
"""Check if the current model is a Gemini model."""
|
|
79
83
|
return str(self.model).lower().startswith('gemini')
|
|
@@ -82,6 +86,11 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
82
86
|
"""Check if the current model is a Kimi/Moonshot model."""
|
|
83
87
|
model_str = str(self.model).lower()
|
|
84
88
|
return 'kimi' in model_str or 'moonshot' in model_str
|
|
89
|
+
|
|
90
|
+
def _is_deepseek_model(self) -> bool:
|
|
91
|
+
"""Check if the current model is a Kimi/Moonshot model."""
|
|
92
|
+
model_str = str(self.model).lower()
|
|
93
|
+
return 'deepseek' in model_str
|
|
85
94
|
|
|
86
95
|
def _is_qwen_model(self) -> bool:
|
|
87
96
|
"""Check if the current model is a Qwen model."""
|
|
@@ -223,10 +232,10 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
223
232
|
"""
|
|
224
233
|
# If this is not a special model or no structured output is requested,
|
|
225
234
|
# use the parent implementation directly
|
|
226
|
-
if self._is_qwen_model() or self._is_kimi_model():
|
|
235
|
+
if self._is_qwen_model() or self._is_kimi_model() or self._is_deepseek_model() :
|
|
227
236
|
self.add_schema_to_system_prompt = True
|
|
228
237
|
|
|
229
|
-
if not (self._is_gemini_model() or self._is_kimi_model() or self._is_qwen_model()) or output_format is None:
|
|
238
|
+
if not (self._is_gemini_model() or self._is_kimi_model() or self._is_qwen_model() or self._is_deepseek_model()) or output_format is None:
|
|
230
239
|
return await super().ainvoke(messages, output_format)
|
|
231
240
|
openai_messages = OpenAIMessageSerializer.serialize_messages(messages)
|
|
232
241
|
|
|
@@ -241,6 +250,7 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
241
250
|
|
|
242
251
|
if self.max_completion_tokens is not None:
|
|
243
252
|
model_params['max_completion_tokens'] = self.max_completion_tokens
|
|
253
|
+
model_params['max_tokens'] = self.max_completion_tokens
|
|
244
254
|
|
|
245
255
|
if self.top_p is not None:
|
|
246
256
|
model_params['top_p'] = self.top_p
|
|
@@ -298,12 +308,22 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
298
308
|
]
|
|
299
309
|
|
|
300
310
|
# Return structured response
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
311
|
+
if self.add_schema_to_system_prompt:
|
|
312
|
+
response = await self.get_client().chat.completions.create(
|
|
313
|
+
model=self.model,
|
|
314
|
+
messages=openai_messages,
|
|
315
|
+
response_format={
|
|
316
|
+
'type': 'json_object'
|
|
317
|
+
},
|
|
318
|
+
**model_params,
|
|
319
|
+
)
|
|
320
|
+
else:
|
|
321
|
+
response = await self.get_client().chat.completions.create(
|
|
322
|
+
model=self.model,
|
|
323
|
+
messages=openai_messages,
|
|
324
|
+
response_format=ResponseFormatJSONSchema(json_schema=response_format, type='json_schema'),
|
|
325
|
+
**model_params,
|
|
326
|
+
)
|
|
307
327
|
|
|
308
328
|
if response.choices[0].message.content is None:
|
|
309
329
|
raise ModelProviderError(
|
|
@@ -313,8 +333,13 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
313
333
|
)
|
|
314
334
|
|
|
315
335
|
usage = self._get_usage(response)
|
|
316
|
-
|
|
317
|
-
|
|
336
|
+
output_content = response.choices[0].message.content
|
|
337
|
+
try:
|
|
338
|
+
parsed = output_format.model_validate_json(output_content)
|
|
339
|
+
except Exception as e:
|
|
340
|
+
pdb.set_trace()
|
|
341
|
+
repair_content = repair_json(output_content)
|
|
342
|
+
parsed = output_format.model_validate_json(repair_content)
|
|
318
343
|
|
|
319
344
|
return ChatInvokeCompletion(
|
|
320
345
|
completion=parsed,
|
vibe_surf/tools/file_system.py
CHANGED
|
@@ -224,9 +224,9 @@ class CustomFileSystem(FileSystem):
|
|
|
224
224
|
def _is_valid_filename(self, file_name: str) -> bool:
|
|
225
225
|
"""Check if filename matches the required pattern: name.extension"""
|
|
226
226
|
# Build extensions pattern from _file_types
|
|
227
|
-
file_name = os.path.
|
|
227
|
+
file_name = os.path.splitext(file_name)[1]
|
|
228
228
|
extensions = '|'.join(self._file_types.keys())
|
|
229
|
-
pattern = rf'
|
|
229
|
+
pattern = rf'\.({extensions})$'
|
|
230
230
|
return bool(re.match(pattern, file_name))
|
|
231
231
|
|
|
232
232
|
async def append_file(self, full_filename: str, content: str) -> str:
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive finance tools using Yahoo Finance API.
|
|
3
|
+
Provides access to stock market data, company financials, and trading information.
|
|
4
|
+
"""
|
|
5
|
+
import pdb
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Dict, List, Any, Optional, Union
|
|
8
|
+
from datetime import datetime, timedelta
|
|
9
|
+
import yfinance as yf
|
|
10
|
+
import pandas as pd
|
|
11
|
+
from vibe_surf.logger import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FinanceMethod(Enum):
|
|
17
|
+
"""Available Yahoo Finance data methods"""
|
|
18
|
+
# Basic Information
|
|
19
|
+
GET_INFO = "get_info" # Company basic information
|
|
20
|
+
GET_FAST_INFO = "get_fast_info" # Quick stats like current price, volume
|
|
21
|
+
|
|
22
|
+
# Market Data & History
|
|
23
|
+
GET_HISTORY = "get_history" # Historical price and volume data
|
|
24
|
+
GET_ACTIONS = "get_actions" # Dividends and stock splits
|
|
25
|
+
GET_DIVIDENDS = "get_dividends" # Dividend history
|
|
26
|
+
GET_SPLITS = "get_splits" # Stock split history
|
|
27
|
+
GET_CAPITAL_GAINS = "get_capital_gains" # Capital gains distributions
|
|
28
|
+
|
|
29
|
+
# Financial Statements
|
|
30
|
+
GET_FINANCIALS = "get_financials" # Income statement (annual)
|
|
31
|
+
GET_QUARTERLY_FINANCIALS = "get_quarterly_financials" # Income statement (quarterly)
|
|
32
|
+
GET_BALANCE_SHEET = "get_balance_sheet" # Balance sheet (annual)
|
|
33
|
+
GET_QUARTERLY_BALANCE_SHEET = "get_quarterly_balance_sheet" # Balance sheet (quarterly)
|
|
34
|
+
GET_CASHFLOW = "get_cashflow" # Cash flow statement (annual)
|
|
35
|
+
GET_QUARTERLY_CASHFLOW = "get_quarterly_cashflow" # Cash flow statement (quarterly)
|
|
36
|
+
|
|
37
|
+
# Earnings & Analysis
|
|
38
|
+
GET_EARNINGS = "get_earnings" # Historical earnings data
|
|
39
|
+
GET_QUARTERLY_EARNINGS = "get_quarterly_earnings" # Quarterly earnings
|
|
40
|
+
GET_EARNINGS_DATES = "get_earnings_dates" # Upcoming earnings dates
|
|
41
|
+
GET_CALENDAR = "get_calendar" # Earnings calendar
|
|
42
|
+
|
|
43
|
+
# Recommendations & Analysis
|
|
44
|
+
GET_RECOMMENDATIONS = "get_recommendations" # Analyst recommendations
|
|
45
|
+
GET_RECOMMENDATIONS_SUMMARY = "get_recommendations_summary" # Summary of recommendations
|
|
46
|
+
GET_UPGRADES_DOWNGRADES = "get_upgrades_downgrades" # Rating changes
|
|
47
|
+
GET_ANALYSIS = "get_analysis" # Analyst analysis
|
|
48
|
+
|
|
49
|
+
# Ownership & Holdings
|
|
50
|
+
GET_MAJOR_HOLDERS = "get_major_holders" # Major shareholders
|
|
51
|
+
GET_INSTITUTIONAL_HOLDERS = "get_institutional_holders" # Institutional holdings
|
|
52
|
+
GET_MUTUALFUND_HOLDERS = "get_mutualfund_holders" # Mutual fund holdings
|
|
53
|
+
GET_INSIDER_PURCHASES = "get_insider_purchases" # Insider purchases
|
|
54
|
+
GET_INSIDER_TRANSACTIONS = "get_insider_transactions" # Insider transactions
|
|
55
|
+
GET_INSIDER_ROSTER_HOLDERS = "get_insider_roster_holders" # Insider roster
|
|
56
|
+
|
|
57
|
+
# Additional Data
|
|
58
|
+
GET_NEWS = "get_news" # Latest news
|
|
59
|
+
GET_SUSTAINABILITY = "get_sustainability" # ESG scores
|
|
60
|
+
GET_SEC_FILINGS = "get_sec_filings" # SEC filings
|
|
61
|
+
GET_SHARES = "get_shares" # Share count data
|
|
62
|
+
|
|
63
|
+
# Options (if applicable)
|
|
64
|
+
GET_OPTIONS = "get_options" # Option chain data
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FinanceDataRetriever:
|
|
68
|
+
"""Main class for retrieving and formatting Yahoo Finance data"""
|
|
69
|
+
|
|
70
|
+
def __init__(self, symbol: str):
|
|
71
|
+
"""Initialize with stock symbol"""
|
|
72
|
+
self.symbol = symbol.upper()
|
|
73
|
+
self.ticker = yf.Ticker(self.symbol)
|
|
74
|
+
|
|
75
|
+
def get_finance_data(self, methods: List[str], **kwargs) -> Dict[str, Any]:
|
|
76
|
+
"""
|
|
77
|
+
Retrieve finance data using specified methods
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
methods: List of method names (FinanceMethod enum values)
|
|
81
|
+
**kwargs: Additional parameters (e.g., period, start_date, end_date, num_news)
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Dictionary with method names as keys and data as values
|
|
85
|
+
"""
|
|
86
|
+
results = {}
|
|
87
|
+
|
|
88
|
+
for method in methods:
|
|
89
|
+
try:
|
|
90
|
+
if hasattr(self, f"_{method}"):
|
|
91
|
+
method_func = getattr(self, f"_{method}")
|
|
92
|
+
results[method] = method_func(**kwargs)
|
|
93
|
+
else:
|
|
94
|
+
results[method] = f"Error: Method {method} not implemented"
|
|
95
|
+
logger.warning(f"Method {method} not implemented for {self.symbol}")
|
|
96
|
+
except Exception as e:
|
|
97
|
+
error_msg = f"Error retrieving {method}: {str(e)}"
|
|
98
|
+
results[method] = error_msg
|
|
99
|
+
logger.error(f"Error retrieving {method} for {self.symbol}: {e}")
|
|
100
|
+
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
# Basic Information Methods
|
|
104
|
+
def _get_info(self, **kwargs) -> Dict:
|
|
105
|
+
"""Get basic company information"""
|
|
106
|
+
return self.ticker.info
|
|
107
|
+
|
|
108
|
+
def _get_fast_info(self, **kwargs) -> Dict:
|
|
109
|
+
"""Get quick statistics"""
|
|
110
|
+
try:
|
|
111
|
+
fast_info = self.ticker.fast_info
|
|
112
|
+
return dict(fast_info) if hasattr(fast_info, '__dict__') else fast_info
|
|
113
|
+
except:
|
|
114
|
+
return self.ticker.get_fast_info()
|
|
115
|
+
|
|
116
|
+
# Market Data & History Methods
|
|
117
|
+
def _get_history(self, **kwargs) -> pd.DataFrame:
|
|
118
|
+
"""Get historical price and volume data"""
|
|
119
|
+
period = kwargs.get('period', '1y')
|
|
120
|
+
start_date = kwargs.get('start_date')
|
|
121
|
+
end_date = kwargs.get('end_date')
|
|
122
|
+
interval = kwargs.get('interval', '1d')
|
|
123
|
+
|
|
124
|
+
if start_date and end_date:
|
|
125
|
+
return self.ticker.history(start=start_date, end=end_date, interval=interval)
|
|
126
|
+
else:
|
|
127
|
+
return self.ticker.history(period=period, interval=interval)
|
|
128
|
+
|
|
129
|
+
def _get_actions(self, **kwargs) -> pd.DataFrame:
|
|
130
|
+
"""Get dividend and stock split history"""
|
|
131
|
+
return self.ticker.actions
|
|
132
|
+
|
|
133
|
+
def _get_dividends(self, **kwargs) -> pd.Series:
|
|
134
|
+
"""Get dividend history"""
|
|
135
|
+
return self.ticker.dividends
|
|
136
|
+
|
|
137
|
+
def _get_splits(self, **kwargs) -> pd.Series:
|
|
138
|
+
"""Get stock split history"""
|
|
139
|
+
return self.ticker.splits
|
|
140
|
+
|
|
141
|
+
def _get_capital_gains(self, **kwargs) -> pd.Series:
|
|
142
|
+
"""Get capital gains distributions"""
|
|
143
|
+
return self.ticker.capital_gains
|
|
144
|
+
|
|
145
|
+
# Financial Statements Methods
|
|
146
|
+
def _get_financials(self, **kwargs) -> pd.DataFrame:
|
|
147
|
+
"""Get annual income statement"""
|
|
148
|
+
return self.ticker.financials
|
|
149
|
+
|
|
150
|
+
def _get_quarterly_financials(self, **kwargs) -> pd.DataFrame:
|
|
151
|
+
"""Get quarterly income statement"""
|
|
152
|
+
return self.ticker.quarterly_financials
|
|
153
|
+
|
|
154
|
+
def _get_balance_sheet(self, **kwargs) -> pd.DataFrame:
|
|
155
|
+
"""Get annual balance sheet"""
|
|
156
|
+
return self.ticker.balance_sheet
|
|
157
|
+
|
|
158
|
+
def _get_quarterly_balance_sheet(self, **kwargs) -> pd.DataFrame:
|
|
159
|
+
"""Get quarterly balance sheet"""
|
|
160
|
+
return self.ticker.quarterly_balance_sheet
|
|
161
|
+
|
|
162
|
+
def _get_cashflow(self, **kwargs) -> pd.DataFrame:
|
|
163
|
+
"""Get annual cash flow statement"""
|
|
164
|
+
return self.ticker.cashflow
|
|
165
|
+
|
|
166
|
+
def _get_quarterly_cashflow(self, **kwargs) -> pd.DataFrame:
|
|
167
|
+
"""Get quarterly cash flow statement"""
|
|
168
|
+
return self.ticker.quarterly_cashflow
|
|
169
|
+
|
|
170
|
+
# Earnings & Analysis Methods
|
|
171
|
+
def _get_earnings(self, **kwargs) -> pd.DataFrame:
|
|
172
|
+
"""Get historical earnings data"""
|
|
173
|
+
return self.ticker.earnings
|
|
174
|
+
|
|
175
|
+
def _get_quarterly_earnings(self, **kwargs) -> pd.DataFrame:
|
|
176
|
+
"""Get quarterly earnings data"""
|
|
177
|
+
return self.ticker.quarterly_earnings
|
|
178
|
+
|
|
179
|
+
def _get_earnings_dates(self, **kwargs) -> pd.DataFrame:
|
|
180
|
+
"""Get earnings dates and estimates"""
|
|
181
|
+
return self.ticker.earnings_dates
|
|
182
|
+
|
|
183
|
+
def _get_calendar(self, **kwargs) -> Dict:
|
|
184
|
+
"""Get earnings calendar"""
|
|
185
|
+
return self.ticker.calendar
|
|
186
|
+
|
|
187
|
+
# Recommendations & Analysis Methods
|
|
188
|
+
def _get_recommendations(self, **kwargs) -> pd.DataFrame:
|
|
189
|
+
"""Get analyst recommendations history"""
|
|
190
|
+
return self.ticker.recommendations
|
|
191
|
+
|
|
192
|
+
def _get_recommendations_summary(self, **kwargs) -> pd.DataFrame:
|
|
193
|
+
"""Get summary of analyst recommendations"""
|
|
194
|
+
return self.ticker.recommendations_summary
|
|
195
|
+
|
|
196
|
+
def _get_upgrades_downgrades(self, **kwargs) -> pd.DataFrame:
|
|
197
|
+
"""Get analyst upgrades and downgrades"""
|
|
198
|
+
return self.ticker.upgrades_downgrades
|
|
199
|
+
|
|
200
|
+
def _get_analysis(self, **kwargs) -> pd.DataFrame:
|
|
201
|
+
"""Get analyst analysis"""
|
|
202
|
+
return getattr(self.ticker, 'analysis', pd.DataFrame())
|
|
203
|
+
|
|
204
|
+
# Ownership & Holdings Methods
|
|
205
|
+
def _get_major_holders(self, **kwargs) -> pd.DataFrame:
|
|
206
|
+
"""Get major shareholders"""
|
|
207
|
+
return self.ticker.major_holders
|
|
208
|
+
|
|
209
|
+
def _get_institutional_holders(self, **kwargs) -> pd.DataFrame:
|
|
210
|
+
"""Get institutional holdings"""
|
|
211
|
+
return self.ticker.institutional_holders
|
|
212
|
+
|
|
213
|
+
def _get_mutualfund_holders(self, **kwargs) -> pd.DataFrame:
|
|
214
|
+
"""Get mutual fund holdings"""
|
|
215
|
+
return self.ticker.mutualfund_holders
|
|
216
|
+
|
|
217
|
+
def _get_insider_purchases(self, **kwargs) -> pd.DataFrame:
|
|
218
|
+
"""Get insider purchases"""
|
|
219
|
+
return getattr(self.ticker, 'insider_purchases', pd.DataFrame())
|
|
220
|
+
|
|
221
|
+
def _get_insider_transactions(self, **kwargs) -> pd.DataFrame:
|
|
222
|
+
"""Get insider transactions"""
|
|
223
|
+
return getattr(self.ticker, 'insider_transactions', pd.DataFrame())
|
|
224
|
+
|
|
225
|
+
def _get_insider_roster_holders(self, **kwargs) -> pd.DataFrame:
|
|
226
|
+
"""Get insider roster"""
|
|
227
|
+
return getattr(self.ticker, 'insider_roster_holders', pd.DataFrame())
|
|
228
|
+
|
|
229
|
+
# Additional Data Methods
|
|
230
|
+
def _get_news(self, **kwargs) -> List[Dict]:
|
|
231
|
+
"""Get latest news"""
|
|
232
|
+
num_news = kwargs.get('num_news', 5)
|
|
233
|
+
news = self.ticker.news
|
|
234
|
+
return news[:num_news] if news else []
|
|
235
|
+
|
|
236
|
+
def _get_sustainability(self, **kwargs) -> pd.DataFrame:
|
|
237
|
+
"""Get ESG sustainability data"""
|
|
238
|
+
return getattr(self.ticker, 'sustainability', pd.DataFrame())
|
|
239
|
+
|
|
240
|
+
def _get_sec_filings(self, **kwargs) -> pd.DataFrame:
|
|
241
|
+
"""Get SEC filings"""
|
|
242
|
+
return getattr(self.ticker, 'sec_filings', pd.DataFrame())
|
|
243
|
+
|
|
244
|
+
def _get_shares(self, **kwargs) -> pd.DataFrame:
|
|
245
|
+
"""Get share count data"""
|
|
246
|
+
return getattr(self.ticker, 'shares', pd.DataFrame())
|
|
247
|
+
|
|
248
|
+
def _get_options(self, **kwargs) -> Dict:
|
|
249
|
+
"""Get options data"""
|
|
250
|
+
try:
|
|
251
|
+
option_dates = self.ticker.options
|
|
252
|
+
if option_dates:
|
|
253
|
+
# Get the first available expiration date
|
|
254
|
+
first_expiry = option_dates[0]
|
|
255
|
+
opt_chain = self.ticker.option_chain(first_expiry)
|
|
256
|
+
return {
|
|
257
|
+
'expiration_dates': list(option_dates),
|
|
258
|
+
'calls': opt_chain.calls,
|
|
259
|
+
'puts': opt_chain.puts,
|
|
260
|
+
'selected_expiry': first_expiry
|
|
261
|
+
}
|
|
262
|
+
return {'expiration_dates': [], 'calls': pd.DataFrame(), 'puts': pd.DataFrame()}
|
|
263
|
+
except:
|
|
264
|
+
return {'error': 'Options data not available for this ticker'}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class FinanceMarkdownFormatter:
|
|
268
|
+
"""Formats finance data into markdown"""
|
|
269
|
+
|
|
270
|
+
@staticmethod
|
|
271
|
+
def format_finance_data(symbol: str, results: Dict[str, Any], methods: List[str]) -> str:
|
|
272
|
+
"""Format all finance data as markdown"""
|
|
273
|
+
markdown = f"# 💹 Financial Data for {symbol.upper()}\n\n"
|
|
274
|
+
|
|
275
|
+
for method in methods:
|
|
276
|
+
data = results.get(method)
|
|
277
|
+
|
|
278
|
+
if isinstance(data, str) and data.startswith('Error'):
|
|
279
|
+
markdown += f"## ❌ {method.replace('_', ' ').title()}\n{data}\n\n"
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
markdown += f"## 📊 {method.replace('_', ' ').title()}\n\n"
|
|
283
|
+
|
|
284
|
+
# Route to appropriate formatter
|
|
285
|
+
formatter_method = f"_format_{method}"
|
|
286
|
+
if hasattr(FinanceMarkdownFormatter, formatter_method):
|
|
287
|
+
formatter = getattr(FinanceMarkdownFormatter, formatter_method)
|
|
288
|
+
markdown += formatter(data)
|
|
289
|
+
else:
|
|
290
|
+
# Generic formatter for unhandled methods
|
|
291
|
+
markdown += FinanceMarkdownFormatter._format_generic(data)
|
|
292
|
+
|
|
293
|
+
markdown += "\n\n"
|
|
294
|
+
|
|
295
|
+
return markdown.strip()
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _format_generic(data: Any) -> str:
|
|
299
|
+
"""Generic formatter for any data type"""
|
|
300
|
+
if data is None or (hasattr(data, 'empty') and data.empty):
|
|
301
|
+
return "No data available.\n"
|
|
302
|
+
|
|
303
|
+
if isinstance(data, pd.DataFrame):
|
|
304
|
+
if len(data) == 0:
|
|
305
|
+
return "No data available.\n"
|
|
306
|
+
return f"```\n{data.to_string()}\n```\n"
|
|
307
|
+
elif isinstance(data, pd.Series):
|
|
308
|
+
if len(data) == 0:
|
|
309
|
+
return "No data available.\n"
|
|
310
|
+
return f"```\n{data.to_string()}\n```\n"
|
|
311
|
+
elif isinstance(data, (list, dict)):
|
|
312
|
+
import json
|
|
313
|
+
return f"```json\n{json.dumps(data, indent=2, default=str)}\n```\n"
|
|
314
|
+
else:
|
|
315
|
+
return f"```\n{str(data)}\n```\n"
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _format_get_info(info: Dict) -> str:
|
|
319
|
+
"""Format company info as markdown"""
|
|
320
|
+
if not info:
|
|
321
|
+
return "No company information available.\n"
|
|
322
|
+
|
|
323
|
+
markdown = ""
|
|
324
|
+
|
|
325
|
+
# Basic company info
|
|
326
|
+
if 'longName' in info:
|
|
327
|
+
markdown += f"**Company Name:** {info['longName']}\n"
|
|
328
|
+
if 'sector' in info:
|
|
329
|
+
markdown += f"**Sector:** {info['sector']}\n"
|
|
330
|
+
if 'industry' in info:
|
|
331
|
+
markdown += f"**Industry:** {info['industry']}\n"
|
|
332
|
+
if 'website' in info:
|
|
333
|
+
markdown += f"**Website:** {info['website']}\n"
|
|
334
|
+
if 'country' in info:
|
|
335
|
+
markdown += f"**Country:** {info['country']}\n"
|
|
336
|
+
|
|
337
|
+
markdown += "\n### 💰 Financial Metrics\n"
|
|
338
|
+
|
|
339
|
+
# Financial metrics
|
|
340
|
+
if 'marketCap' in info and info['marketCap']:
|
|
341
|
+
markdown += f"**Market Cap:** ${info['marketCap']:,.0f}\n"
|
|
342
|
+
if 'enterpriseValue' in info and info['enterpriseValue']:
|
|
343
|
+
markdown += f"**Enterprise Value:** ${info['enterpriseValue']:,.0f}\n"
|
|
344
|
+
if 'totalRevenue' in info and info['totalRevenue']:
|
|
345
|
+
markdown += f"**Total Revenue:** ${info['totalRevenue']:,.0f}\n"
|
|
346
|
+
if 'grossMargins' in info and info['grossMargins']:
|
|
347
|
+
markdown += f"**Gross Margin:** {info['grossMargins']:.2%}\n"
|
|
348
|
+
if 'profitMargins' in info and info['profitMargins']:
|
|
349
|
+
markdown += f"**Profit Margin:** {info['profitMargins']:.2%}\n"
|
|
350
|
+
|
|
351
|
+
markdown += "\n### 📈 Stock Price Info\n"
|
|
352
|
+
|
|
353
|
+
# Stock price info
|
|
354
|
+
if 'currentPrice' in info and info['currentPrice']:
|
|
355
|
+
markdown += f"**Current Price:** ${info['currentPrice']:.2f}\n"
|
|
356
|
+
if 'previousClose' in info and info['previousClose']:
|
|
357
|
+
markdown += f"**Previous Close:** ${info['previousClose']:.2f}\n"
|
|
358
|
+
if 'fiftyTwoWeekHigh' in info and info['fiftyTwoWeekHigh']:
|
|
359
|
+
markdown += f"**52 Week High:** ${info['fiftyTwoWeekHigh']:.2f}\n"
|
|
360
|
+
if 'fiftyTwoWeekLow' in info and info['fiftyTwoWeekLow']:
|
|
361
|
+
markdown += f"**52 Week Low:** ${info['fiftyTwoWeekLow']:.2f}\n"
|
|
362
|
+
if 'dividendYield' in info and info['dividendYield']:
|
|
363
|
+
markdown += f"**Dividend Yield:** {info['dividendYield']:.2%}\n"
|
|
364
|
+
|
|
365
|
+
# Business summary
|
|
366
|
+
if 'longBusinessSummary' in info:
|
|
367
|
+
summary = info['longBusinessSummary'][:500]
|
|
368
|
+
if len(info['longBusinessSummary']) > 500:
|
|
369
|
+
summary += "..."
|
|
370
|
+
markdown += f"\n### 📋 Business Summary\n{summary}\n"
|
|
371
|
+
|
|
372
|
+
return markdown
|
|
373
|
+
|
|
374
|
+
@staticmethod
|
|
375
|
+
def _format_get_fast_info(fast_info) -> str:
|
|
376
|
+
"""Format fast info as markdown"""
|
|
377
|
+
if not fast_info:
|
|
378
|
+
return "No fast info available.\n"
|
|
379
|
+
|
|
380
|
+
markdown = ""
|
|
381
|
+
|
|
382
|
+
# Convert to dict if needed
|
|
383
|
+
if hasattr(fast_info, '__dict__'):
|
|
384
|
+
data = fast_info.__dict__
|
|
385
|
+
elif isinstance(fast_info, dict):
|
|
386
|
+
data = fast_info
|
|
387
|
+
else:
|
|
388
|
+
return f"Fast info data: {str(fast_info)}\n"
|
|
389
|
+
|
|
390
|
+
# Format key metrics
|
|
391
|
+
for key, value in data.items():
|
|
392
|
+
if value is not None:
|
|
393
|
+
key_formatted = key.replace('_', ' ').title()
|
|
394
|
+
if isinstance(value, (int, float)):
|
|
395
|
+
if 'price' in key.lower() or 'value' in key.lower():
|
|
396
|
+
markdown += f"**{key_formatted}:** ${value:,.2f}\n"
|
|
397
|
+
elif 'volume' in key.lower():
|
|
398
|
+
markdown += f"**{key_formatted}:** {value:,}\n"
|
|
399
|
+
else:
|
|
400
|
+
markdown += f"**{key_formatted}:** {value}\n"
|
|
401
|
+
else:
|
|
402
|
+
markdown += f"**{key_formatted}:** {value}\n"
|
|
403
|
+
|
|
404
|
+
return markdown
|
|
405
|
+
|
|
406
|
+
@staticmethod
|
|
407
|
+
def _format_get_history(history: pd.DataFrame) -> str:
|
|
408
|
+
"""Format historical data as markdown"""
|
|
409
|
+
if history.empty:
|
|
410
|
+
return "No historical data available.\n"
|
|
411
|
+
|
|
412
|
+
markdown = f"**Period:** {history.index.min().strftime('%Y-%m-%d')} to {history.index.max().strftime('%Y-%m-%d')}\n"
|
|
413
|
+
markdown += f"**Total Records:** {len(history)}\n\n"
|
|
414
|
+
|
|
415
|
+
# Determine how much data to show based on total records
|
|
416
|
+
total_records = len(history)
|
|
417
|
+
if total_records <= 30:
|
|
418
|
+
# Show all data if 30 records or less
|
|
419
|
+
display_data = history
|
|
420
|
+
markdown += f"### 📈 Historical Data (All {total_records} Records)\n\n"
|
|
421
|
+
else:
|
|
422
|
+
# Show recent 30 records for larger datasets
|
|
423
|
+
display_data = history.tail(30)
|
|
424
|
+
markdown += f"### 📈 Recent Data (Last 30 Records)\n\n"
|
|
425
|
+
|
|
426
|
+
markdown += "| Date | Open | High | Low | Close | Volume |\n"
|
|
427
|
+
markdown += "|------|------|------|-----|-------|--------|\n"
|
|
428
|
+
|
|
429
|
+
for date, row in display_data.iterrows():
|
|
430
|
+
markdown += f"| {date.strftime('%Y-%m-%d')} | ${row['Open']:.2f} | ${row['High']:.2f} | ${row['Low']:.2f} | ${row['Close']:.2f} | {row['Volume']:,} |\n"
|
|
431
|
+
|
|
432
|
+
# Summary statistics
|
|
433
|
+
markdown += "\n### 📊 Summary Statistics\n"
|
|
434
|
+
markdown += f"**Highest Price:** ${history['High'].max():.2f}\n"
|
|
435
|
+
markdown += f"**Lowest Price:** ${history['Low'].min():.2f}\n"
|
|
436
|
+
markdown += f"**Average Volume:** {history['Volume'].mean():,.0f}\n"
|
|
437
|
+
markdown += f"**Total Volume:** {history['Volume'].sum():,}\n"
|
|
438
|
+
|
|
439
|
+
return markdown
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def _format_get_news(news: List[Dict]) -> str:
|
|
443
|
+
"""Format news data as markdown"""
|
|
444
|
+
if not news:
|
|
445
|
+
return "No news available.\n"
|
|
446
|
+
|
|
447
|
+
markdown = f"**Total News Articles:** {len(news)}\n\n"
|
|
448
|
+
pdb.set_trace()
|
|
449
|
+
for i, article in enumerate(news, 1):
|
|
450
|
+
if isinstance(article, dict):
|
|
451
|
+
# Try different possible field names for title
|
|
452
|
+
title = (article.get('title') or
|
|
453
|
+
article.get('headline') or
|
|
454
|
+
article.get('summary') or
|
|
455
|
+
'No title available')
|
|
456
|
+
|
|
457
|
+
# Try different possible field names for link/URL
|
|
458
|
+
link = (article.get('link') or
|
|
459
|
+
article.get('url') or
|
|
460
|
+
article.get('guid') or '')
|
|
461
|
+
|
|
462
|
+
# Try different possible field names for publisher
|
|
463
|
+
publisher = (article.get('publisher') or
|
|
464
|
+
article.get('source') or
|
|
465
|
+
article.get('author') or
|
|
466
|
+
'Unknown')
|
|
467
|
+
|
|
468
|
+
# Try different possible field names for timestamp
|
|
469
|
+
publish_time = (article.get('providerPublishTime') or
|
|
470
|
+
article.get('timestamp') or
|
|
471
|
+
article.get('pubDate') or
|
|
472
|
+
article.get('published') or '')
|
|
473
|
+
|
|
474
|
+
markdown += f"### {i}. {title}\n"
|
|
475
|
+
markdown += f"**Publisher:** {publisher}\n"
|
|
476
|
+
|
|
477
|
+
if publish_time:
|
|
478
|
+
try:
|
|
479
|
+
# Handle different timestamp formats
|
|
480
|
+
if isinstance(publish_time, (int, float)):
|
|
481
|
+
dt = datetime.fromtimestamp(publish_time)
|
|
482
|
+
markdown += f"**Published:** {dt.strftime('%Y-%m-%d %H:%M')}\n"
|
|
483
|
+
elif isinstance(publish_time, str):
|
|
484
|
+
# Try to parse string timestamp
|
|
485
|
+
try:
|
|
486
|
+
publish_time_int = int(float(publish_time))
|
|
487
|
+
dt = datetime.fromtimestamp(publish_time_int)
|
|
488
|
+
markdown += f"**Published:** {dt.strftime('%Y-%m-%d %H:%M')}\n"
|
|
489
|
+
except:
|
|
490
|
+
markdown += f"**Published:** {publish_time}\n"
|
|
491
|
+
except Exception as e:
|
|
492
|
+
# If timestamp parsing fails, show raw value
|
|
493
|
+
markdown += f"**Published:** {publish_time}\n"
|
|
494
|
+
|
|
495
|
+
if link:
|
|
496
|
+
markdown += f"**Link:** {link}\n"
|
|
497
|
+
|
|
498
|
+
# Add summary or description if available
|
|
499
|
+
summary = (article.get('summary') or
|
|
500
|
+
article.get('description') or
|
|
501
|
+
article.get('snippet') or '')
|
|
502
|
+
if summary and summary != title:
|
|
503
|
+
# Limit summary length
|
|
504
|
+
if len(summary) > 200:
|
|
505
|
+
summary = summary[:200] + "..."
|
|
506
|
+
markdown += f"**Summary:** {summary}\n"
|
|
507
|
+
|
|
508
|
+
markdown += "\n"
|
|
509
|
+
|
|
510
|
+
return markdown
|
|
511
|
+
|
|
512
|
+
@staticmethod
|
|
513
|
+
def _format_get_dividends(dividends: pd.Series) -> str:
|
|
514
|
+
"""Format dividend data as markdown"""
|
|
515
|
+
if dividends.empty:
|
|
516
|
+
return "No dividend data available.\n"
|
|
517
|
+
|
|
518
|
+
markdown = f"**Total Dividends Recorded:** {len(dividends)}\n"
|
|
519
|
+
markdown += f"**Date Range:** {dividends.index.min().strftime('%Y-%m-%d')} to {dividends.index.max().strftime('%Y-%m-%d')}\n\n"
|
|
520
|
+
|
|
521
|
+
# Recent dividends (last 10)
|
|
522
|
+
recent_dividends = dividends.tail(10)
|
|
523
|
+
markdown += "### 💰 Recent Dividends\n\n"
|
|
524
|
+
markdown += "| Date | Dividend Amount |\n"
|
|
525
|
+
markdown += "|------|----------------|\n"
|
|
526
|
+
|
|
527
|
+
for date, amount in recent_dividends.items():
|
|
528
|
+
markdown += f"| {date.strftime('%Y-%m-%d')} | ${amount:.4f} |\n"
|
|
529
|
+
|
|
530
|
+
# Summary
|
|
531
|
+
markdown += f"\n**Total Dividends Paid:** ${dividends.sum():.4f}\n"
|
|
532
|
+
markdown += f"**Average Dividend:** ${dividends.mean():.4f}\n"
|
|
533
|
+
if len(dividends) > 1:
|
|
534
|
+
yearly_frequency = len(dividends) / ((dividends.index.max() - dividends.index.min()).days / 365.25)
|
|
535
|
+
markdown += f"**Estimated Annual Frequency:** {yearly_frequency:.1f} times per year\n"
|
|
536
|
+
|
|
537
|
+
return markdown
|
|
538
|
+
|
|
539
|
+
@staticmethod
|
|
540
|
+
def _format_get_recommendations(recommendations: pd.DataFrame) -> str:
|
|
541
|
+
"""Format recommendations as markdown"""
|
|
542
|
+
if recommendations.empty:
|
|
543
|
+
return "No recommendations available.\n"
|
|
544
|
+
|
|
545
|
+
markdown = f"**Total Recommendations:** {len(recommendations)}\n\n"
|
|
546
|
+
|
|
547
|
+
# Recent recommendations (last 15)
|
|
548
|
+
recent_recs = recommendations.tail(15)
|
|
549
|
+
markdown += "### 📊 Recent Analyst Recommendations\n\n"
|
|
550
|
+
markdown += "| Date | Firm | To Grade | From Grade | Action |\n"
|
|
551
|
+
markdown += "|------|------|----------|------------|--------|\n"
|
|
552
|
+
|
|
553
|
+
for _, rec in recent_recs.iterrows():
|
|
554
|
+
date = rec.get('Date', 'N/A')
|
|
555
|
+
firm = rec.get('Firm', 'N/A')
|
|
556
|
+
to_grade = rec.get('To Grade', 'N/A')
|
|
557
|
+
from_grade = rec.get('From Grade', 'N/A')
|
|
558
|
+
action = rec.get('Action', 'N/A')
|
|
559
|
+
|
|
560
|
+
markdown += f"| {date} | {firm} | {to_grade} | {from_grade} | {action} |\n"
|
|
561
|
+
|
|
562
|
+
return markdown
|
|
563
|
+
|
|
564
|
+
@staticmethod
|
|
565
|
+
def _format_get_earnings(earnings: pd.DataFrame) -> str:
|
|
566
|
+
"""Format earnings as markdown"""
|
|
567
|
+
if earnings.empty:
|
|
568
|
+
return "No earnings data available.\n"
|
|
569
|
+
|
|
570
|
+
markdown = "### 💼 Annual Earnings History\n\n"
|
|
571
|
+
markdown += "| Year | Revenue | Earnings |\n"
|
|
572
|
+
markdown += "|------|---------|----------|\n"
|
|
573
|
+
|
|
574
|
+
for year, row in earnings.iterrows():
|
|
575
|
+
revenue = row.get('Revenue', 'N/A')
|
|
576
|
+
earnings_val = row.get('Earnings', 'N/A')
|
|
577
|
+
|
|
578
|
+
# Format numbers if they're numeric
|
|
579
|
+
if isinstance(revenue, (int, float)):
|
|
580
|
+
revenue = f"${revenue:,.0f}"
|
|
581
|
+
if isinstance(earnings_val, (int, float)):
|
|
582
|
+
earnings_val = f"${earnings_val:,.0f}"
|
|
583
|
+
|
|
584
|
+
markdown += f"| {year} | {revenue} | {earnings_val} |\n"
|
|
585
|
+
|
|
586
|
+
return markdown
|
|
@@ -2,11 +2,12 @@ from browser_use.tools.registry.service import Registry
|
|
|
2
2
|
from vibe_surf.tools.vibesurf_tools import VibeSurfTools
|
|
3
3
|
from vibe_surf.tools.file_system import CustomFileSystem
|
|
4
4
|
from browser_use.tools.views import NoParamsAction
|
|
5
|
+
from vibe_surf.tools.vibesurf_registry import VibeSurfRegistry
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class ReportWriterTools(VibeSurfTools):
|
|
8
9
|
def __init__(self, exclude_actions: list[str] = []):
|
|
9
|
-
self.registry =
|
|
10
|
+
self.registry = VibeSurfRegistry(exclude_actions)
|
|
10
11
|
self._register_file_actions()
|
|
11
12
|
self._register_done_action()
|
|
12
13
|
|
|
@@ -8,6 +8,8 @@ import json
|
|
|
8
8
|
import enum
|
|
9
9
|
import base64
|
|
10
10
|
import mimetypes
|
|
11
|
+
import yfinance as yf
|
|
12
|
+
import pprint
|
|
11
13
|
from json_repair import repair_json
|
|
12
14
|
from datetime import datetime
|
|
13
15
|
from typing import Optional, Type, Callable, Dict, Any, Union, Awaitable, TypeVar
|
|
@@ -29,7 +31,8 @@ from browser_use.tools.views import NoParamsAction
|
|
|
29
31
|
from vibe_surf.browser.agent_browser_session import AgentBrowserSession
|
|
30
32
|
from vibe_surf.tools.views import HoverAction, ExtractionAction, FileExtractionAction, BrowserUseAgentExecution, \
|
|
31
33
|
ReportWriterTask, TodoGenerateAction, TodoModifyAction, VibeSurfDoneAction, SkillSearchAction, SkillCrawlAction, \
|
|
32
|
-
SkillSummaryAction, SkillTakeScreenshotAction, SkillDeepResearchAction, SkillCodeAction
|
|
34
|
+
SkillSummaryAction, SkillTakeScreenshotAction, SkillDeepResearchAction, SkillCodeAction, SkillFinanceAction
|
|
35
|
+
from vibe_surf.tools.finance_tools import FinanceDataRetriever, FinanceMarkdownFormatter, FinanceMethod
|
|
33
36
|
from vibe_surf.tools.mcp_client import CustomMCPClient
|
|
34
37
|
from vibe_surf.tools.file_system import CustomFileSystem
|
|
35
38
|
from vibe_surf.browser.browser_manager import BrowserManager
|
|
@@ -818,6 +821,92 @@ Please generate alternative JavaScript code that avoids this system error:"""
|
|
|
818
821
|
include_extracted_content_only_once=True,
|
|
819
822
|
)
|
|
820
823
|
|
|
824
|
+
@self.registry.action(
|
|
825
|
+
'Skill: Get comprehensive financial data for stocks - retrieve company information, historical prices, news, earnings, dividends, analyst recommendations and other financial data using Yahoo Finance. Available methods include: get_info (company info), get_history (price history), get_news (latest news), get_dividends (dividend history), get_earnings (earnings data), get_recommendations (analyst recommendations), get_balance_sheet (balance sheet data), get_income_stmt (income statement), get_cashflow (cash flow statement), get_fast_info (quick stats), get_institutional_holders (institutional ownership), get_major_holders (major shareholders), get_sustainability (ESG data), get_upgrades_downgrades (analyst upgrades/downgrades), and more. If no methods specified, defaults to get_info.',
|
|
826
|
+
param_model=SkillFinanceAction,
|
|
827
|
+
)
|
|
828
|
+
async def skill_finance(
|
|
829
|
+
params: SkillFinanceAction,
|
|
830
|
+
):
|
|
831
|
+
"""
|
|
832
|
+
Skill: Get comprehensive financial data using Yahoo Finance
|
|
833
|
+
|
|
834
|
+
Available methods include:
|
|
835
|
+
- get_info: Company information including sector, industry, market cap, business summary
|
|
836
|
+
- get_history: Historical stock prices and volume data over time periods
|
|
837
|
+
- get_news: Latest news articles about the company
|
|
838
|
+
- get_dividends: Historical dividend payments and yield data
|
|
839
|
+
- get_earnings: Quarterly and annual earnings data and growth trends
|
|
840
|
+
- get_recommendations: Analyst recommendations, price targets, and ratings
|
|
841
|
+
- get_balance_sheet: Company balance sheet data (assets, liabilities, equity)
|
|
842
|
+
- get_income_stmt: Income statement data (revenue, expenses, profit)
|
|
843
|
+
- get_cashflow: Cash flow statement data (operating, investing, financing)
|
|
844
|
+
- get_fast_info: Quick statistics like current price, volume, market cap
|
|
845
|
+
- get_institutional_holders: Institutional ownership and holdings data
|
|
846
|
+
- get_major_holders: Major shareholders and insider ownership percentages
|
|
847
|
+
- get_sustainability: ESG (Environmental, Social, Governance) scores and data
|
|
848
|
+
- get_upgrades_downgrades: Recent analyst upgrades and downgrades
|
|
849
|
+
- get_splits: Historical stock splits and stock split dates
|
|
850
|
+
- get_actions: Corporate actions including dividends and splits
|
|
851
|
+
- get_sec_filings: Recent SEC filings and regulatory documents
|
|
852
|
+
- get_calendar: Upcoming earnings dates and events
|
|
853
|
+
- get_mutualfund_holders: Mutual fund ownership data
|
|
854
|
+
- get_insider_purchases: Recent insider buying activity
|
|
855
|
+
- get_insider_transactions: All insider trading transactions
|
|
856
|
+
- get_shares: Outstanding shares and float data
|
|
857
|
+
"""
|
|
858
|
+
try:
|
|
859
|
+
# Default to get_info if no methods specified
|
|
860
|
+
methods = params.methods if params.methods else [FinanceMethod.GET_INFO]
|
|
861
|
+
|
|
862
|
+
# Convert string methods to FinanceMethod enum if needed
|
|
863
|
+
if methods and isinstance(methods[0], str):
|
|
864
|
+
try:
|
|
865
|
+
methods = [FinanceMethod(method) for method in methods]
|
|
866
|
+
except ValueError as e:
|
|
867
|
+
available_methods = [method.value for method in FinanceMethod]
|
|
868
|
+
return ActionResult(
|
|
869
|
+
error=f'Invalid method in {methods}. Available methods: {available_methods}'
|
|
870
|
+
)
|
|
871
|
+
|
|
872
|
+
# Create data retriever with symbol
|
|
873
|
+
retriever = FinanceDataRetriever(params.symbol)
|
|
874
|
+
|
|
875
|
+
# Convert FinanceMethod enum values to strings for the retriever
|
|
876
|
+
method_strings = [method.value for method in methods]
|
|
877
|
+
|
|
878
|
+
# Retrieve financial data
|
|
879
|
+
financial_data = retriever.get_finance_data(
|
|
880
|
+
methods=method_strings,
|
|
881
|
+
period=getattr(params, 'period', '1y'),
|
|
882
|
+
start_date=getattr(params, 'start_date', None),
|
|
883
|
+
end_date=getattr(params, 'end_date', None),
|
|
884
|
+
interval=getattr(params, 'interval', '1d'),
|
|
885
|
+
num_news=getattr(params, 'num_news', 5)
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
# Format as markdown using the static method
|
|
889
|
+
markdown_content = FinanceMarkdownFormatter.format_finance_data(
|
|
890
|
+
symbol=params.symbol,
|
|
891
|
+
results=financial_data,
|
|
892
|
+
methods=method_strings
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
method_names = [method.value for method in methods]
|
|
896
|
+
logger.info(f'💹 Comprehensive finance data retrieved for {params.symbol} with methods: {method_names}')
|
|
897
|
+
|
|
898
|
+
return ActionResult(
|
|
899
|
+
extracted_content=markdown_content,
|
|
900
|
+
include_extracted_content_only_once=True,
|
|
901
|
+
long_term_memory=f'Retrieved comprehensive financial data for {params.symbol} using methods: {", ".join(method_names)}',
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
except Exception as e:
|
|
905
|
+
error_msg = f'❌ Failed to retrieve financial data for {params.symbol}: {str(e)}'
|
|
906
|
+
logger.error(error_msg)
|
|
907
|
+
return ActionResult(error=error_msg)
|
|
908
|
+
|
|
909
|
+
|
|
821
910
|
async def _perform_google_search(self, browser_session, query: str, llm: BaseChatModel):
|
|
822
911
|
"""Helper method to perform Google search and extract top 5 results"""
|
|
823
912
|
try:
|
|
@@ -1379,7 +1468,7 @@ You will be given a query and the markdown of a webpage that has been filtered t
|
|
|
1379
1468
|
async def write_file(
|
|
1380
1469
|
file_path: str,
|
|
1381
1470
|
content: str,
|
|
1382
|
-
file_system:
|
|
1471
|
+
file_system: CustomFileSystem,
|
|
1383
1472
|
append: bool = False,
|
|
1384
1473
|
trailing_newline: bool = True,
|
|
1385
1474
|
leading_newline: bool = False,
|
vibe_surf/tools/views.py
CHANGED
|
@@ -178,3 +178,36 @@ class SkillCodeAction(BaseModel):
|
|
|
178
178
|
max_length=4,
|
|
179
179
|
description='Optional 4 character Tab ID to execute code on specific tab',
|
|
180
180
|
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class SkillFinanceAction(BaseModel):
|
|
184
|
+
"""Parameters for skill_finance action"""
|
|
185
|
+
symbol: str = Field(
|
|
186
|
+
description='Stock symbol to retrieve financial data for (e.g., AAPL, GOOG, TSLA)',
|
|
187
|
+
)
|
|
188
|
+
methods: list[str] | None = Field(
|
|
189
|
+
default=None,
|
|
190
|
+
description='List of finance methods to retrieve. Common methods: get_info (basic company info), get_history (stock price history), get_news (latest news), get_dividends (dividend history), get_earnings (earnings data), get_fast_info (quick stats), get_recommendations (analyst recommendations), get_financials (income statement), get_balance_sheet (balance sheet), get_cashflow (cash flow). If empty, defaults to get_info. Full list available in FinanceMethod enum.',
|
|
191
|
+
)
|
|
192
|
+
period: str = Field(
|
|
193
|
+
default='1y',
|
|
194
|
+
description='Time period for historical data (1d, 5d, 1mo, 3mo, 6mo, 1y, 2y, 5y, 10y, ytd, max)',
|
|
195
|
+
)
|
|
196
|
+
start_date: str | None = Field(
|
|
197
|
+
default=None,
|
|
198
|
+
description='Start date for historical data (YYYY-MM-DD format). Use with end_date instead of period.',
|
|
199
|
+
)
|
|
200
|
+
end_date: str | None = Field(
|
|
201
|
+
default=None,
|
|
202
|
+
description='End date for historical data (YYYY-MM-DD format). Use with start_date instead of period.',
|
|
203
|
+
)
|
|
204
|
+
interval: str = Field(
|
|
205
|
+
default='1d',
|
|
206
|
+
description='Data interval for historical data (1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo)',
|
|
207
|
+
)
|
|
208
|
+
num_news: int = Field(
|
|
209
|
+
default=5,
|
|
210
|
+
description='Number of news articles to retrieve when get_news method is selected',
|
|
211
|
+
ge=1,
|
|
212
|
+
le=20,
|
|
213
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vibesurf
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.24
|
|
4
4
|
Summary: VibeSurf: A powerful browser assistant for vibe surfing
|
|
5
5
|
Author: Shao Warm
|
|
6
6
|
License: Apache-2.0
|
|
@@ -43,6 +43,7 @@ Requires-Dist: nanoid>=2.0.0
|
|
|
43
43
|
Requires-Dist: markdownify>=1.2.0
|
|
44
44
|
Requires-Dist: pathvalidate>=3.3.1
|
|
45
45
|
Requires-Dist: dashscope>=1.24.5
|
|
46
|
+
Requires-Dist: yfinance>=0.2.66
|
|
46
47
|
Dynamic: license-file
|
|
47
48
|
|
|
48
49
|
# VibeSurf: A powerful browser assistant for vibe surfing
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
vibe_surf/__init__.py,sha256=WtduuMFGauMD_9dpk4fnRnLTAP6ka9Lfu0feAFNzLfo,339
|
|
2
|
-
vibe_surf/_version.py,sha256=
|
|
2
|
+
vibe_surf/_version.py,sha256=IV4a2R7tlzuACf6FAyPEbprLKNroeE-n_UPSKi1QJSc,706
|
|
3
3
|
vibe_surf/cli.py,sha256=pbep2dBeQqralZ8AggkH4h2nayBarbdN8lhZxo35gNU,16689
|
|
4
4
|
vibe_surf/common.py,sha256=_WWMxen5wFwzUjEShn3yDVC1OBFUiJ6Vccadi6tuG6w,1215
|
|
5
5
|
vibe_surf/logger.py,sha256=k53MFA96QX6t9OfcOf1Zws8PP0OOqjVJfhUD3Do9lKw,3043
|
|
6
6
|
vibe_surf/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
vibe_surf/agents/browser_use_agent.py,sha256=jeUYV7yk6vyycw6liju_597GdjB3CW_B2wEhn2F0ekk,45957
|
|
8
8
|
vibe_surf/agents/report_writer_agent.py,sha256=pCF2k6VLyO-sSviGBqqIyVD3SLqaZtSqiW3kvNfPY1I,20967
|
|
9
|
-
vibe_surf/agents/vibe_surf_agent.py,sha256=
|
|
9
|
+
vibe_surf/agents/vibe_surf_agent.py,sha256=sTUO4xAiznr7RRzdrRYzXENos9XovicZw8ow2UuJsyI,74286
|
|
10
10
|
vibe_surf/agents/views.py,sha256=yHjNJloa-aofVTGyuRy08tBYP_Y3XLqt1DUWOUmHRng,4825
|
|
11
11
|
vibe_surf/agents/prompts/__init__.py,sha256=l4ieA0D8kLJthyNN85FKLNe4ExBa3stY3l-aImLDRD0,36
|
|
12
12
|
vibe_surf/agents/prompts/report_writer_prompt.py,sha256=sZE8MUT1CDLmRzbnbEQzAvTwJjpITgh2Q8g1_eXmkzE,4454
|
|
@@ -20,7 +20,7 @@ vibe_surf/backend/api/__init__.py,sha256=XxF1jUOORpLYCfFuPrrnUGRnOrr6ClH0_MNPU-4
|
|
|
20
20
|
vibe_surf/backend/api/activity.py,sha256=_cnHusqolt5Hf3KdAf6FK-3sBc-TSaadmb5dJxGI57A,9398
|
|
21
21
|
vibe_surf/backend/api/agent.py,sha256=ISsG3FUIYoUCGcoQAfV3T6mtJSKHxC809p4bqjzjqlU,1199
|
|
22
22
|
vibe_surf/backend/api/browser.py,sha256=NXedyZG3NIVRIx5O7d9mHwVWX-Q4_KsX5mSgfKt8UEA,2122
|
|
23
|
-
vibe_surf/backend/api/config.py,sha256=
|
|
23
|
+
vibe_surf/backend/api/config.py,sha256=vKY6ZnKZeazQP9qqUEiQvP9HoPtJbAzETORuPWZomGw,27272
|
|
24
24
|
vibe_surf/backend/api/files.py,sha256=kJMG9MWECKXwGh64Q6xvAzNjeZGcLhIEnn65HiMZHKE,11762
|
|
25
25
|
vibe_surf/backend/api/models.py,sha256=n_bu8vavvO8bIKA1WUAbaGPFeZKeamMJelDWU3DlFJc,10533
|
|
26
26
|
vibe_surf/backend/api/task.py,sha256=vpQMOn6YBuD_16jzfUajUvBYaydC0jj8Ny3WOJDVuck,14359
|
|
@@ -36,7 +36,7 @@ vibe_surf/backend/database/migrations/v003_fix_task_status_case.sql,sha256=npzRg
|
|
|
36
36
|
vibe_surf/backend/database/migrations/v004_add_voice_profiles.sql,sha256=-9arjQBF-OxvFIOwkEl7JJJRDTS_nJ8GNX3T7bJgVq0,1321
|
|
37
37
|
vibe_surf/backend/utils/__init__.py,sha256=V8leMFp7apAglUAoCHPZrNNcRHthSLYIudIJE5qwjb0,184
|
|
38
38
|
vibe_surf/backend/utils/encryption.py,sha256=CjLNh_n0Luhfa-6BB-icfzkiiDqj5b4Gu6MADU3p2eM,3754
|
|
39
|
-
vibe_surf/backend/utils/llm_factory.py,sha256=
|
|
39
|
+
vibe_surf/backend/utils/llm_factory.py,sha256=KF84YYgPaOF0_1P_IF0cAtY1kua0D-8gEP2NoSu2UZM,9033
|
|
40
40
|
vibe_surf/browser/__init__.py,sha256=_UToO2fZfSCrfjOcxhn4Qq7ZLbYeyPuUUEmqIva-Yv8,325
|
|
41
41
|
vibe_surf/browser/agen_browser_profile.py,sha256=J06hCBJSJ-zAFVM9yDFz8UpmiLuFyWke1EMekpU45eo,5871
|
|
42
42
|
vibe_surf/browser/agent_browser_session.py,sha256=xV0nHo_TCb7b7QYhIee4cLzH-1rqJswYwH7GEwyQmqc,33980
|
|
@@ -85,19 +85,20 @@ vibe_surf/chrome_extension/styles/settings-responsive.css,sha256=jLE0yG15n2aI6_6
|
|
|
85
85
|
vibe_surf/chrome_extension/styles/settings-utilities.css,sha256=3PuQS2857kg83d5erLbLdo_7J95-qV-qyNWS5M-w1oQ,505
|
|
86
86
|
vibe_surf/chrome_extension/styles/variables.css,sha256=enjyhsa0PeU3b-3uiXa-VkV-1-h2-Ai3m4KpmC2k0rY,2984
|
|
87
87
|
vibe_surf/llm/__init__.py,sha256=_vDVPo6STf343p1SgMQrF5023hicAx0g83pK2Gbk4Ek,601
|
|
88
|
-
vibe_surf/llm/openai_compatible.py,sha256=
|
|
88
|
+
vibe_surf/llm/openai_compatible.py,sha256=7e0XC-Mtz8MmgQZHH8tx8H_VXB6MLvMhDy1qKbESmVo,16149
|
|
89
89
|
vibe_surf/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
90
|
vibe_surf/tools/browser_use_tools.py,sha256=tacxKUJL6uOt04f52_iIw1cs-FT-mBgIPmAsIc4Hww0,23730
|
|
91
|
-
vibe_surf/tools/file_system.py,sha256=
|
|
91
|
+
vibe_surf/tools/file_system.py,sha256=Tw_6J5QjCahQ3fd26CXziF1zPvRxhYM0889oK4bDhlU,19304
|
|
92
|
+
vibe_surf/tools/finance_tools.py,sha256=pwPSBb0HwCDTdKZNAS5NPE8-rM1Nz57foj9XyKgQmI4,24803
|
|
92
93
|
vibe_surf/tools/mcp_client.py,sha256=OeCoTgyx4MoY7JxXndK6pGHIoyFOhf5r7XCbx25y1Ec,2446
|
|
93
|
-
vibe_surf/tools/report_writer_tools.py,sha256=
|
|
94
|
+
vibe_surf/tools/report_writer_tools.py,sha256=2CyTTXOahTKZo7XwyWDDhJ--1mRA0uTtUWxu_DACAY0,776
|
|
94
95
|
vibe_surf/tools/vibesurf_registry.py,sha256=Z-8d9BrJl3RFMEK0Tw1Q5xNHX2kZGsnIGCTBZ3RM-pw,2159
|
|
95
|
-
vibe_surf/tools/vibesurf_tools.py,sha256=
|
|
96
|
-
vibe_surf/tools/views.py,sha256=
|
|
96
|
+
vibe_surf/tools/vibesurf_tools.py,sha256=KMf9J_GDo9MbjBruv6-aHi5srR2pvlvW3uegihAMRIc,79994
|
|
97
|
+
vibe_surf/tools/views.py,sha256=AEAPzML-lqWJ7dBMjXTl7o-rk4hp5PGaPRqLyilJUl8,7789
|
|
97
98
|
vibe_surf/tools/voice_asr.py,sha256=AJG0yq_Jq-j8ulDlbPhVFfK1jch9_ASesis73iki9II,4702
|
|
98
|
-
vibesurf-0.1.
|
|
99
|
-
vibesurf-0.1.
|
|
100
|
-
vibesurf-0.1.
|
|
101
|
-
vibesurf-0.1.
|
|
102
|
-
vibesurf-0.1.
|
|
103
|
-
vibesurf-0.1.
|
|
99
|
+
vibesurf-0.1.24.dist-info/licenses/LICENSE,sha256=czn6QYya0-jhLnStD9JqnMS-hwP5wRByipkrGTvoXLI,11355
|
|
100
|
+
vibesurf-0.1.24.dist-info/METADATA,sha256=Ck-enMQ77f9ekeLQG9xzNGX3mOuDhqIXiXdA3_Zcq4I,5190
|
|
101
|
+
vibesurf-0.1.24.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
102
|
+
vibesurf-0.1.24.dist-info/entry_points.txt,sha256=UxqpvMocL-PR33S6vLF2OmXn-kVzM-DneMeZeHcPMM8,48
|
|
103
|
+
vibesurf-0.1.24.dist-info/top_level.txt,sha256=VPZGHqSb6EEqcJ4ZX6bHIuWfon5f6HXl3c7BYpbRqnY,10
|
|
104
|
+
vibesurf-0.1.24.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|