opencandle 0.3.0 → 0.5.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.
Files changed (408) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +106 -14
  3. package/assets/logo.svg +187 -0
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +40 -3
  6. package/dist/cli.js.map +1 -1
  7. package/dist/config.d.ts +25 -0
  8. package/dist/config.js +72 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/infra/browser.d.ts +11 -3
  11. package/dist/infra/browser.js +2 -1
  12. package/dist/infra/browser.js.map +1 -1
  13. package/dist/infra/native-dependencies.d.ts +1 -0
  14. package/dist/infra/native-dependencies.js +10 -0
  15. package/dist/infra/native-dependencies.js.map +1 -0
  16. package/dist/infra/node-version.d.ts +2 -0
  17. package/dist/infra/node-version.js +23 -0
  18. package/dist/infra/node-version.js.map +1 -0
  19. package/dist/infra/rate-limiter.d.ts +4 -0
  20. package/dist/infra/rate-limiter.js +5 -1
  21. package/dist/infra/rate-limiter.js.map +1 -1
  22. package/dist/memory/index.d.ts +2 -0
  23. package/dist/memory/index.js +1 -0
  24. package/dist/memory/index.js.map +1 -1
  25. package/dist/memory/manager.d.ts +9 -0
  26. package/dist/memory/manager.js +28 -11
  27. package/dist/memory/manager.js.map +1 -1
  28. package/dist/memory/sqlite.js +42 -4
  29. package/dist/memory/sqlite.js.map +1 -1
  30. package/dist/memory/storage.d.ts +7 -0
  31. package/dist/memory/storage.js +3 -3
  32. package/dist/memory/storage.js.map +1 -1
  33. package/dist/memory/tool-defaults.d.ts +8 -0
  34. package/dist/memory/tool-defaults.js +59 -0
  35. package/dist/memory/tool-defaults.js.map +1 -0
  36. package/dist/memory/types.js +4 -0
  37. package/dist/memory/types.js.map +1 -1
  38. package/dist/onboarding/connect.d.ts +13 -1
  39. package/dist/onboarding/connect.js +21 -10
  40. package/dist/onboarding/connect.js.map +1 -1
  41. package/dist/onboarding/prompt-user.d.ts +1 -1
  42. package/dist/onboarding/providers.d.ts +7 -0
  43. package/dist/onboarding/providers.js +6 -3
  44. package/dist/onboarding/providers.js.map +1 -1
  45. package/dist/onboarding/tool-helpers.d.ts +1 -1
  46. package/dist/pi/opencandle-extension.d.ts +7 -1
  47. package/dist/pi/opencandle-extension.js +391 -21
  48. package/dist/pi/opencandle-extension.js.map +1 -1
  49. package/dist/pi/session-storage.d.ts +2 -0
  50. package/dist/pi/session-storage.js +5 -0
  51. package/dist/pi/session-storage.js.map +1 -0
  52. package/dist/pi/session.d.ts +4 -1
  53. package/dist/pi/session.js +25 -3
  54. package/dist/pi/session.js.map +1 -1
  55. package/dist/pi/setup.d.ts +1 -1
  56. package/dist/pi/setup.js +11 -1
  57. package/dist/pi/setup.js.map +1 -1
  58. package/dist/pi/tool-adapter.d.ts +2 -2
  59. package/dist/pi/tool-adapter.js +14 -1
  60. package/dist/pi/tool-adapter.js.map +1 -1
  61. package/dist/prompts/context-builder.d.ts +40 -3
  62. package/dist/prompts/context-builder.js +140 -19
  63. package/dist/prompts/context-builder.js.map +1 -1
  64. package/dist/prompts/disclaimer.d.ts +6 -0
  65. package/dist/prompts/disclaimer.js +9 -0
  66. package/dist/prompts/disclaimer.js.map +1 -0
  67. package/dist/prompts/policy-cards.d.ts +13 -0
  68. package/dist/prompts/policy-cards.js +197 -0
  69. package/dist/prompts/policy-cards.js.map +1 -0
  70. package/dist/prompts/sections.js +3 -3
  71. package/dist/prompts/sections.js.map +1 -1
  72. package/dist/prompts/workflow-prompts.d.ts +8 -0
  73. package/dist/prompts/workflow-prompts.js +208 -22
  74. package/dist/prompts/workflow-prompts.js.map +1 -1
  75. package/dist/providers/alpha-vantage.js +23 -1
  76. package/dist/providers/alpha-vantage.js.map +1 -1
  77. package/dist/providers/sec-edgar.d.ts +8 -1
  78. package/dist/providers/sec-edgar.js +172 -5
  79. package/dist/providers/sec-edgar.js.map +1 -1
  80. package/dist/providers/yahoo-finance.d.ts +2 -0
  81. package/dist/providers/yahoo-finance.js +203 -35
  82. package/dist/providers/yahoo-finance.js.map +1 -1
  83. package/dist/routing/classify-intent.d.ts +3 -0
  84. package/dist/routing/classify-intent.js +82 -3
  85. package/dist/routing/classify-intent.js.map +1 -1
  86. package/dist/routing/defaults.js +4 -4
  87. package/dist/routing/defaults.js.map +1 -1
  88. package/dist/routing/entity-extractor.d.ts +1 -0
  89. package/dist/routing/entity-extractor.js +158 -12
  90. package/dist/routing/entity-extractor.js.map +1 -1
  91. package/dist/routing/index.d.ts +10 -0
  92. package/dist/routing/index.js +7 -0
  93. package/dist/routing/index.js.map +1 -1
  94. package/dist/routing/legacy-rule-router.d.ts +9 -0
  95. package/dist/routing/legacy-rule-router.js +12 -0
  96. package/dist/routing/legacy-rule-router.js.map +1 -0
  97. package/dist/routing/planning.d.ts +54 -0
  98. package/dist/routing/planning.js +531 -0
  99. package/dist/routing/planning.js.map +1 -0
  100. package/dist/routing/route-manifest.d.ts +35 -0
  101. package/dist/routing/route-manifest.js +221 -0
  102. package/dist/routing/route-manifest.js.map +1 -0
  103. package/dist/routing/router-llm-client.d.ts +11 -0
  104. package/dist/routing/router-llm-client.js +42 -0
  105. package/dist/routing/router-llm-client.js.map +1 -0
  106. package/dist/routing/router-prompt.d.ts +2 -0
  107. package/dist/routing/router-prompt.js +141 -0
  108. package/dist/routing/router-prompt.js.map +1 -0
  109. package/dist/routing/router-types.d.ts +71 -0
  110. package/dist/routing/router-types.js +2 -0
  111. package/dist/routing/router-types.js.map +1 -0
  112. package/dist/routing/router.d.ts +11 -0
  113. package/dist/routing/router.js +638 -0
  114. package/dist/routing/router.js.map +1 -0
  115. package/dist/routing/slot-resolver.js +46 -6
  116. package/dist/routing/slot-resolver.js.map +1 -1
  117. package/dist/routing/turn-context.d.ts +44 -0
  118. package/dist/routing/turn-context.js +45 -0
  119. package/dist/routing/turn-context.js.map +1 -0
  120. package/dist/routing/types.d.ts +13 -1
  121. package/dist/runtime/answer-contracts.d.ts +82 -0
  122. package/dist/runtime/answer-contracts.js +414 -0
  123. package/dist/runtime/answer-contracts.js.map +1 -0
  124. package/dist/runtime/artifact-contracts.d.ts +14 -0
  125. package/dist/runtime/artifact-contracts.js +57 -0
  126. package/dist/runtime/artifact-contracts.js.map +1 -0
  127. package/dist/runtime/planning-evidence.d.ts +99 -0
  128. package/dist/runtime/planning-evidence.js +445 -0
  129. package/dist/runtime/planning-evidence.js.map +1 -0
  130. package/dist/runtime/session-coordinator.d.ts +81 -3
  131. package/dist/runtime/session-coordinator.js +201 -17
  132. package/dist/runtime/session-coordinator.js.map +1 -1
  133. package/dist/runtime/tool-defaults-wrapper.d.ts +3 -0
  134. package/dist/runtime/tool-defaults-wrapper.js +25 -0
  135. package/dist/runtime/tool-defaults-wrapper.js.map +1 -0
  136. package/dist/sentiment/store.js +5 -0
  137. package/dist/sentiment/store.js.map +1 -1
  138. package/dist/system-prompt.js +23 -12
  139. package/dist/system-prompt.js.map +1 -1
  140. package/dist/tool-kit.d.ts +4 -4
  141. package/dist/tools/fundamentals/company-overview.d.ts +1 -1
  142. package/dist/tools/fundamentals/company-overview.js +1 -1
  143. package/dist/tools/fundamentals/company-overview.js.map +1 -1
  144. package/dist/tools/fundamentals/comps.d.ts +1 -1
  145. package/dist/tools/fundamentals/comps.js +1 -1
  146. package/dist/tools/fundamentals/comps.js.map +1 -1
  147. package/dist/tools/fundamentals/dcf.d.ts +1 -1
  148. package/dist/tools/fundamentals/dcf.js +1 -1
  149. package/dist/tools/fundamentals/dcf.js.map +1 -1
  150. package/dist/tools/fundamentals/earnings.d.ts +1 -1
  151. package/dist/tools/fundamentals/earnings.js +1 -1
  152. package/dist/tools/fundamentals/earnings.js.map +1 -1
  153. package/dist/tools/fundamentals/financials.d.ts +1 -1
  154. package/dist/tools/fundamentals/financials.js +1 -1
  155. package/dist/tools/fundamentals/financials.js.map +1 -1
  156. package/dist/tools/fundamentals/sec-filings.d.ts +2 -1
  157. package/dist/tools/fundamentals/sec-filings.js +19 -2
  158. package/dist/tools/fundamentals/sec-filings.js.map +1 -1
  159. package/dist/tools/index.d.ts +29 -1
  160. package/dist/tools/index.js +30 -0
  161. package/dist/tools/index.js.map +1 -1
  162. package/dist/tools/interaction/ask-user.d.ts +1 -1
  163. package/dist/tools/interaction/twitter-login.d.ts +1 -1
  164. package/dist/tools/macro/fear-greed.d.ts +1 -1
  165. package/dist/tools/macro/fear-greed.js +1 -1
  166. package/dist/tools/macro/fear-greed.js.map +1 -1
  167. package/dist/tools/macro/fred-data.d.ts +1 -1
  168. package/dist/tools/macro/fred-data.js +29 -5
  169. package/dist/tools/macro/fred-data.js.map +1 -1
  170. package/dist/tools/market/crypto-history.d.ts +1 -1
  171. package/dist/tools/market/crypto-history.js +18 -2
  172. package/dist/tools/market/crypto-history.js.map +1 -1
  173. package/dist/tools/market/crypto-price.d.ts +1 -1
  174. package/dist/tools/market/crypto-price.js +1 -1
  175. package/dist/tools/market/crypto-price.js.map +1 -1
  176. package/dist/tools/market/search-ticker.d.ts +1 -1
  177. package/dist/tools/market/search-ticker.js +1 -1
  178. package/dist/tools/market/search-ticker.js.map +1 -1
  179. package/dist/tools/market/stock-history.d.ts +1 -1
  180. package/dist/tools/market/stock-history.js +1 -1
  181. package/dist/tools/market/stock-history.js.map +1 -1
  182. package/dist/tools/market/stock-quote.d.ts +1 -1
  183. package/dist/tools/market/stock-quote.js +1 -1
  184. package/dist/tools/market/stock-quote.js.map +1 -1
  185. package/dist/tools/options/greeks.js +0 -1
  186. package/dist/tools/options/greeks.js.map +1 -1
  187. package/dist/tools/options/option-chain.d.ts +1 -1
  188. package/dist/tools/options/option-chain.js +13 -5
  189. package/dist/tools/options/option-chain.js.map +1 -1
  190. package/dist/tools/portfolio/correlation.d.ts +1 -1
  191. package/dist/tools/portfolio/correlation.js +1 -1
  192. package/dist/tools/portfolio/correlation.js.map +1 -1
  193. package/dist/tools/portfolio/holdings-overlap.d.ts +8 -0
  194. package/dist/tools/portfolio/holdings-overlap.js +105 -0
  195. package/dist/tools/portfolio/holdings-overlap.js.map +1 -0
  196. package/dist/tools/portfolio/predictions.d.ts +1 -1
  197. package/dist/tools/portfolio/predictions.js +1 -1
  198. package/dist/tools/portfolio/predictions.js.map +1 -1
  199. package/dist/tools/portfolio/risk-analysis.d.ts +1 -1
  200. package/dist/tools/portfolio/risk-analysis.js +1 -1
  201. package/dist/tools/portfolio/risk-analysis.js.map +1 -1
  202. package/dist/tools/portfolio/tracker.d.ts +1 -1
  203. package/dist/tools/portfolio/tracker.js +1 -1
  204. package/dist/tools/portfolio/tracker.js.map +1 -1
  205. package/dist/tools/portfolio/watchlist.d.ts +1 -1
  206. package/dist/tools/portfolio/watchlist.js +12 -4
  207. package/dist/tools/portfolio/watchlist.js.map +1 -1
  208. package/dist/tools/sentiment/reddit-sentiment.d.ts +1 -1
  209. package/dist/tools/sentiment/reddit-sentiment.js +1 -1
  210. package/dist/tools/sentiment/reddit-sentiment.js.map +1 -1
  211. package/dist/tools/sentiment/sentiment-summary.d.ts +1 -1
  212. package/dist/tools/sentiment/sentiment-summary.js +57 -2
  213. package/dist/tools/sentiment/sentiment-summary.js.map +1 -1
  214. package/dist/tools/sentiment/sentiment-trend.d.ts +1 -1
  215. package/dist/tools/sentiment/twitter-sentiment.d.ts +1 -1
  216. package/dist/tools/sentiment/twitter-sentiment.js +1 -1
  217. package/dist/tools/sentiment/twitter-sentiment.js.map +1 -1
  218. package/dist/tools/sentiment/web-search.d.ts +1 -1
  219. package/dist/tools/sentiment/web-search.js +32 -3
  220. package/dist/tools/sentiment/web-search.js.map +1 -1
  221. package/dist/tools/sentiment/web-sentiment.d.ts +1 -1
  222. package/dist/tools/sentiment/web-sentiment.js +1 -1
  223. package/dist/tools/sentiment/web-sentiment.js.map +1 -1
  224. package/dist/tools/technical/backtest.d.ts +3 -3
  225. package/dist/tools/technical/backtest.js +41 -27
  226. package/dist/tools/technical/backtest.js.map +1 -1
  227. package/dist/tools/technical/indicators.d.ts +1 -1
  228. package/dist/tools/technical/indicators.js +8 -4
  229. package/dist/tools/technical/indicators.js.map +1 -1
  230. package/dist/types/options.d.ts +10 -0
  231. package/dist/types/portfolio.d.ts +27 -0
  232. package/dist/workflows/compare-assets.js +38 -2
  233. package/dist/workflows/compare-assets.js.map +1 -1
  234. package/dist/workflows/options-screener.js +94 -8
  235. package/dist/workflows/options-screener.js.map +1 -1
  236. package/dist/workflows/portfolio-builder.js +9 -5
  237. package/dist/workflows/portfolio-builder.js.map +1 -1
  238. package/gui/server/ask-user-bridge.ts +82 -0
  239. package/gui/server/background-quotes.ts +31 -0
  240. package/gui/server/chat-event-adapter.ts +142 -0
  241. package/gui/server/gui-session-manager.ts +5 -0
  242. package/gui/server/invoke-tool.ts +89 -0
  243. package/gui/server/live-chat-event-adapter.ts +181 -0
  244. package/gui/server/model-setup.ts +100 -0
  245. package/gui/server/package.json +5 -0
  246. package/gui/server/projector.ts +254 -0
  247. package/gui/server/prompt-observation.ts +61 -0
  248. package/gui/server/server.ts +703 -0
  249. package/gui/server/session-actions.ts +31 -0
  250. package/gui/server/session-entry-wait.ts +81 -0
  251. package/gui/server/tool-metadata.ts +88 -0
  252. package/gui/server/websocket.ts +128 -0
  253. package/gui/server/writer-lock.ts +118 -0
  254. package/gui/shared/chat-events.ts +118 -0
  255. package/gui/shared/event-reducer.ts +186 -0
  256. package/gui/web/dist/assets/CatalogOverlay-Bmp6Knu7.js +1 -0
  257. package/gui/web/dist/assets/index-Bxt9QpLX.css +1 -0
  258. package/gui/web/dist/assets/index-CZ9DHZYy.js +67 -0
  259. package/gui/web/dist/assets/logo-CWpt6Y2a.svg +187 -0
  260. package/gui/web/dist/index.html +17 -0
  261. package/package.json +50 -18
  262. package/src/analysts/contracts.ts +189 -0
  263. package/src/analysts/orchestrator.ts +300 -0
  264. package/src/cli.ts +206 -0
  265. package/src/config.ts +245 -0
  266. package/src/index.ts +5 -0
  267. package/src/infra/browser.ts +111 -0
  268. package/src/infra/cache.ts +103 -0
  269. package/src/infra/http-client.ts +68 -0
  270. package/src/infra/index.ts +18 -0
  271. package/src/infra/native-dependencies.ts +12 -0
  272. package/src/infra/node-version.ts +24 -0
  273. package/src/infra/open-url.ts +28 -0
  274. package/src/infra/opencandle-paths.ts +64 -0
  275. package/src/infra/rate-limiter.ts +73 -0
  276. package/src/memory/index.ts +10 -0
  277. package/src/memory/manager.ts +192 -0
  278. package/src/memory/preference-extractor.ts +106 -0
  279. package/src/memory/retrieval.ts +70 -0
  280. package/src/memory/sqlite.ts +172 -0
  281. package/src/memory/storage.ts +205 -0
  282. package/src/memory/tool-defaults.ts +87 -0
  283. package/src/memory/types.ts +71 -0
  284. package/src/onboarding/connect.ts +184 -0
  285. package/src/onboarding/credential-interceptor.ts +134 -0
  286. package/src/onboarding/degradation-accumulator.ts +79 -0
  287. package/src/onboarding/prompt-user.ts +85 -0
  288. package/src/onboarding/providers.ts +315 -0
  289. package/src/onboarding/state.ts +218 -0
  290. package/src/onboarding/tool-helpers.ts +111 -0
  291. package/src/onboarding/tool-tags.ts +201 -0
  292. package/src/onboarding/validation.ts +158 -0
  293. package/src/pi/opencandle-extension.ts +955 -0
  294. package/src/pi/session-storage.ts +5 -0
  295. package/src/pi/session.ts +81 -0
  296. package/src/pi/setup.ts +381 -0
  297. package/src/pi/tool-adapter.ts +36 -0
  298. package/src/prompts/context-builder.ts +315 -0
  299. package/src/prompts/disclaimer.ts +9 -0
  300. package/src/prompts/policy-cards.ts +220 -0
  301. package/src/prompts/sections.ts +46 -0
  302. package/src/prompts/workflow-prompts.ts +433 -0
  303. package/src/providers/alpha-vantage.ts +315 -0
  304. package/src/providers/coingecko.ts +96 -0
  305. package/src/providers/exa-search.ts +373 -0
  306. package/src/providers/fear-greed.ts +45 -0
  307. package/src/providers/finnhub.ts +124 -0
  308. package/src/providers/fred.ts +83 -0
  309. package/src/providers/index.ts +9 -0
  310. package/src/providers/provider-credential-error.ts +23 -0
  311. package/src/providers/reddit.ts +151 -0
  312. package/src/providers/sec-edgar.ts +312 -0
  313. package/src/providers/twitter.ts +173 -0
  314. package/src/providers/web-search.ts +293 -0
  315. package/src/providers/with-fallback.ts +41 -0
  316. package/src/providers/wrap-provider.ts +64 -0
  317. package/src/providers/yahoo-finance.ts +534 -0
  318. package/src/routing/classify-intent.ts +285 -0
  319. package/src/routing/defaults.ts +29 -0
  320. package/src/routing/entity-extractor.ts +291 -0
  321. package/src/routing/index.ts +70 -0
  322. package/src/routing/legacy-rule-router.ts +13 -0
  323. package/src/routing/planning.ts +732 -0
  324. package/src/routing/route-manifest.ts +287 -0
  325. package/src/routing/router-llm-client.ts +51 -0
  326. package/src/routing/router-prompt.ts +163 -0
  327. package/src/routing/router-types.ts +87 -0
  328. package/src/routing/router.ts +712 -0
  329. package/src/routing/slot-resolver.ts +190 -0
  330. package/src/routing/turn-context.ts +111 -0
  331. package/src/routing/types.ts +75 -0
  332. package/src/runtime/answer-contracts.ts +633 -0
  333. package/src/runtime/artifact-contracts.ts +76 -0
  334. package/src/runtime/evidence.ts +77 -0
  335. package/src/runtime/planning-evidence.ts +591 -0
  336. package/src/runtime/prompt-step.ts +75 -0
  337. package/src/runtime/provider-tracker.ts +40 -0
  338. package/src/runtime/run-context.ts +22 -0
  339. package/src/runtime/session-coordinator.ts +472 -0
  340. package/src/runtime/tool-defaults-wrapper.ts +35 -0
  341. package/src/runtime/validation.ts +214 -0
  342. package/src/runtime/workflow-events.ts +75 -0
  343. package/src/runtime/workflow-runner.ts +188 -0
  344. package/src/runtime/workflow-types.ts +102 -0
  345. package/src/sentiment/adapters/finnhub.ts +44 -0
  346. package/src/sentiment/adapters/reddit.ts +65 -0
  347. package/src/sentiment/adapters/twitter.ts +36 -0
  348. package/src/sentiment/adapters/web.ts +44 -0
  349. package/src/sentiment/index.ts +58 -0
  350. package/src/sentiment/keywords.ts +9 -0
  351. package/src/sentiment/pipeline.ts +68 -0
  352. package/src/sentiment/scorer.ts +78 -0
  353. package/src/sentiment/store.ts +260 -0
  354. package/src/sentiment/trends.ts +90 -0
  355. package/src/sentiment/types.ts +108 -0
  356. package/src/system-prompt.ts +118 -0
  357. package/src/tool-kit.ts +68 -0
  358. package/src/tools/AGENTS.md +36 -0
  359. package/src/tools/fundamentals/company-overview.ts +54 -0
  360. package/src/tools/fundamentals/comps.ts +156 -0
  361. package/src/tools/fundamentals/dcf.ts +267 -0
  362. package/src/tools/fundamentals/earnings.ts +47 -0
  363. package/src/tools/fundamentals/financials.ts +54 -0
  364. package/src/tools/fundamentals/sec-filings.ts +84 -0
  365. package/src/tools/index.ts +91 -0
  366. package/src/tools/interaction/ask-user.ts +81 -0
  367. package/src/tools/interaction/twitter-login.ts +93 -0
  368. package/src/tools/macro/fear-greed.ts +41 -0
  369. package/src/tools/macro/fred-data.ts +80 -0
  370. package/src/tools/market/crypto-history.ts +67 -0
  371. package/src/tools/market/crypto-price.ts +53 -0
  372. package/src/tools/market/search-ticker.ts +53 -0
  373. package/src/tools/market/stock-history.ts +79 -0
  374. package/src/tools/market/stock-quote.ts +64 -0
  375. package/src/tools/options/greeks.ts +81 -0
  376. package/src/tools/options/option-chain.ts +96 -0
  377. package/src/tools/portfolio/correlation.ts +162 -0
  378. package/src/tools/portfolio/holdings-overlap.ts +123 -0
  379. package/src/tools/portfolio/predictions.ts +253 -0
  380. package/src/tools/portfolio/risk-analysis.ts +134 -0
  381. package/src/tools/portfolio/tracker.ts +147 -0
  382. package/src/tools/portfolio/watchlist.ts +159 -0
  383. package/src/tools/sentiment/reddit-sentiment.ts +164 -0
  384. package/src/tools/sentiment/sentiment-summary.ts +316 -0
  385. package/src/tools/sentiment/sentiment-trend.ts +58 -0
  386. package/src/tools/sentiment/twitter-sentiment.ts +96 -0
  387. package/src/tools/sentiment/web-search.ts +183 -0
  388. package/src/tools/sentiment/web-sentiment.ts +76 -0
  389. package/src/tools/technical/backtest.ts +267 -0
  390. package/src/tools/technical/indicators.ts +256 -0
  391. package/src/types/fundamentals.ts +46 -0
  392. package/src/types/index.ts +20 -0
  393. package/src/types/macro.ts +27 -0
  394. package/src/types/market.ts +43 -0
  395. package/src/types/options.ts +52 -0
  396. package/src/types/portfolio.ts +73 -0
  397. package/src/types/sentiment.ts +70 -0
  398. package/src/workflows/compare-assets.ts +75 -0
  399. package/src/workflows/index.ts +4 -0
  400. package/src/workflows/options-screener.ts +127 -0
  401. package/src/workflows/portfolio-builder.ts +56 -0
  402. package/src/workflows/types.ts +4 -0
  403. package/dist/runtime/index.d.ts +0 -16
  404. package/dist/runtime/index.js +0 -10
  405. package/dist/runtime/index.js.map +0 -1
  406. package/dist/runtime/provider-ids.d.ts +0 -14
  407. package/dist/runtime/provider-ids.js +0 -14
  408. package/dist/runtime/provider-ids.js.map +0 -1
@@ -0,0 +1,89 @@
1
+ import type { AgentTool, AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import type { Message, ToolCall, Usage } from "@earendil-works/pi-ai";
3
+ import type { SessionManager } from "@earendil-works/pi-coding-agent";
4
+ import type { TSchema } from "@sinclair/typebox";
5
+ import { Value } from "@sinclair/typebox/value";
6
+ import { getDefaults } from "../../src/memory/tool-defaults.js";
7
+ import { wrapWithDefaults } from "../../src/runtime/tool-defaults-wrapper.js";
8
+
9
+ export interface InvokeToolResult {
10
+ toolCallId: string;
11
+ result: AgentToolResult<unknown>;
12
+ isError: boolean;
13
+ }
14
+
15
+ export async function invokeToolFromUi(
16
+ sessionManager: SessionManager,
17
+ tool: AgentTool<TSchema, unknown>,
18
+ args: Record<string, unknown>,
19
+ source: "ui" | "background" = "ui",
20
+ options: { recordTranscript?: boolean } = {},
21
+ ): Promise<InvokeToolResult> {
22
+ if (!Value.Check(tool.parameters, args)) {
23
+ const errors = [...Value.Errors(tool.parameters, args)]
24
+ .map((error) => `${error.path || "/"} ${error.message}`)
25
+ .join("; ");
26
+ throw new Error(errors || "Invalid tool arguments");
27
+ }
28
+
29
+ const toolCallId = `${source}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
30
+ const call: ToolCall = {
31
+ type: "toolCall",
32
+ id: toolCallId,
33
+ name: tool.name,
34
+ arguments: args,
35
+ };
36
+ const assistant = {
37
+ role: "assistant",
38
+ content: [call],
39
+ api: "openai-responses",
40
+ provider: "openai",
41
+ model: "ui-direct",
42
+ usage: emptyUsage(),
43
+ stopReason: "toolUse",
44
+ timestamp: Date.now(),
45
+ } satisfies Message;
46
+
47
+ const recordTranscript = options.recordTranscript ?? true;
48
+ if (recordTranscript) {
49
+ sessionManager.appendMessage(assistant);
50
+ }
51
+
52
+ const wrapped = wrapWithDefaults(tool, getDefaults(tool.name));
53
+ let result: AgentToolResult<unknown>;
54
+ let isError = false;
55
+ try {
56
+ result = await wrapped.execute(toolCallId, args as never);
57
+ } catch (error) {
58
+ isError = true;
59
+ result = {
60
+ content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }],
61
+ details: null,
62
+ };
63
+ }
64
+
65
+ if (recordTranscript) {
66
+ sessionManager.appendMessage({
67
+ role: "toolResult",
68
+ toolCallId,
69
+ toolName: tool.name,
70
+ content: result.content,
71
+ details: { source, args, value: result.details },
72
+ isError,
73
+ timestamp: Date.now(),
74
+ });
75
+ }
76
+
77
+ return { toolCallId, result, isError };
78
+ }
79
+
80
+ function emptyUsage(): Usage {
81
+ return {
82
+ input: 0,
83
+ output: 0,
84
+ cacheRead: 0,
85
+ cacheWrite: 0,
86
+ totalTokens: 0,
87
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
88
+ };
89
+ }
@@ -0,0 +1,181 @@
1
+ import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
+ import type { AssistantMessage, Message } from "@earendil-works/pi-ai";
3
+ import type { ChatEvent, MessageContent, ToolOutput } from "../shared/chat-events.js";
4
+
5
+ export interface LiveChatEventAdapterOptions {
6
+ runId: string;
7
+ sessionId: string;
8
+ startSeq: number;
9
+ emit: (event: ChatEvent) => void;
10
+ }
11
+
12
+ export interface LiveChatEventAdapter {
13
+ handle(event: AgentSessionEvent): void;
14
+ nextSeq(): number;
15
+ }
16
+
17
+ export function createLiveChatEventAdapter(options: LiveChatEventAdapterOptions): LiveChatEventAdapter {
18
+ let seq = options.startSeq;
19
+ let userCount = 0;
20
+ let assistantCount = 0;
21
+ let currentAssistantMessageId: string | undefined;
22
+ let lastAssistantMessageId: string | undefined;
23
+ const completedMessageIds = new Set<string>();
24
+
25
+ const emit = (event: Omit<ChatEvent, "seq">) => {
26
+ options.emit({ ...event, seq: seq++ } as ChatEvent);
27
+ };
28
+
29
+ const ensureAssistantMessage = (): string => {
30
+ if (currentAssistantMessageId) return currentAssistantMessageId;
31
+ currentAssistantMessageId = `${options.runId}-assistant-${++assistantCount}`;
32
+ lastAssistantMessageId = currentAssistantMessageId;
33
+ emit({ type: "message.created", messageId: currentAssistantMessageId, role: "assistant" });
34
+ return currentAssistantMessageId;
35
+ };
36
+
37
+ const messageIdForTool = (): string => lastAssistantMessageId ?? ensureAssistantMessage();
38
+
39
+ const completeAssistantMessage = (message: AssistantMessage) => {
40
+ const messageId = ensureAssistantMessage();
41
+ if (completedMessageIds.has(messageId)) return;
42
+ completedMessageIds.add(messageId);
43
+ emit({
44
+ type: "message.completed",
45
+ messageId,
46
+ content: contentToChatContent(message.content),
47
+ });
48
+ currentAssistantMessageId = undefined;
49
+ };
50
+
51
+ return {
52
+ handle(event) {
53
+ switch (event.type) {
54
+ case "message_start": {
55
+ const message = event.message as Message;
56
+ if (message.role === "user") {
57
+ const messageId = `${options.runId}-user-${++userCount}`;
58
+ emit({ type: "message.created", messageId, role: "user" });
59
+ emit({
60
+ type: "message.completed",
61
+ messageId,
62
+ content: [{ type: "text", text: messageText(message.content) }],
63
+ });
64
+ return;
65
+ }
66
+ if (message.role === "assistant") {
67
+ ensureAssistantMessage();
68
+ }
69
+ return;
70
+ }
71
+
72
+ case "message_update": {
73
+ const update = event.assistantMessageEvent;
74
+ if (update.type === "text_delta") {
75
+ emit({
76
+ type: "message.delta",
77
+ messageId: ensureAssistantMessage(),
78
+ text: update.delta,
79
+ });
80
+ }
81
+ if (update.type === "thinking_delta") {
82
+ emit({
83
+ type: "thinking.delta",
84
+ runId: options.runId,
85
+ text: update.delta,
86
+ });
87
+ }
88
+ if (update.type === "thinking_end") {
89
+ emit({
90
+ type: "thinking.completed",
91
+ runId: options.runId,
92
+ text: update.content,
93
+ });
94
+ }
95
+ return;
96
+ }
97
+
98
+ case "message_end": {
99
+ const message = event.message as Message;
100
+ if (message.role === "assistant") completeAssistantMessage(message);
101
+ return;
102
+ }
103
+
104
+ case "tool_execution_start": {
105
+ const messageId = messageIdForTool();
106
+ emit({
107
+ type: "tool.started",
108
+ toolCallId: event.toolCallId,
109
+ messageId,
110
+ name: event.toolName,
111
+ input: event.args,
112
+ });
113
+ return;
114
+ }
115
+
116
+ case "tool_execution_update":
117
+ emit({
118
+ type: "tool.delta",
119
+ toolCallId: event.toolCallId,
120
+ chunk: event.partialResult,
121
+ });
122
+ return;
123
+
124
+ case "tool_execution_end": {
125
+ const output = toolOutput(event.result, event.isError);
126
+ if (event.isError) {
127
+ emit({
128
+ type: "tool.failed",
129
+ toolCallId: event.toolCallId,
130
+ error: {
131
+ message: messageText(output.content),
132
+ details: output.details,
133
+ },
134
+ });
135
+ } else {
136
+ emit({
137
+ type: "tool.completed",
138
+ toolCallId: event.toolCallId,
139
+ output,
140
+ });
141
+ }
142
+ return;
143
+ }
144
+ }
145
+ },
146
+ nextSeq() {
147
+ return seq;
148
+ },
149
+ };
150
+ }
151
+
152
+ function contentToChatContent(content: AssistantMessage["content"]): MessageContent[] {
153
+ return content.flatMap((part): MessageContent[] => {
154
+ if (part.type === "text") return [{ type: "text", text: part.text }];
155
+ if (part.type === "toolCall") return [{ type: "tool", toolCallId: part.id }];
156
+ return [];
157
+ });
158
+ }
159
+
160
+ function messageText(content: unknown): string {
161
+ if (typeof content === "string") return content;
162
+ if (!Array.isArray(content)) return "";
163
+ return content
164
+ .map((part) => typeof part === "object" && part !== null && "text" in part && typeof part.text === "string" ? part.text : "")
165
+ .join("");
166
+ }
167
+
168
+ function toolOutput(result: unknown, isError: boolean): ToolOutput {
169
+ const record = asRecord(result);
170
+ return {
171
+ content: Array.isArray(record.content) ? record.content as ToolOutput["content"] : [],
172
+ details: record.details,
173
+ isError,
174
+ };
175
+ }
176
+
177
+ function asRecord(value: unknown): Record<string, unknown> {
178
+ return typeof value === "object" && value !== null && !Array.isArray(value)
179
+ ? value as Record<string, unknown>
180
+ : {};
181
+ }
@@ -0,0 +1,100 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+
3
+ export type ModelSetupRequirement = "ready" | "select_model" | "connect_auth";
4
+
5
+ export interface ModelSetupProvider {
6
+ id: string;
7
+ label: string;
8
+ envVar: string;
9
+ defaultProvider: string;
10
+ defaultModel: string;
11
+ signupUrl: string;
12
+ }
13
+
14
+ export interface ModelSetupState {
15
+ requirement: ModelSetupRequirement;
16
+ currentModel?: string;
17
+ providers: ModelSetupProvider[];
18
+ availableModels: Array<{ provider: string; id: string; label: string }>;
19
+ }
20
+
21
+ export interface ModelSetupRegistry {
22
+ refresh(): void;
23
+ getAvailable(): Model<Api>[];
24
+ hasConfiguredAuth(model: Model<Api>): boolean;
25
+ }
26
+
27
+ export const modelSetupProviders: ModelSetupProvider[] = [
28
+ {
29
+ id: "google",
30
+ label: "Google Gemini",
31
+ envVar: "GEMINI_API_KEY",
32
+ defaultProvider: "google",
33
+ defaultModel: "gemini-2.5-flash",
34
+ signupUrl: "https://aistudio.google.com/app/apikey",
35
+ },
36
+ {
37
+ id: "openai",
38
+ label: "OpenAI",
39
+ envVar: "OPENAI_API_KEY",
40
+ defaultProvider: "openai",
41
+ defaultModel: "gpt-5-mini",
42
+ signupUrl: "https://platform.openai.com/api-keys",
43
+ },
44
+ {
45
+ id: "anthropic",
46
+ label: "Anthropic",
47
+ envVar: "ANTHROPIC_API_KEY",
48
+ defaultProvider: "anthropic",
49
+ defaultModel: "claude-haiku-4-5",
50
+ signupUrl: "https://console.anthropic.com/settings/keys",
51
+ },
52
+ ];
53
+
54
+ export function buildModelSetupState(
55
+ registry: ModelSetupRegistry,
56
+ currentModel: Model<Api> | undefined,
57
+ ): ModelSetupState {
58
+ registry.refresh();
59
+ const availableModels = sortModels(registry.getAvailable()).map((model) => ({
60
+ provider: model.provider,
61
+ id: model.id,
62
+ label: `${model.provider}/${model.id}`,
63
+ }));
64
+ const requirement =
65
+ currentModel && registry.hasConfiguredAuth(currentModel)
66
+ ? "ready"
67
+ : availableModels.length > 0
68
+ ? "select_model"
69
+ : "connect_auth";
70
+
71
+ return {
72
+ requirement,
73
+ currentModel: currentModel ? `${currentModel.provider}/${currentModel.id}` : undefined,
74
+ providers: modelSetupProviders,
75
+ availableModels,
76
+ };
77
+ }
78
+
79
+ export function findPreferredModel(
80
+ registry: Pick<ModelSetupRegistry, "getAvailable">,
81
+ provider: ModelSetupProvider,
82
+ ): Model<Api> | undefined {
83
+ const available = sortModels(registry.getAvailable(), provider.defaultProvider);
84
+ return (
85
+ available.find(
86
+ (model) =>
87
+ model.provider === provider.defaultProvider && model.id === provider.defaultModel,
88
+ ) ?? available.find((model) => model.provider === provider.defaultProvider)
89
+ );
90
+ }
91
+
92
+ export function sortModels(models: Model<Api>[], preferredProvider?: string): Model<Api>[] {
93
+ return [...models].sort((a, b) => {
94
+ const aPreferred = preferredProvider && a.provider === preferredProvider ? -1 : 0;
95
+ const bPreferred = preferredProvider && b.provider === preferredProvider ? -1 : 0;
96
+ if (aPreferred !== bPreferred) return aPreferred - bPreferred;
97
+ const byProvider = a.provider.localeCompare(b.provider);
98
+ return byProvider !== 0 ? byProvider : a.id.localeCompare(b.id);
99
+ });
100
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@opencandle/gui-server",
3
+ "private": true,
4
+ "type": "module"
5
+ }
@@ -0,0 +1,254 @@
1
+ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
2
+ import type { Message, ToolResultMessage } from "@earendil-works/pi-ai";
3
+
4
+ export interface DashboardState {
5
+ watchlist: Array<{
6
+ symbol: string;
7
+ quote: Record<string, unknown> | null;
8
+ pinned: boolean;
9
+ lastSeen: string;
10
+ }>;
11
+ activeAnalyses: Array<{
12
+ workflowId: string;
13
+ workflow: string;
14
+ symbol?: string;
15
+ analystsTotal: number;
16
+ analystsDone: number;
17
+ startedAt: string;
18
+ }>;
19
+ recentResearch: Array<{
20
+ sessionId: string;
21
+ workflow: string;
22
+ symbol?: string;
23
+ completedAt: string;
24
+ }>;
25
+ dataQuality: {
26
+ softGaps: Array<{ provider: string; lastSeen: string }>;
27
+ hardSkips: Array<{ provider: string; lastSeen: string }>;
28
+ };
29
+ }
30
+
31
+ const DIRECT_TOOL_GAP_PROVIDERS: Record<string, string> = {
32
+ get_company_overview: "alpha_vantage",
33
+ get_financials: "alpha_vantage",
34
+ get_earnings: "alpha_vantage",
35
+ compute_dcf: "alpha_vantage",
36
+ compare_companies: "alpha_vantage",
37
+ get_economic_data: "fred",
38
+ get_twitter_sentiment: "twitter",
39
+ };
40
+
41
+ export function createEmptyDashboardState(): DashboardState {
42
+ return {
43
+ watchlist: [],
44
+ activeAnalyses: [],
45
+ recentResearch: [],
46
+ dataQuality: { softGaps: [], hardSkips: [] },
47
+ };
48
+ }
49
+
50
+ export function projectDashboard(entries: SessionEntry[], sessionId = "local"): DashboardState {
51
+ const state = createEmptyDashboardState();
52
+
53
+ for (const entry of entries) {
54
+ if (entry.type === "message") {
55
+ projectMessage(state, entry.message as Message, entry.timestamp, sessionId);
56
+ continue;
57
+ }
58
+
59
+ if (entry.type === "custom" && entry.customType === "opencandle-workflow") {
60
+ const data = asRecord(entry.data);
61
+ const workflow = stringValue(data.workflow) ?? stringValue(data.workflowType) ?? "workflow";
62
+ const slots = asRecord(data.resolvedSlots);
63
+ const symbol = stringValue(slots.symbol) ?? firstString(slots.symbols);
64
+ state.activeAnalyses.push({
65
+ workflowId: stringValue(data.runId) ?? entry.id,
66
+ workflow,
67
+ symbol,
68
+ analystsTotal: numberValue(data.analystsTotal) ?? 0,
69
+ analystsDone: 0,
70
+ startedAt: entry.timestamp,
71
+ });
72
+ continue;
73
+ }
74
+
75
+ if (entry.type === "custom" && entry.customType === "opencandle-turn-gap") {
76
+ // The accumulator writes a single combined annotation string with one
77
+ // [OPENCANDLE_SKIPPED ... provider=X ...] tag per fallback provider.
78
+ // Older shapes may carry { softGaps: [...] } or { provider } directly —
79
+ // accept either to stay forward/backward compatible.
80
+ const data = asRecord(entry.data);
81
+ const annotation = stringValue(data.annotation);
82
+ if (annotation) {
83
+ for (const provider of parseSkippedProviders(annotation)) {
84
+ upsertProviderGap(state.dataQuality.softGaps, provider, entry.timestamp);
85
+ }
86
+ }
87
+ for (const gap of asArray(data.softGaps)) {
88
+ const provider = stringValue(asRecord(gap).provider);
89
+ if (provider) upsertProviderGap(state.dataQuality.softGaps, provider, entry.timestamp);
90
+ }
91
+ const provider = stringValue(data.provider);
92
+ if (provider) upsertProviderGap(state.dataQuality.softGaps, provider, entry.timestamp);
93
+ }
94
+
95
+ if (entry.type === "custom" && entry.customType === "opencandle-quote-refresh") {
96
+ const data = asRecord(entry.data);
97
+ projectQuote(
98
+ state,
99
+ stringValue(data.symbol),
100
+ asRecord(data.value),
101
+ asArray(data.content) as ToolResultMessage["content"],
102
+ entry.timestamp,
103
+ );
104
+ }
105
+ }
106
+
107
+ return state;
108
+ }
109
+
110
+ function projectMessage(
111
+ state: DashboardState,
112
+ message: Message,
113
+ timestamp: string,
114
+ sessionId: string,
115
+ ): void {
116
+ if (message.role === "toolResult") {
117
+ projectToolResult(state, message, timestamp);
118
+ return;
119
+ }
120
+
121
+ if (message.role === "assistant" && message.stopReason === "stop") {
122
+ const active = state.activeAnalyses.shift();
123
+ if (active) {
124
+ state.recentResearch.unshift({
125
+ sessionId,
126
+ workflow: active.workflow,
127
+ symbol: active.symbol,
128
+ completedAt: timestamp,
129
+ });
130
+ }
131
+ }
132
+ }
133
+
134
+ function projectToolResult(
135
+ state: DashboardState,
136
+ message: ToolResultMessage,
137
+ timestamp: string,
138
+ ): void {
139
+ if (message.toolName === "get_stock_quote") {
140
+ const rawDetails = asRecord(message.details);
141
+ const nestedValue = asRecord(rawDetails.value);
142
+ const details = Object.keys(nestedValue).length > 0 ? nestedValue : rawDetails;
143
+ projectQuote(state, stringValue(details.symbol), details, message.content, timestamp);
144
+ }
145
+
146
+ const text = message.content
147
+ .filter((part) => part.type === "text")
148
+ .map((part) => part.text)
149
+ .join("\n");
150
+ for (const provider of parseSoftGapProviders(text)) {
151
+ upsertProviderGap(state.dataQuality.softGaps, provider, timestamp);
152
+ }
153
+ for (const provider of parseCredentialRequiredProviders(text)) {
154
+ upsertProviderGap(state.dataQuality.hardSkips, provider, timestamp);
155
+ }
156
+ const provider = inferDirectToolGapProvider(message.toolName, text);
157
+ if (provider) upsertProviderGap(state.dataQuality.softGaps, provider, timestamp);
158
+ }
159
+
160
+ function upsertProviderGap(
161
+ gaps: Array<{ provider: string; lastSeen: string }>,
162
+ provider: string,
163
+ lastSeen: string,
164
+ ): void {
165
+ const existing = gaps.find((gap) => gap.provider === provider);
166
+ if (!existing) {
167
+ gaps.push({ provider, lastSeen });
168
+ return;
169
+ }
170
+
171
+ const existingTime = Date.parse(existing.lastSeen);
172
+ const nextTime = Date.parse(lastSeen);
173
+ if (!Number.isFinite(existingTime) || (Number.isFinite(nextTime) && nextTime > existingTime)) {
174
+ existing.lastSeen = lastSeen;
175
+ }
176
+ }
177
+
178
+ function projectQuote(
179
+ state: DashboardState,
180
+ symbolHint: string | undefined,
181
+ details: Record<string, unknown>,
182
+ content: ToolResultMessage["content"],
183
+ timestamp: string,
184
+ ): void {
185
+ const symbol = symbolHint ?? inferSymbolFromContent(content);
186
+ if (!symbol) return;
187
+
188
+ const existing = state.watchlist.find((row) => row.symbol === symbol);
189
+ const row = {
190
+ symbol,
191
+ quote: Object.keys(details).length > 0 ? details : null,
192
+ pinned: existing?.pinned ?? false,
193
+ lastSeen: timestamp,
194
+ };
195
+ if (existing) Object.assign(existing, row);
196
+ else state.watchlist.push(row);
197
+ }
198
+
199
+ function parseCredentialRequiredProviders(text: string): string[] {
200
+ const providers: string[] = [];
201
+ const re = /\[OPENCANDLE_CREDENTIAL_REQUIRED[^\]]*provider=([a-z0-9_-]+)/gi;
202
+ let match: RegExpExecArray | null;
203
+ while ((match = re.exec(text)) !== null) {
204
+ providers.push(match[1]);
205
+ }
206
+ return providers;
207
+ }
208
+
209
+ function parseSkippedProviders(text: string): string[] {
210
+ return parseSoftGapProviders(text);
211
+ }
212
+
213
+ function parseSoftGapProviders(text: string): string[] {
214
+ const providers: string[] = [];
215
+ const re = /\[OPENCANDLE_(?:SKIPPED|SOFT_DEGRADED)[^\]]*provider=([a-z0-9_-]+)/gi;
216
+ let match: RegExpExecArray | null;
217
+ while ((match = re.exec(text)) !== null) {
218
+ providers.push(match[1]);
219
+ }
220
+ return providers;
221
+ }
222
+
223
+ function inferDirectToolGapProvider(toolName: string | undefined, text: string): string | undefined {
224
+ if (!toolName || !/(?:⚠|unavailable|No .*data found|LOGIN_NEEDED)/i.test(text)) return undefined;
225
+ return DIRECT_TOOL_GAP_PROVIDERS[toolName];
226
+ }
227
+
228
+ function inferSymbolFromContent(content: ToolResultMessage["content"]): string | undefined {
229
+ const text = content.find((part) => part.type === "text")?.text;
230
+ const match = text?.match(/^([A-Z]{1,8})\b/);
231
+ return match?.[1];
232
+ }
233
+
234
+ function asRecord(value: unknown): Record<string, unknown> {
235
+ return typeof value === "object" && value !== null && !Array.isArray(value)
236
+ ? value as Record<string, unknown>
237
+ : {};
238
+ }
239
+
240
+ function asArray(value: unknown): unknown[] {
241
+ return Array.isArray(value) ? value : [];
242
+ }
243
+
244
+ function stringValue(value: unknown): string | undefined {
245
+ return typeof value === "string" && value.length > 0 ? value : undefined;
246
+ }
247
+
248
+ function firstString(value: unknown): string | undefined {
249
+ return Array.isArray(value) ? value.find((item) => typeof item === "string") : undefined;
250
+ }
251
+
252
+ function numberValue(value: unknown): number | undefined {
253
+ return typeof value === "number" ? value : undefined;
254
+ }
@@ -0,0 +1,61 @@
1
+ import type { AgentSessionEvent } from "@earendil-works/pi-coding-agent";
2
+
3
+ export interface PromptObservation {
4
+ userTexts: string[];
5
+ sawAssistantOrTool: boolean;
6
+ }
7
+
8
+ export function createPromptObservation(): PromptObservation {
9
+ return {
10
+ userTexts: [],
11
+ sawAssistantOrTool: false,
12
+ };
13
+ }
14
+
15
+ export function observePromptEvent(observation: PromptObservation, event: AgentSessionEvent): void {
16
+ if (event.type === "message_start") {
17
+ const message = event.message as { role?: unknown; content?: unknown };
18
+ if (message.role === "user") {
19
+ observation.userTexts.push(messageTextFromContent(message.content));
20
+ return;
21
+ }
22
+ if (message.role === "assistant") {
23
+ observation.sawAssistantOrTool = true;
24
+ }
25
+ return;
26
+ }
27
+
28
+ if (event.type === "tool_execution_start") {
29
+ observation.sawAssistantOrTool = true;
30
+ }
31
+ }
32
+
33
+ export function selectReplayPrompt(
34
+ observation: PromptObservation,
35
+ originalPrompt: string,
36
+ ): string | undefined {
37
+ if (observation.sawAssistantOrTool) return undefined;
38
+
39
+ const original = originalPrompt.trim();
40
+ const text = [...observation.userTexts]
41
+ .reverse()
42
+ .map((candidate) => candidate.trim())
43
+ .find((candidate) => candidate.length > 0 && candidate !== original);
44
+
45
+ return text || undefined;
46
+ }
47
+
48
+ function messageTextFromContent(content: unknown): string {
49
+ if (typeof content === "string") return content;
50
+ if (!Array.isArray(content)) return "";
51
+ return content
52
+ .map((part) => (
53
+ typeof part === "object" &&
54
+ part !== null &&
55
+ "text" in part &&
56
+ typeof part.text === "string"
57
+ ? part.text
58
+ : ""
59
+ ))
60
+ .join("");
61
+ }