nitrostack 1.0.72 → 1.0.74

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 (370) hide show
  1. package/dist/auth/__tests__/api-key.test.d.ts +2 -0
  2. package/dist/auth/__tests__/api-key.test.d.ts.map +1 -0
  3. package/dist/auth/__tests__/api-key.test.js +136 -0
  4. package/dist/auth/__tests__/api-key.test.js.map +1 -0
  5. package/dist/auth/__tests__/middleware.test.d.ts +2 -0
  6. package/dist/auth/__tests__/middleware.test.d.ts.map +1 -0
  7. package/dist/auth/__tests__/middleware.test.js +186 -0
  8. package/dist/auth/__tests__/middleware.test.js.map +1 -0
  9. package/dist/auth/__tests__/pkce.test.d.ts +2 -0
  10. package/dist/auth/__tests__/pkce.test.d.ts.map +1 -0
  11. package/dist/auth/__tests__/pkce.test.js +61 -0
  12. package/dist/auth/__tests__/pkce.test.js.map +1 -0
  13. package/dist/auth/__tests__/secure-secret.test.d.ts +2 -0
  14. package/dist/auth/__tests__/secure-secret.test.d.ts.map +1 -0
  15. package/dist/auth/__tests__/secure-secret.test.js +42 -0
  16. package/dist/auth/__tests__/secure-secret.test.js.map +1 -0
  17. package/dist/auth/__tests__/simple-jwt.test.d.ts +2 -0
  18. package/dist/auth/__tests__/simple-jwt.test.d.ts.map +1 -0
  19. package/dist/auth/__tests__/simple-jwt.test.js +68 -0
  20. package/dist/auth/__tests__/simple-jwt.test.js.map +1 -0
  21. package/dist/auth/__tests__/token-store.test.d.ts +2 -0
  22. package/dist/auth/__tests__/token-store.test.d.ts.map +1 -0
  23. package/dist/auth/__tests__/token-store.test.js +79 -0
  24. package/dist/auth/__tests__/token-store.test.js.map +1 -0
  25. package/dist/auth/__tests__/token-validation.test.d.ts +2 -0
  26. package/dist/auth/__tests__/token-validation.test.d.ts.map +1 -0
  27. package/dist/auth/__tests__/token-validation.test.js +23 -0
  28. package/dist/auth/__tests__/token-validation.test.js.map +1 -0
  29. package/dist/auth/api-key.js.map +1 -1
  30. package/dist/auth/client.js.map +1 -1
  31. package/dist/auth/index.d.ts +2 -1
  32. package/dist/auth/index.d.ts.map +1 -1
  33. package/dist/auth/index.js +3 -0
  34. package/dist/auth/index.js.map +1 -1
  35. package/dist/auth/middleware.d.ts +1 -1
  36. package/dist/auth/middleware.d.ts.map +1 -1
  37. package/dist/auth/middleware.js.map +1 -1
  38. package/dist/auth/secure-secret.d.ts +136 -0
  39. package/dist/auth/secure-secret.d.ts.map +1 -0
  40. package/dist/auth/secure-secret.js +182 -0
  41. package/dist/auth/secure-secret.js.map +1 -0
  42. package/dist/auth/server-metadata.d.ts.map +1 -1
  43. package/dist/auth/server-metadata.js.map +1 -1
  44. package/dist/auth/simple-jwt.d.ts +100 -14
  45. package/dist/auth/simple-jwt.d.ts.map +1 -1
  46. package/dist/auth/simple-jwt.js +19 -9
  47. package/dist/auth/simple-jwt.js.map +1 -1
  48. package/dist/auth/token-store.js +1 -1
  49. package/dist/auth/token-store.js.map +1 -1
  50. package/dist/auth/token-validation.js +1 -1
  51. package/dist/auth/token-validation.js.map +1 -1
  52. package/dist/cli/__tests__/cli-dev.test.d.ts +2 -0
  53. package/dist/cli/__tests__/cli-dev.test.d.ts.map +1 -0
  54. package/dist/cli/__tests__/cli-dev.test.js +83 -0
  55. package/dist/cli/__tests__/cli-dev.test.js.map +1 -0
  56. package/dist/cli/__tests__/cli-start.test.d.ts +2 -0
  57. package/dist/cli/__tests__/cli-start.test.d.ts.map +1 -0
  58. package/dist/cli/__tests__/cli-start.test.js +61 -0
  59. package/dist/cli/__tests__/cli-start.test.js.map +1 -0
  60. package/dist/cli/__tests__/cli.test.d.ts +2 -0
  61. package/dist/cli/__tests__/cli.test.d.ts.map +1 -0
  62. package/dist/cli/__tests__/cli.test.js +73 -0
  63. package/dist/cli/__tests__/cli.test.js.map +1 -0
  64. package/dist/cli/__tests__/commands.test.d.ts +2 -0
  65. package/dist/cli/__tests__/commands.test.d.ts.map +1 -0
  66. package/dist/cli/__tests__/commands.test.js +64 -0
  67. package/dist/cli/__tests__/commands.test.js.map +1 -0
  68. package/dist/cli/commands/build.js +1 -1
  69. package/dist/cli/commands/build.js.map +1 -1
  70. package/dist/cli/commands/generate-types.js +12 -12
  71. package/dist/cli/commands/generate-types.js.map +1 -1
  72. package/dist/cli/commands/generate.d.ts +8 -1
  73. package/dist/cli/commands/generate.d.ts.map +1 -1
  74. package/dist/cli/commands/generate.js +13 -12
  75. package/dist/cli/commands/generate.js.map +1 -1
  76. package/dist/cli/commands/init.js +1 -1
  77. package/dist/cli/commands/init.js.map +1 -1
  78. package/dist/cli/commands/upgrade.d.ts +10 -0
  79. package/dist/cli/commands/upgrade.d.ts.map +1 -0
  80. package/dist/cli/commands/upgrade.js +221 -0
  81. package/dist/cli/commands/upgrade.js.map +1 -0
  82. package/dist/cli/index.d.ts +2 -1
  83. package/dist/cli/index.d.ts.map +1 -1
  84. package/dist/cli/index.js +69 -39
  85. package/dist/cli/index.js.map +1 -1
  86. package/dist/core/__tests__/core-modules.test.d.ts +2 -0
  87. package/dist/core/__tests__/core-modules.test.d.ts.map +1 -0
  88. package/dist/core/__tests__/core-modules.test.js +36 -0
  89. package/dist/core/__tests__/core-modules.test.js.map +1 -0
  90. package/dist/core/__tests__/models.test.d.ts +2 -0
  91. package/dist/core/__tests__/models.test.d.ts.map +1 -0
  92. package/dist/core/__tests__/models.test.js +142 -0
  93. package/dist/core/__tests__/models.test.js.map +1 -0
  94. package/dist/core/__tests__/module.test.d.ts +2 -0
  95. package/dist/core/__tests__/module.test.d.ts.map +1 -0
  96. package/dist/core/__tests__/module.test.js +60 -0
  97. package/dist/core/__tests__/module.test.js.map +1 -0
  98. package/dist/core/__tests__/server.test.d.ts +2 -0
  99. package/dist/core/__tests__/server.test.d.ts.map +1 -0
  100. package/dist/core/__tests__/server.test.js +120 -0
  101. package/dist/core/__tests__/server.test.js.map +1 -0
  102. package/dist/core/__tests__/transports.test.d.ts +2 -0
  103. package/dist/core/__tests__/transports.test.d.ts.map +1 -0
  104. package/dist/core/__tests__/transports.test.js +12 -0
  105. package/dist/core/__tests__/transports.test.js.map +1 -0
  106. package/dist/core/app-decorator.d.ts +4 -3
  107. package/dist/core/app-decorator.d.ts.map +1 -1
  108. package/dist/core/app-decorator.js +67 -28
  109. package/dist/core/app-decorator.js.map +1 -1
  110. package/dist/core/builders.d.ts +19 -7
  111. package/dist/core/builders.d.ts.map +1 -1
  112. package/dist/core/builders.js +15 -8
  113. package/dist/core/builders.js.map +1 -1
  114. package/dist/core/component.d.ts +8 -8
  115. package/dist/core/component.d.ts.map +1 -1
  116. package/dist/core/component.js +3 -2
  117. package/dist/core/component.js.map +1 -1
  118. package/dist/core/config-module.d.ts +11 -4
  119. package/dist/core/config-module.d.ts.map +1 -1
  120. package/dist/core/config-module.js +1 -1
  121. package/dist/core/config-module.js.map +1 -1
  122. package/dist/core/decorators/__tests__/decorators.test.d.ts +2 -0
  123. package/dist/core/decorators/__tests__/decorators.test.d.ts.map +1 -0
  124. package/dist/core/decorators/__tests__/decorators.test.js +103 -0
  125. package/dist/core/decorators/__tests__/decorators.test.js.map +1 -0
  126. package/dist/core/decorators/__tests__/extended-decorators.test.d.ts +2 -0
  127. package/dist/core/decorators/__tests__/extended-decorators.test.d.ts.map +1 -0
  128. package/dist/core/decorators/__tests__/extended-decorators.test.js +194 -0
  129. package/dist/core/decorators/__tests__/extended-decorators.test.js.map +1 -0
  130. package/dist/core/decorators/cache.decorator.d.ts +9 -9
  131. package/dist/core/decorators/cache.decorator.d.ts.map +1 -1
  132. package/dist/core/decorators/cache.decorator.js +3 -3
  133. package/dist/core/decorators/cache.decorator.js.map +1 -1
  134. package/dist/core/decorators/health-check.decorator.d.ts +3 -3
  135. package/dist/core/decorators/health-check.decorator.d.ts.map +1 -1
  136. package/dist/core/decorators/health-check.decorator.js +2 -2
  137. package/dist/core/decorators/health-check.decorator.js.map +1 -1
  138. package/dist/core/decorators/rate-limit.decorator.d.ts +5 -4
  139. package/dist/core/decorators/rate-limit.decorator.d.ts.map +1 -1
  140. package/dist/core/decorators/rate-limit.decorator.js +3 -3
  141. package/dist/core/decorators/rate-limit.decorator.js.map +1 -1
  142. package/dist/core/decorators.d.ts +47 -29
  143. package/dist/core/decorators.d.ts.map +1 -1
  144. package/dist/core/decorators.js +9 -9
  145. package/dist/core/decorators.js.map +1 -1
  146. package/dist/core/di/__tests__/container.test.d.ts +2 -0
  147. package/dist/core/di/__tests__/container.test.d.ts.map +1 -0
  148. package/dist/core/di/__tests__/container.test.js +88 -0
  149. package/dist/core/di/__tests__/container.test.js.map +1 -0
  150. package/dist/core/di/container.d.ts +21 -4
  151. package/dist/core/di/container.d.ts.map +1 -1
  152. package/dist/core/di/container.js +11 -7
  153. package/dist/core/di/container.js.map +1 -1
  154. package/dist/core/di/injectable.decorator.d.ts +5 -3
  155. package/dist/core/di/injectable.decorator.d.ts.map +1 -1
  156. package/dist/core/di/injectable.decorator.js.map +1 -1
  157. package/dist/core/errors.d.ts +4 -4
  158. package/dist/core/errors.d.ts.map +1 -1
  159. package/dist/core/errors.js.map +1 -1
  160. package/dist/core/events/__tests__/events.test.d.ts +2 -0
  161. package/dist/core/events/__tests__/events.test.d.ts.map +1 -0
  162. package/dist/core/events/__tests__/events.test.js +177 -0
  163. package/dist/core/events/__tests__/events.test.js.map +1 -0
  164. package/dist/core/events/event-emitter.d.ts +3 -3
  165. package/dist/core/events/event-emitter.d.ts.map +1 -1
  166. package/dist/core/events/event-emitter.js.map +1 -1
  167. package/dist/core/events/event.decorator.d.ts +5 -5
  168. package/dist/core/events/event.decorator.d.ts.map +1 -1
  169. package/dist/core/events/event.decorator.js +10 -6
  170. package/dist/core/events/event.decorator.js.map +1 -1
  171. package/dist/core/events/log-emitter.d.ts +7 -1
  172. package/dist/core/events/log-emitter.d.ts.map +1 -1
  173. package/dist/core/events/log-emitter.js.map +1 -1
  174. package/dist/core/filters/__tests__/filters.test.d.ts +2 -0
  175. package/dist/core/filters/__tests__/filters.test.d.ts.map +1 -0
  176. package/dist/core/filters/__tests__/filters.test.js +72 -0
  177. package/dist/core/filters/__tests__/filters.test.js.map +1 -0
  178. package/dist/core/filters/exception-filter.decorator.d.ts +5 -5
  179. package/dist/core/filters/exception-filter.decorator.d.ts.map +1 -1
  180. package/dist/core/filters/exception-filter.decorator.js +3 -3
  181. package/dist/core/filters/exception-filter.decorator.js.map +1 -1
  182. package/dist/core/filters/exception-filter.interface.d.ts +14 -5
  183. package/dist/core/filters/exception-filter.interface.d.ts.map +1 -1
  184. package/dist/core/guards/__tests__/guards.test.d.ts +2 -0
  185. package/dist/core/guards/__tests__/guards.test.d.ts.map +1 -0
  186. package/dist/core/guards/__tests__/guards.test.js +55 -0
  187. package/dist/core/guards/__tests__/guards.test.js.map +1 -0
  188. package/dist/core/guards/apikey.guard.d.ts +1 -1
  189. package/dist/core/guards/apikey.guard.d.ts.map +1 -1
  190. package/dist/core/guards/guard.interface.d.ts +1 -1
  191. package/dist/core/guards/guard.interface.d.ts.map +1 -1
  192. package/dist/core/guards/jwt.guard.d.ts +1 -1
  193. package/dist/core/guards/jwt.guard.d.ts.map +1 -1
  194. package/dist/core/guards/oauth.guard.d.ts +1 -1
  195. package/dist/core/guards/oauth.guard.d.ts.map +1 -1
  196. package/dist/core/guards/use-guards.decorator.d.ts +3 -3
  197. package/dist/core/guards/use-guards.decorator.d.ts.map +1 -1
  198. package/dist/core/guards/use-guards.decorator.js +1 -1
  199. package/dist/core/guards/use-guards.decorator.js.map +1 -1
  200. package/dist/core/health/__tests__/health.test.d.ts +2 -0
  201. package/dist/core/health/__tests__/health.test.d.ts.map +1 -0
  202. package/dist/core/health/__tests__/health.test.js +31 -0
  203. package/dist/core/health/__tests__/health.test.js.map +1 -0
  204. package/dist/core/index.d.ts +2 -2
  205. package/dist/core/index.d.ts.map +1 -1
  206. package/dist/core/index.js.map +1 -1
  207. package/dist/core/interceptors/__tests__/interceptors.test.d.ts +2 -0
  208. package/dist/core/interceptors/__tests__/interceptors.test.d.ts.map +1 -0
  209. package/dist/core/interceptors/__tests__/interceptors.test.js +52 -0
  210. package/dist/core/interceptors/__tests__/interceptors.test.js.map +1 -0
  211. package/dist/core/interceptors/interceptor.decorator.d.ts +4 -4
  212. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  213. package/dist/core/interceptors/interceptor.decorator.js +2 -2
  214. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  215. package/dist/core/interceptors/interceptor.interface.d.ts +3 -3
  216. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  217. package/dist/core/logger.d.ts.map +1 -1
  218. package/dist/core/logger.js.map +1 -1
  219. package/dist/core/middleware/__tests__/middleware.test.d.ts +2 -0
  220. package/dist/core/middleware/__tests__/middleware.test.d.ts.map +1 -0
  221. package/dist/core/middleware/__tests__/middleware.test.js +105 -0
  222. package/dist/core/middleware/__tests__/middleware.test.js.map +1 -0
  223. package/dist/core/middleware/middleware.decorator.d.ts +4 -4
  224. package/dist/core/middleware/middleware.decorator.d.ts.map +1 -1
  225. package/dist/core/middleware/middleware.decorator.js +2 -2
  226. package/dist/core/middleware/middleware.decorator.js.map +1 -1
  227. package/dist/core/middleware/middleware.interface.d.ts +3 -3
  228. package/dist/core/middleware/middleware.interface.d.ts.map +1 -1
  229. package/dist/core/module.d.ts +33 -14
  230. package/dist/core/module.d.ts.map +1 -1
  231. package/dist/core/module.js +11 -6
  232. package/dist/core/module.js.map +1 -1
  233. package/dist/core/oauth-module.d.ts +9 -3
  234. package/dist/core/oauth-module.d.ts.map +1 -1
  235. package/dist/core/oauth-module.js +4 -3
  236. package/dist/core/oauth-module.js.map +1 -1
  237. package/dist/core/pipes/__tests__/pipes.test.d.ts +2 -0
  238. package/dist/core/pipes/__tests__/pipes.test.d.ts.map +1 -0
  239. package/dist/core/pipes/__tests__/pipes.test.js +52 -0
  240. package/dist/core/pipes/__tests__/pipes.test.js.map +1 -0
  241. package/dist/core/pipes/pipe.decorator.d.ts +14 -5
  242. package/dist/core/pipes/pipe.decorator.d.ts.map +1 -1
  243. package/dist/core/pipes/pipe.decorator.js +2 -2
  244. package/dist/core/pipes/pipe.decorator.js.map +1 -1
  245. package/dist/core/pipes/pipe.interface.d.ts +9 -4
  246. package/dist/core/pipes/pipe.interface.d.ts.map +1 -1
  247. package/dist/core/prompt.d.ts +13 -4
  248. package/dist/core/prompt.d.ts.map +1 -1
  249. package/dist/core/prompt.js +2 -2
  250. package/dist/core/prompt.js.map +1 -1
  251. package/dist/core/resource.d.ts +7 -2
  252. package/dist/core/resource.d.ts.map +1 -1
  253. package/dist/core/resource.js +2 -2
  254. package/dist/core/resource.js.map +1 -1
  255. package/dist/core/server.d.ts +49 -3
  256. package/dist/core/server.d.ts.map +1 -1
  257. package/dist/core/server.js +61 -34
  258. package/dist/core/server.js.map +1 -1
  259. package/dist/core/tool.d.ts +44 -16
  260. package/dist/core/tool.d.ts.map +1 -1
  261. package/dist/core/tool.js +19 -6
  262. package/dist/core/tool.js.map +1 -1
  263. package/dist/core/transports/__tests__/transports.test.d.ts +2 -0
  264. package/dist/core/transports/__tests__/transports.test.d.ts.map +1 -0
  265. package/dist/core/transports/__tests__/transports.test.js +249 -0
  266. package/dist/core/transports/__tests__/transports.test.js.map +1 -0
  267. package/dist/core/transports/discovery-http-server.d.ts +7 -1
  268. package/dist/core/transports/discovery-http-server.d.ts.map +1 -1
  269. package/dist/core/transports/discovery-http-server.js.map +1 -1
  270. package/dist/core/transports/http-server.d.ts +2 -2
  271. package/dist/core/transports/http-server.d.ts.map +1 -1
  272. package/dist/core/transports/http-server.js +1 -1
  273. package/dist/core/transports/http-server.js.map +1 -1
  274. package/dist/core/transports/streamable-http.d.ts +4 -4
  275. package/dist/core/transports/streamable-http.d.ts.map +1 -1
  276. package/dist/core/transports/streamable-http.js +1 -1
  277. package/dist/core/transports/streamable-http.js.map +1 -1
  278. package/dist/core/types.d.ts +87 -15
  279. package/dist/core/types.d.ts.map +1 -1
  280. package/dist/core/widgets/__tests__/registry.test.d.ts +2 -0
  281. package/dist/core/widgets/__tests__/registry.test.d.ts.map +1 -0
  282. package/dist/core/widgets/__tests__/registry.test.js +69 -0
  283. package/dist/core/widgets/__tests__/registry.test.js.map +1 -0
  284. package/dist/core/widgets/widget-registry.d.ts +2 -2
  285. package/dist/core/widgets/widget-registry.d.ts.map +1 -1
  286. package/dist/core/widgets/widget-registry.js +1 -1
  287. package/dist/core/widgets/widget-registry.js.map +1 -1
  288. package/dist/testing/__tests__/testing.test.d.ts +2 -0
  289. package/dist/testing/__tests__/testing.test.d.ts.map +1 -0
  290. package/dist/testing/__tests__/testing.test.js +167 -0
  291. package/dist/testing/__tests__/testing.test.js.map +1 -0
  292. package/dist/testing/index.d.ts +40 -19
  293. package/dist/testing/index.d.ts.map +1 -1
  294. package/dist/testing/index.js +5 -8
  295. package/dist/testing/index.js.map +1 -1
  296. package/dist/ui-next/__tests__/ui-next.test.d.ts +2 -0
  297. package/dist/ui-next/__tests__/ui-next.test.d.ts.map +1 -0
  298. package/dist/ui-next/__tests__/ui-next.test.js +74 -0
  299. package/dist/ui-next/__tests__/ui-next.test.js.map +1 -0
  300. package/dist/ui-next/index.d.ts +1 -1
  301. package/dist/ui-next/index.d.ts.map +1 -1
  302. package/dist/ui-next/index.js.map +1 -1
  303. package/dist/widgets/__tests__/utils.test.d.ts +2 -0
  304. package/dist/widgets/__tests__/utils.test.d.ts.map +1 -0
  305. package/dist/widgets/__tests__/utils.test.js +80 -0
  306. package/dist/widgets/__tests__/utils.test.js.map +1 -0
  307. package/dist/widgets/__tests__/widgets.test.d.ts +2 -0
  308. package/dist/widgets/__tests__/widgets.test.d.ts.map +1 -0
  309. package/dist/widgets/__tests__/widgets.test.js +245 -0
  310. package/dist/widgets/__tests__/widgets.test.js.map +1 -0
  311. package/dist/widgets/hooks/useWidgetSDK.d.ts +5 -5
  312. package/dist/widgets/runtime/WidgetLayout.js.map +1 -1
  313. package/dist/widgets/sdk.d.ts +5 -5
  314. package/dist/widgets/sdk.d.ts.map +1 -1
  315. package/dist/widgets/sdk.js.map +1 -1
  316. package/package.json +1 -1
  317. package/src/studio/app/api/auth/fetch-metadata/route.ts +3 -2
  318. package/src/studio/app/api/auth/register-client/route.ts +3 -2
  319. package/src/studio/app/api/chat/route.ts +31 -17
  320. package/src/studio/app/api/health/checks/route.ts +5 -4
  321. package/src/studio/app/api/init/route.ts +3 -2
  322. package/src/studio/app/api/ping/route.ts +3 -2
  323. package/src/studio/app/api/prompts/[name]/route.ts +4 -3
  324. package/src/studio/app/api/prompts/route.ts +3 -2
  325. package/src/studio/app/api/resources/[...uri]/route.ts +3 -2
  326. package/src/studio/app/api/resources/route.ts +3 -2
  327. package/src/studio/app/api/roots/route.ts +3 -2
  328. package/src/studio/app/api/sampling/route.ts +3 -2
  329. package/src/studio/app/api/tools/[name]/call/route.ts +3 -2
  330. package/src/studio/app/api/tools/route.ts +4 -3
  331. package/src/studio/app/api/widget-examples/route.ts +5 -4
  332. package/src/studio/app/auth/callback/page.tsx +3 -2
  333. package/src/studio/app/chat/page.tsx +481 -105
  334. package/src/studio/app/health/page.tsx +1 -1
  335. package/src/studio/app/logs/page.tsx +2 -2
  336. package/src/studio/app/page.tsx +5 -5
  337. package/src/studio/app/prompts/page.tsx +2 -2
  338. package/src/studio/app/settings/page.tsx +3 -2
  339. package/src/studio/app/tools/page.tsx +3 -3
  340. package/src/studio/components/LogMessage.tsx +1 -1
  341. package/src/studio/components/MarkdownRenderer.tsx +245 -348
  342. package/src/studio/components/Sidebar.tsx +18 -3
  343. package/src/studio/components/VoiceOrbOverlay.tsx +12 -6
  344. package/src/studio/components/WidgetErrorBoundary.tsx +49 -0
  345. package/src/studio/components/WidgetRenderer.tsx +168 -215
  346. package/src/studio/components/ops/OpsCanvas.tsx +748 -0
  347. package/src/studio/components/ops/OpsNodeDetailPanel.tsx +150 -0
  348. package/src/studio/components/ops/OpsSummaryBar.tsx +90 -0
  349. package/src/studio/components/ops/index.ts +6 -0
  350. package/src/studio/components/ops/nodes/BaseNode.tsx +65 -0
  351. package/src/studio/components/ops/nodes/LLMCallNode.tsx +34 -0
  352. package/src/studio/components/ops/nodes/LLMResponseNode.tsx +33 -0
  353. package/src/studio/components/ops/nodes/ToolCallNode.tsx +30 -0
  354. package/src/studio/components/ops/nodes/ToolResultNode.tsx +43 -0
  355. package/src/studio/components/ops/nodes/UserPromptNode.tsx +34 -0
  356. package/src/studio/components/ops/nodes/WidgetRenderNode.tsx +23 -0
  357. package/src/studio/components/ops/nodes/index.ts +9 -0
  358. package/src/studio/components/tools/ToolsCanvas.tsx +2 -2
  359. package/src/studio/lib/api.ts +61 -42
  360. package/src/studio/lib/http-client-transport.ts +2 -2
  361. package/src/studio/lib/llm-service.ts +126 -47
  362. package/src/studio/lib/mcp-client.ts +9 -6
  363. package/src/studio/lib/ops-store.ts +427 -0
  364. package/src/studio/lib/ops-tracker.ts +416 -0
  365. package/src/studio/lib/ops-types.ts +164 -0
  366. package/src/studio/lib/store.ts +8 -11
  367. package/src/studio/lib/types.ts +228 -38
  368. package/src/studio/lib/widget-loader.ts +2 -2
  369. package/templates/typescript-oauth/src/modules/flights/flights.prompts.ts +19 -22
  370. package/dist/cli/build-widgets.mjs +0 -165
@@ -1,11 +1,16 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef, useState } from 'react';
3
+ import { useEffect, useRef, useState, useCallback } from 'react';
4
4
  import { useStudioStore } from '@/lib/store';
5
+ import { useOpsStore } from '@/lib/ops-store';
6
+ import { opsTracker, clearOpsSession } from '@/lib/ops-tracker';
7
+ import type { LLMProvider } from '@/lib/ops-types';
5
8
  import { api } from '@/lib/api';
6
9
  import { WidgetRenderer } from '@/components/WidgetRenderer';
10
+ import { WidgetErrorBoundary } from '@/components/WidgetErrorBoundary';
7
11
  import { MarkdownRenderer } from '@/components/MarkdownRenderer';
8
12
  import { VoiceOrbOverlay, MiniVoiceOrb } from '@/components/VoiceOrbOverlay';
13
+ import { OpsCanvas } from '@/components/ops/OpsCanvas';
9
14
  import type { ChatMessage, Tool, ToolCall, Prompt } from '@/lib/types';
10
15
  import {
11
16
  SparklesIcon,
@@ -23,16 +28,104 @@ import {
23
28
  EllipsisVerticalIcon,
24
29
  MicrophoneIcon,
25
30
  SpeakerWaveIcon,
26
- StopIcon
31
+ StopIcon,
27
32
  } from '@heroicons/react/24/outline';
28
33
 
29
34
  // Add type for webkitSpeechRecognition
30
35
  declare global {
31
36
  interface Window {
32
- webkitSpeechRecognition: any;
37
+ webkitSpeechRecognition?: typeof SpeechRecognition;
33
38
  }
34
39
  }
35
40
 
41
+ // =============================================================================
42
+ // Token Optimization: Reduce token count for LLM API calls
43
+ // =============================================================================
44
+
45
+ const MAX_HISTORY_MESSAGES = 20; // Sliding window size
46
+ const MAX_TOOL_RESULT_LENGTH = 2000; // Truncate large tool results
47
+ const MAX_CONTENT_LENGTH = 4000; // Truncate very long content
48
+
49
+ /**
50
+ * Optimize messages for LLM to reduce token count while preserving context
51
+ * - Strips widget data (result) from toolCalls (only needed for UI)
52
+ * - Truncates large tool results
53
+ * - Applies sliding window for message history
54
+ * - Removes file data from old messages
55
+ */
56
+ function optimizeMessagesForLLM(messages: ChatMessage[]): ChatMessage[] {
57
+ // Apply sliding window - keep last N messages
58
+ // But always keep system messages and the most recent exchange
59
+ const recentMessages = messages.length > MAX_HISTORY_MESSAGES
60
+ ? messages.slice(-MAX_HISTORY_MESSAGES)
61
+ : messages;
62
+
63
+ return recentMessages.map((msg, idx) => {
64
+ const isLastMessage = idx === recentMessages.length - 1;
65
+
66
+ const cleaned: ChatMessage = {
67
+ role: msg.role,
68
+ content: msg.content || '',
69
+ };
70
+
71
+ // Strip result from toolCalls - LLM already gets results via tool role messages
72
+ // The result is only needed for UI widget rendering, not for LLM context
73
+ if (msg.toolCalls && msg.toolCalls.length > 0) {
74
+ cleaned.toolCalls = msg.toolCalls.map(tc => ({
75
+ id: tc.id,
76
+ name: tc.name,
77
+ arguments: tc.arguments,
78
+ // result intentionally omitted - saves significant tokens!
79
+ }));
80
+ }
81
+
82
+ if (msg.toolCallId) {
83
+ cleaned.toolCallId = msg.toolCallId;
84
+ }
85
+
86
+ // Truncate large tool results to save tokens
87
+ if (msg.role === 'tool' && cleaned.content.length > MAX_TOOL_RESULT_LENGTH) {
88
+ // Try to parse and summarize JSON results
89
+ try {
90
+ const parsed = JSON.parse(cleaned.content);
91
+ if (Array.isArray(parsed)) {
92
+ // For arrays (like flight results), keep first few items
93
+ const truncated = parsed.slice(0, 3);
94
+ cleaned.content = JSON.stringify(truncated) +
95
+ `\n[... ${parsed.length - 3} more items truncated for efficiency]`;
96
+ } else if (typeof parsed === 'object') {
97
+ // For objects, stringify with limit
98
+ cleaned.content = cleaned.content.substring(0, MAX_TOOL_RESULT_LENGTH) +
99
+ '\n[Result truncated for context efficiency]';
100
+ }
101
+ } catch {
102
+ // Not JSON, just truncate
103
+ cleaned.content = cleaned.content.substring(0, MAX_TOOL_RESULT_LENGTH) +
104
+ '\n[Result truncated for context efficiency]';
105
+ }
106
+ }
107
+
108
+ // Truncate very long assistant/user content (rare but possible)
109
+ if ((msg.role === 'assistant' || msg.role === 'user') &&
110
+ cleaned.content.length > MAX_CONTENT_LENGTH) {
111
+ cleaned.content = cleaned.content.substring(0, MAX_CONTENT_LENGTH) +
112
+ '\n[Content truncated]';
113
+ }
114
+
115
+ // Only include file data for the CURRENT message, not history
116
+ // Images are expensive (base64) and LLM already saw them
117
+ if (msg.file && isLastMessage) {
118
+ cleaned.file = msg.file;
119
+ }
120
+ // Old messages with files: just note that an image was attached
121
+ else if (msg.file && !isLastMessage) {
122
+ cleaned.content = cleaned.content || '[Image was attached]';
123
+ }
124
+
125
+ return cleaned;
126
+ });
127
+ }
128
+
36
129
  export default function ChatPage() {
37
130
  const {
38
131
  chatMessages,
@@ -48,6 +141,9 @@ export default function ChatPage() {
48
141
  setElevenLabsApiKey
49
142
  } = useStudioStore();
50
143
 
144
+ // Ops state
145
+ const { isOpsViewOpen, toggleOpsView, clearSession: clearOpsSessionStore } = useOpsStore();
146
+
51
147
  // ... (existing helper methods)
52
148
  const getAuthTokens = () => {
53
149
  const state = useStudioStore.getState();
@@ -64,7 +160,11 @@ export default function ChatPage() {
64
160
  const [prompts, setPrompts] = useState<Prompt[]>([]);
65
161
  const [selectedPrompt, setSelectedPrompt] = useState<Prompt | null>(null);
66
162
  const [promptArgs, setPromptArgs] = useState<Record<string, string>>({});
67
- const [fullscreenWidget, setFullscreenWidget] = useState<{ uri: string, data: any } | null>(null);
163
+ const [fullscreenWidget, setFullscreenWidget] = useState<{ uri: string, data: unknown } | null>(null);
164
+
165
+ // API key inputs - start empty to avoid hydration mismatch
166
+ const [openaiApiKeyInput, setOpenaiApiKeyInput] = useState('');
167
+ const [geminiApiKeyInput, setGeminiApiKeyInput] = useState('');
68
168
 
69
169
  // Language presets for quick selection
70
170
  const LANG_PRESETS: Record<string, { model: string; voice: string; input: string; name: string; greeting: string }> = {
@@ -86,31 +186,31 @@ export default function ChatPage() {
86
186
  const [voiceDisplayMode, setVoiceDisplayMode] = useState<'voice-only' | 'voice-chat'>('voice-only');
87
187
  const [showVoiceSettings, setShowVoiceSettings] = useState(false);
88
188
 
89
- // Voice Configuration - load from localStorage
90
- const [voiceModel, setVoiceModel] = useState(() => {
91
- if (typeof window !== 'undefined') {
92
- return localStorage.getItem('voice_model') || 'eleven_multilingual_v2';
93
- }
94
- return 'eleven_multilingual_v2';
95
- });
96
- const [outputLanguage, setOutputLanguage] = useState(() => {
97
- if (typeof window !== 'undefined') {
98
- return localStorage.getItem('output_language') || 'en';
99
- }
100
- return 'en';
101
- });
102
- const [inputLanguage, setInputLanguage] = useState(() => {
103
- if (typeof window !== 'undefined') {
104
- return localStorage.getItem('input_language') || 'en-US';
105
- }
106
- return 'en-US';
107
- });
108
- const [voiceId, setVoiceId] = useState(() => {
109
- if (typeof window !== 'undefined') {
110
- return localStorage.getItem('voice_id') || '21m00Tcm4TlvDq8ikWAM';
111
- }
112
- return '21m00Tcm4TlvDq8ikWAM';
113
- });
189
+ // Voice Configuration - use defaults initially, load from localStorage in useEffect
190
+ const [voiceModel, setVoiceModel] = useState('eleven_multilingual_v2');
191
+ const [outputLanguage, setOutputLanguage] = useState('en');
192
+ const [inputLanguage, setInputLanguage] = useState('en-US');
193
+ const [voiceId, setVoiceId] = useState('21m00Tcm4TlvDq8ikWAM');
194
+
195
+ // Load settings from localStorage after mount (avoids hydration mismatch)
196
+ useEffect(() => {
197
+ // Voice settings
198
+ const savedVoiceModel = localStorage.getItem('voice_model');
199
+ const savedOutputLang = localStorage.getItem('output_language');
200
+ const savedInputLang = localStorage.getItem('input_language');
201
+ const savedVoiceId = localStorage.getItem('voice_id');
202
+
203
+ if (savedVoiceModel) setVoiceModel(savedVoiceModel);
204
+ if (savedOutputLang) setOutputLanguage(savedOutputLang);
205
+ if (savedInputLang) setInputLanguage(savedInputLang);
206
+ if (savedVoiceId) setVoiceId(savedVoiceId);
207
+
208
+ // API keys
209
+ const savedOpenaiKey = localStorage.getItem('openai_api_key');
210
+ const savedGeminiKey = localStorage.getItem('gemini_api_key');
211
+ if (savedOpenaiKey) setOpenaiApiKeyInput(savedOpenaiKey);
212
+ if (savedGeminiKey) setGeminiApiKeyInput(savedGeminiKey);
213
+ }, []);
114
214
 
115
215
  // Dynamic API data
116
216
  interface ElevenLabsModel {
@@ -136,6 +236,24 @@ export default function ChatPage() {
136
236
  const textareaRef = useRef<HTMLTextAreaElement>(null);
137
237
  const initialToolExecuted = useRef(false);
138
238
 
239
+ // Load tools and prompts on mount
240
+ useEffect(() => {
241
+ loadTools();
242
+ loadPrompts();
243
+ }, []);
244
+
245
+ // Auto-execute initial tool when tools are loaded
246
+ useEffect(() => {
247
+ if (tools.length > 0 && !initialToolExecuted.current) {
248
+ checkAndRunInitialTool();
249
+ }
250
+ }, [tools]);
251
+
252
+ // Scroll to bottom when messages change
253
+ useEffect(() => {
254
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
255
+ }, [chatMessages]);
256
+
139
257
  // Fetch ElevenLabs models when settings opens
140
258
  useEffect(() => {
141
259
  if ((!showVoiceSettings && !showSettings) || !elevenLabsApiKey) return;
@@ -189,7 +307,13 @@ export default function ChatPage() {
189
307
  let sharedVoices: ElevenLabsVoice[] = [];
190
308
  if (sharedVoicesRes.ok) {
191
309
  const data = await sharedVoicesRes.json();
192
- sharedVoices = (data.voices || []).map((v: any) => ({
310
+ interface SharedVoiceData {
311
+ voice_id: string;
312
+ name: string;
313
+ accent?: string;
314
+ language?: string;
315
+ }
316
+ sharedVoices = (data.voices || []).map((v: SharedVoiceData) => ({
193
317
  voice_id: v.voice_id,
194
318
  name: v.name,
195
319
  labels: { accent: v.accent || v.language },
@@ -230,6 +354,119 @@ export default function ChatPage() {
230
354
  }
231
355
  }, [chatMessages, voiceModeEnabled, voiceOverlayOpen, elevenLabsApiKey]);
232
356
 
357
+ // Listen for widget-to-MCP tool calls
358
+ useEffect(() => {
359
+ const handleWidgetToolCall = async (event: CustomEvent<{ toolName: string; toolArgs: Record<string, unknown> }>) => {
360
+ const { toolName, toolArgs } = event.detail;
361
+ console.log('🔧 Widget tool call received:', toolName, toolArgs);
362
+
363
+ try {
364
+ setLoading(true);
365
+ const { jwtToken, mcpApiKey } = getAuthTokens();
366
+ const effectiveToken = jwtToken || useStudioStore.getState().oauthState?.currentToken;
367
+
368
+ // Track ops: Start tool call from widget interaction
369
+ const toolCallId = `call_${Date.now()}`;
370
+ opsTracker.startToolCall(toolCallId, toolName, toolArgs || {});
371
+ const toolStartTime = Date.now();
372
+
373
+ // Call the tool via API
374
+ const rawResult = await api.callTool(
375
+ toolName,
376
+ toolArgs || {},
377
+ effectiveToken,
378
+ mcpApiKey || undefined
379
+ );
380
+
381
+ console.log('✅ Widget tool call raw result:', rawResult);
382
+
383
+ // Parse the MCP result format: { content: [{ type: "text", text: "..." }] }
384
+ let parsedResult = rawResult;
385
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
386
+ const mcpResult = rawResult as any;
387
+ if (mcpResult?.content?.[0]?.text) {
388
+ try {
389
+ parsedResult = JSON.parse(mcpResult.content[0].text);
390
+ // Unwrap if response was wrapped by TransformInterceptor
391
+ if (parsedResult && typeof parsedResult === 'object' && 'success' in parsedResult && 'data' in parsedResult) {
392
+ parsedResult = (parsedResult as { success: boolean; data: unknown }).data;
393
+ }
394
+ } catch {
395
+ parsedResult = { content: mcpResult.content[0].text };
396
+ }
397
+ }
398
+
399
+ console.log('✅ Widget tool call parsed result:', parsedResult);
400
+
401
+ // Track ops: Complete tool call with result
402
+ const resultNodeId = opsTracker.completeToolCall(toolCallId, toolName, parsedResult);
403
+
404
+ // Track ops: Widget render
405
+ const toolDef = tools.find(t => t.name === toolName);
406
+ const widgetUri = toolDef?.widget?.route || toolDef?.outputTemplate || toolDef?._meta?.['openai/outputTemplate'];
407
+ if (widgetUri && parsedResult && resultNodeId) {
408
+ opsTracker.trackWidget(widgetUri, toolName, parsedResult, resultNodeId);
409
+ }
410
+
411
+ // Add assistant message with tool call info
412
+ const assistantMsg: ChatMessage = {
413
+ role: 'assistant',
414
+ content: ``,
415
+ toolCalls: [{
416
+ id: toolCallId,
417
+ name: toolName,
418
+ arguments: toolArgs || {},
419
+ result: parsedResult // Attach parsed result for widget rendering
420
+ }]
421
+ };
422
+ addChatMessage(assistantMsg);
423
+
424
+ // Add tool result message
425
+ const toolResultMsg: ChatMessage = {
426
+ role: 'tool',
427
+ content: JSON.stringify(parsedResult),
428
+ toolCallId: toolCallId
429
+ };
430
+ addChatMessage(toolResultMsg);
431
+
432
+ // Continue conversation to get LLM response about the tool result (like nitrochat)
433
+ const apiKey = localStorage.getItem(`${currentProvider}_api_key`);
434
+ if (apiKey && apiKey !== '••••••••') {
435
+ // Get current messages and add instruction to prevent tool chaining
436
+ const currentMessages = useStudioStore.getState().chatMessages;
437
+
438
+ // Add a system-style instruction (as user message) to prevent the LLM from calling more tools
439
+ // This is hidden from the user but guides the LLM to just summarize
440
+ const messagesForLLM: ChatMessage[] = [
441
+ ...currentMessages,
442
+ {
443
+ role: 'user',
444
+ content: `[INSTRUCTION: The user clicked on a widget item which called the "${toolName}" tool. Please summarize the result above in a helpful way. IMPORTANT: Do NOT call any additional tools - just provide a text summary of what was retrieved. The user only wanted to see details for this specific item.]`
445
+ }
446
+ ];
447
+
448
+ await continueChatWithToolResults(apiKey, messagesForLLM, true);
449
+ }
450
+
451
+ } catch (error) {
452
+ console.error('❌ Widget tool call failed:', error);
453
+ // Track ops: Tool call error
454
+ opsTracker.errorLLMCall();
455
+ addChatMessage({
456
+ role: 'assistant',
457
+ content: `Failed to execute tool ${toolName}: ${error instanceof Error ? error.message : String(error)}`
458
+ });
459
+ } finally {
460
+ setLoading(false);
461
+ }
462
+ };
463
+
464
+ window.addEventListener('widget-tool-call', handleWidgetToolCall as EventListener);
465
+ return () => {
466
+ window.removeEventListener('widget-tool-call', handleWidgetToolCall as EventListener);
467
+ };
468
+ }, [addChatMessage, currentProvider]);
469
+
233
470
  // Convert markdown content to voice-friendly, conversational text
234
471
  // Optimized for minimal TTS token usage
235
472
  const convertToVoiceFriendlyText = (text: string): string => {
@@ -423,36 +660,65 @@ export default function ChatPage() {
423
660
  initialToolExecuted.current = true;
424
661
  console.log('🚀 Auto-executing initial tool:', initialTool.name);
425
662
 
426
- // Initial message
427
- const autoMsg: ChatMessage = {
428
- role: 'user',
429
- content: `(Auto) Executing initial tool: ${initialTool.name}`,
430
- };
431
- addChatMessage(autoMsg);
432
663
  setLoading(true);
433
664
 
665
+ // Track ops: Start a new turn for initial tool
666
+ opsTracker.startTurn(`[Initial] ${initialTool.name}`);
667
+
434
668
  try {
435
669
  const { jwtToken, mcpApiKey } = getAuthTokens();
436
670
  const effectiveToken = jwtToken || useStudioStore.getState().oauthState?.currentToken;
437
671
 
672
+ // Track ops: Start tool call
673
+ const toolCallId = `call_${Date.now()}_init`;
674
+ opsTracker.startToolCall(toolCallId, initialTool.name, {});
675
+
438
676
  // Call the tool
439
- const result = await api.callTool(
677
+ const rawResult = await api.callTool(
440
678
  initialTool.name,
441
679
  {},
442
680
  effectiveToken,
443
681
  mcpApiKey || undefined
444
682
  );
445
683
 
446
- // Add assistant message with tool call info
447
- const toolCallId = `call_${Date.now()}`;
684
+ console.log('✅ Initial tool raw result:', rawResult);
685
+
686
+ // Parse the MCP result format: { content: [{ type: "text", text: "..." }] }
687
+ let parsedResult = rawResult;
688
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
689
+ const mcpResult = rawResult as any;
690
+ if (mcpResult?.content?.[0]?.text) {
691
+ try {
692
+ parsedResult = JSON.parse(mcpResult.content[0].text);
693
+ // Unwrap if response was wrapped by TransformInterceptor
694
+ if (parsedResult && typeof parsedResult === 'object' && 'success' in parsedResult && 'data' in parsedResult) {
695
+ parsedResult = (parsedResult as { success: boolean; data: unknown }).data;
696
+ }
697
+ } catch {
698
+ parsedResult = { content: mcpResult.content[0].text };
699
+ }
700
+ }
701
+
702
+ console.log('✅ Initial tool parsed result:', parsedResult);
703
+
704
+ // Track ops: Complete tool call with result
705
+ const resultNodeId = opsTracker.completeToolCall(toolCallId, initialTool.name, parsedResult);
706
+
707
+ // Track ops: Widget render
708
+ const widgetUri = initialTool?.widget?.route || initialTool?.outputTemplate || initialTool?._meta?.['openai/outputTemplate'];
709
+ if (widgetUri && parsedResult && resultNodeId) {
710
+ opsTracker.trackWidget(widgetUri, initialTool.name, parsedResult, resultNodeId);
711
+ }
712
+
713
+ // Add assistant message with tool call info (widget will render from this)
448
714
  const assistantMsg: ChatMessage = {
449
715
  role: 'assistant',
450
- content: `Invoking ${initialTool.name}...`,
716
+ content: '',
451
717
  toolCalls: [{
452
718
  id: toolCallId,
453
719
  name: initialTool.name,
454
720
  arguments: {},
455
- result // Attach result here for widget rendering
721
+ result: parsedResult // Attach parsed result for widget rendering
456
722
  }]
457
723
  };
458
724
  addChatMessage(assistantMsg);
@@ -460,13 +726,18 @@ export default function ChatPage() {
460
726
  // Add tool result message
461
727
  const toolResultMsg: ChatMessage = {
462
728
  role: 'tool',
463
- content: JSON.stringify(result),
729
+ content: JSON.stringify(parsedResult),
464
730
  toolCallId: toolCallId
465
731
  };
466
732
  addChatMessage(toolResultMsg);
467
733
 
734
+ // Note: Like nitrochat, we don't continue chat automatically here
735
+ // We just show the result (widget) as the starting state
736
+
468
737
  } catch (error) {
469
- console.error('Initial tool execution failed:', error);
738
+ console.error('Initial tool execution failed:', error);
739
+ // Track ops: Error
740
+ opsTracker.errorLLMCall();
470
741
  addChatMessage({
471
742
  role: 'assistant',
472
743
  content: `Failed to execute initial tool ${initialTool.name}: ${error instanceof Error ? error.message : String(error)}`
@@ -506,11 +777,15 @@ export default function ChatPage() {
506
777
  // Add the prompt result as an assistant message
507
778
  if (result.messages && result.messages.length > 0) {
508
779
  // Combine all prompt messages into one assistant message
509
- const combinedContent = result.messages
510
- .map((msg: any) => {
780
+ interface PromptMessageResult {
781
+ role: string;
782
+ content: string | { text?: string } | unknown;
783
+ }
784
+ const combinedContent = (result.messages as PromptMessageResult[])
785
+ .map((msg) => {
511
786
  const content = typeof msg.content === 'string'
512
787
  ? msg.content
513
- : msg.content?.text || JSON.stringify(msg.content);
788
+ : (msg.content as { text?: string })?.text || JSON.stringify(msg.content);
514
789
  return `[${msg.role.toUpperCase()}]\n${content}`;
515
790
  })
516
791
  .join('\n\n');
@@ -584,31 +859,14 @@ export default function ChatPage() {
584
859
  setCurrentFile(null);
585
860
  setLoading(true);
586
861
 
862
+ // Track ops: Start a new turn
863
+ opsTracker.startTurn(messageText, !!currentFile);
864
+
587
865
  try {
588
866
  const messagesToSend = [...chatMessages, userMessage];
589
867
 
590
- // Clean messages to ensure they're serializable
591
- const cleanedMessages = messagesToSend.map(msg => {
592
- const cleaned: any = {
593
- role: msg.role,
594
- content: msg.content || '',
595
- };
596
-
597
- if (msg.toolCalls && msg.toolCalls.length > 0) {
598
- cleaned.toolCalls = msg.toolCalls;
599
- }
600
-
601
- if (msg.toolCallId) {
602
- cleaned.toolCallId = msg.toolCallId;
603
- }
604
-
605
- // Skip image property for now (not supported by OpenAI chat completions)
606
- if (msg.file) {
607
- cleaned.file = msg.file;
608
- }
609
-
610
- return cleaned;
611
- });
868
+ // Optimize messages to reduce token count
869
+ const cleanedMessages = optimizeMessagesForLLM(messagesToSend);
612
870
 
613
871
  // Get fresh auth tokens from store
614
872
  const { jwtToken, mcpApiKey } = getAuthTokens();
@@ -633,6 +891,10 @@ export default function ChatPage() {
633
891
  console.log('Original messages:', messagesToSend);
634
892
  console.log('Voice mode:', voiceModeEnabled, 'Output language:', outputLanguage);
635
893
 
894
+ // Track ops: LLM call start
895
+ const llmModel = currentProvider === 'gemini' ? 'gemini-1.5-pro' : 'gpt-4o';
896
+ opsTracker.startLLMCall(currentProvider as LLMProvider, llmModel);
897
+
636
898
  const response = await api.chat({
637
899
  provider: currentProvider,
638
900
  messages: messagesForApi,
@@ -641,14 +903,23 @@ export default function ChatPage() {
641
903
  mcpApiKey: mcpApiKey || undefined, // MCP server API key
642
904
  });
643
905
 
906
+ // Track ops: LLM call complete (estimate tokens based on content length)
907
+ const inputTokenEstimate = Math.ceil(JSON.stringify(messagesForApi).length / 4);
908
+ const outputTokenEstimate = Math.ceil((response.message?.content?.length || 0) / 4);
909
+ opsTracker.completeLLMCall(inputTokenEstimate, outputTokenEstimate);
910
+
644
911
  // Handle tool calls FIRST (before adding the message)
645
912
  if (response.toolCalls && response.toolResults) {
646
- // Attach results to tool calls for widget rendering
913
+ // Track ops: Process each tool call
647
914
  const toolCallsWithResults = response.toolCalls.map((tc: ToolCall, i: number) => {
648
915
  const toolResult = response.toolResults[i];
649
916
 
917
+ // Track ops: Tool call start
918
+ opsTracker.startToolCall(tc.id, tc.name, tc.arguments as Record<string, unknown>);
919
+
650
920
  // Parse the result content
651
921
  let parsedResult;
922
+ let hasError = false;
652
923
  if (toolResult.content) {
653
924
  try {
654
925
  parsedResult = JSON.parse(toolResult.content);
@@ -662,6 +933,16 @@ export default function ChatPage() {
662
933
  }
663
934
  }
664
935
 
936
+ // Track ops: Tool call complete with result
937
+ const resultNodeId = opsTracker.completeToolCall(tc.id, tc.name, parsedResult, hasError ? 'Error processing result' : undefined);
938
+
939
+ // Track ops: Widget render if tool has a widget
940
+ const toolDef = tools.find(t => t.name === tc.name);
941
+ const widgetUri = toolDef?.widget?.route || toolDef?.outputTemplate || toolDef?._meta?.['openai/outputTemplate'];
942
+ if (widgetUri && parsedResult && resultNodeId) {
943
+ opsTracker.trackWidget(widgetUri, tc.name, parsedResult, resultNodeId);
944
+ }
945
+
665
946
  return {
666
947
  ...tc,
667
948
  result: parsedResult,
@@ -714,6 +995,12 @@ export default function ChatPage() {
714
995
  // No tool calls, just add the message
715
996
  if (response.message) {
716
997
  addChatMessage(response.message);
998
+
999
+ // Track ops: Final response
1000
+ opsTracker.trackResponse(
1001
+ response.message.content || '',
1002
+ outputTokenEstimate
1003
+ );
717
1004
  }
718
1005
  }
719
1006
 
@@ -721,6 +1008,10 @@ export default function ChatPage() {
721
1008
  setLoading(false);
722
1009
  } catch (error) {
723
1010
  console.error('Chat error:', error);
1011
+
1012
+ // Track ops: LLM call error
1013
+ opsTracker.errorLLMCall();
1014
+
724
1015
  addChatMessage({
725
1016
  role: 'assistant',
726
1017
  content: 'Sorry, I encountered an error. Please try again.',
@@ -729,7 +1020,7 @@ export default function ChatPage() {
729
1020
  }
730
1021
  };
731
1022
 
732
- const continueChatWithToolResults = async (apiKey: string, messages?: ChatMessage[]) => {
1023
+ const continueChatWithToolResults = async (apiKey: string, messages?: ChatMessage[], isFromWidget: boolean = false) => {
733
1024
  try {
734
1025
  // Use provided messages or fall back to store (for recursive calls)
735
1026
  const messagesToUse = messages || chatMessages;
@@ -737,26 +1028,15 @@ export default function ChatPage() {
737
1028
  // Get fresh auth tokens from store (token may have been updated by login)
738
1029
  const { jwtToken, mcpApiKey } = getAuthTokens();
739
1030
 
740
- // Clean messages before sending
741
- const cleanedMessages = messagesToUse.map(msg => {
742
- const cleaned: any = {
743
- role: msg.role,
744
- content: msg.content || '',
745
- };
1031
+ // Optimize messages to reduce token count
1032
+ const cleanedMessages = optimizeMessagesForLLM(messagesToUse);
746
1033
 
747
- if (msg.toolCalls && msg.toolCalls.length > 0) {
748
- cleaned.toolCalls = msg.toolCalls;
749
- }
1034
+ console.log('Continue with optimized messages:', cleanedMessages.length, 'messages');
750
1035
 
751
- if (msg.toolCallId) {
752
- cleaned.toolCallId = msg.toolCallId;
753
- }
754
-
755
- return cleaned;
756
- });
757
-
758
- console.log('Continue with cleaned messages:', JSON.stringify(cleanedMessages));
759
- console.log('Continue auth tokens:', { hasJwtToken: !!jwtToken, hasMcpApiKey: !!mcpApiKey });
1036
+ // Track ops: LLM call start for continuation
1037
+ const llmModel = currentProvider === 'gemini' ? 'gemini-1.5-pro' : 'gpt-4o';
1038
+ opsTracker.startLLMCall(currentProvider as LLMProvider, llmModel);
1039
+ const llmStartTime = Date.now();
760
1040
 
761
1041
  const response = await api.chat({
762
1042
  provider: currentProvider,
@@ -766,12 +1046,58 @@ export default function ChatPage() {
766
1046
  mcpApiKey: mcpApiKey || undefined, // MCP server API key
767
1047
  });
768
1048
 
769
- if (response.message) {
770
- addChatMessage(response.message);
771
- }
1049
+ // Track ops: LLM call complete with token estimates
1050
+ const inputTokenEstimate = Math.ceil(JSON.stringify(cleanedMessages).length / 4);
1051
+ const outputTokenEstimate = Math.ceil((response.message?.content?.length || 0) / 4);
1052
+ opsTracker.completeLLMCall(inputTokenEstimate, outputTokenEstimate);
772
1053
 
773
- // Recursive tool calls
1054
+ // Handle tool calls - attach results before adding message (same as handleSend)
774
1055
  if (response.toolCalls && response.toolResults) {
1056
+ // Track ops: Process each tool call
1057
+ const toolCallsWithResults = response.toolCalls.map((tc: ToolCall, i: number) => {
1058
+ const toolResult = response.toolResults[i];
1059
+
1060
+ // Track ops: Tool call start
1061
+ opsTracker.startToolCall(tc.id, tc.name, tc.arguments as Record<string, unknown>);
1062
+
1063
+ // Parse the result content
1064
+ let parsedResult;
1065
+ if (toolResult.content) {
1066
+ try {
1067
+ parsedResult = JSON.parse(toolResult.content);
1068
+
1069
+ // Unwrap if response was wrapped by TransformInterceptor
1070
+ if (parsedResult.success !== undefined && parsedResult.data !== undefined) {
1071
+ parsedResult = parsedResult.data;
1072
+ }
1073
+ } catch {
1074
+ parsedResult = { content: toolResult.content };
1075
+ }
1076
+ }
1077
+
1078
+ // Track ops: Tool call complete with result
1079
+ const resultNodeId = opsTracker.completeToolCall(tc.id, tc.name, parsedResult);
1080
+
1081
+ // Track ops: Widget render if tool has a widget
1082
+ const toolDef = tools.find(t => t.name === tc.name);
1083
+ const widgetUri = toolDef?.widget?.route || toolDef?.outputTemplate || toolDef?._meta?.['openai/outputTemplate'];
1084
+ if (widgetUri && parsedResult && resultNodeId) {
1085
+ opsTracker.trackWidget(widgetUri, tc.name, parsedResult, resultNodeId);
1086
+ }
1087
+
1088
+ return {
1089
+ ...tc,
1090
+ result: parsedResult,
1091
+ };
1092
+ });
1093
+
1094
+ // Add assistant message with tool calls (with results attached)
1095
+ if (response.message) {
1096
+ response.message.toolCalls = toolCallsWithResults;
1097
+ addChatMessage(response.message);
1098
+ }
1099
+
1100
+ // Add tool result messages
775
1101
  const newToolResults: ChatMessage[] = [];
776
1102
  for (const result of response.toolResults) {
777
1103
  addChatMessage(result);
@@ -784,16 +1110,28 @@ export default function ChatPage() {
784
1110
  response.message!,
785
1111
  ...newToolResults,
786
1112
  ];
787
- await continueChatWithToolResults(apiKey, nextMessages);
1113
+ await continueChatWithToolResults(apiKey, nextMessages, false);
1114
+ } else {
1115
+ // No tool calls, just add the message
1116
+ if (response.message) {
1117
+ addChatMessage(response.message);
1118
+
1119
+ // Track ops: Final response
1120
+ opsTracker.trackResponse(
1121
+ response.message.content || '',
1122
+ outputTokenEstimate
1123
+ );
1124
+ }
788
1125
  }
789
1126
  } catch (error) {
790
1127
  console.error('Continuation error:', error);
1128
+ // Track ops: Error
1129
+ opsTracker.errorLLMCall();
791
1130
  }
792
1131
  };
793
1132
 
794
1133
  const saveApiKey = (provider: 'openai' | 'gemini') => {
795
- const input = document.getElementById(`${provider}-api-key`) as HTMLInputElement;
796
- const key = input?.value.trim();
1134
+ const key = provider === 'openai' ? openaiApiKeyInput.trim() : geminiApiKeyInput.trim();
797
1135
 
798
1136
  if (!key || key === '••••••••') {
799
1137
  alert('Please enter a valid API key');
@@ -801,7 +1139,12 @@ export default function ChatPage() {
801
1139
  }
802
1140
 
803
1141
  localStorage.setItem(`${provider}_api_key`, key);
804
- input.value = '••••••••';
1142
+ // Show masked value after save
1143
+ if (provider === 'openai') {
1144
+ setOpenaiApiKeyInput('••••••••');
1145
+ } else {
1146
+ setGeminiApiKeyInput('••••••••');
1147
+ }
805
1148
  alert(`${provider === 'openai' ? 'OpenAI' : 'Gemini'} API key saved`);
806
1149
  };
807
1150
 
@@ -856,6 +1199,20 @@ export default function ChatPage() {
856
1199
  )}
857
1200
 
858
1201
 
1202
+ {/* Ops View Toggle */}
1203
+ <button
1204
+ onClick={toggleOpsView}
1205
+ className={`h-8 w-8 rounded-lg flex items-center justify-center transition-all flex-shrink-0 ${isOpsViewOpen
1206
+ ? 'bg-indigo-500/20 text-indigo-400 ring-1 ring-indigo-500/40'
1207
+ : 'bg-zinc-800/80 text-zinc-500 hover:bg-zinc-700/80 hover:text-zinc-300'
1208
+ }`}
1209
+ title="Operations"
1210
+ >
1211
+ <svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
1212
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z" />
1213
+ </svg>
1214
+ </button>
1215
+
859
1216
  <button
860
1217
  onClick={() => setShowSettings(!showSettings)}
861
1218
  className={`h-8 w-8 rounded-lg flex items-center justify-center transition-all flex-shrink-0 ${showSettings
@@ -867,7 +1224,10 @@ export default function ChatPage() {
867
1224
  <Cog6ToothIcon className="h-4 w-4" />
868
1225
  </button>
869
1226
  <button
870
- onClick={clearChat}
1227
+ onClick={() => {
1228
+ clearChat();
1229
+ clearOpsSessionStore();
1230
+ }}
871
1231
  className="h-8 w-8 rounded-lg flex items-center justify-center bg-muted/50 text-muted-foreground hover:bg-muted hover:text-foreground transition-all flex-shrink-0"
872
1232
  title="Clear chat"
873
1233
  >
@@ -974,7 +1334,8 @@ export default function ChatPage() {
974
1334
  type="password"
975
1335
  className="input flex-1 text-sm bg-background/50"
976
1336
  placeholder="sk-proj-..."
977
- defaultValue={localStorage.getItem('openai_api_key') || ''}
1337
+ value={openaiApiKeyInput}
1338
+ onChange={(e) => setOpenaiApiKeyInput(e.target.value)}
978
1339
  />
979
1340
  <button onClick={() => saveApiKey('openai')} className="btn btn-primary btn-sm px-4">
980
1341
  Save
@@ -1006,7 +1367,8 @@ export default function ChatPage() {
1006
1367
  type="password"
1007
1368
  className="input flex-1 text-sm bg-background/50"
1008
1369
  placeholder="AIza..."
1009
- defaultValue={localStorage.getItem('gemini_api_key') || ''}
1370
+ value={geminiApiKeyInput}
1371
+ onChange={(e) => setGeminiApiKeyInput(e.target.value)}
1010
1372
  />
1011
1373
  <button onClick={() => saveApiKey('gemini')} className="btn btn-primary btn-sm px-4">
1012
1374
  Save
@@ -1176,9 +1538,12 @@ export default function ChatPage() {
1176
1538
  </div>
1177
1539
  )}
1178
1540
 
1179
- {/* ChatGPT-style Messages Container - ONLY this scrolls */}
1180
- <div className="flex-1 overflow-y-auto overflow-x-hidden">
1181
- <div className="max-w-5xl mx-auto px-4 py-6 space-y-6 min-h-full">
1541
+ {/* Main Content Area with optional Ops split view */}
1542
+ <div className="flex-1 flex overflow-hidden min-h-0">
1543
+ {/* Chat Messages Container */}
1544
+ <div className={`flex flex-col overflow-hidden transition-all duration-300 ${isOpsViewOpen ? 'w-3/5' : 'w-full'}`}>
1545
+ <div className="flex-1 overflow-y-auto overflow-x-hidden">
1546
+ <div className={`mx-auto px-4 py-6 space-y-6 ${isOpsViewOpen ? 'max-w-3xl' : 'max-w-5xl'}`}>
1182
1547
  {chatMessages.length === 0 && !loading ? (
1183
1548
  /* Welcome Screen */
1184
1549
  <div className="flex flex-col items-center justify-center min-h-[calc(100vh-300px)] animate-fade-in">
@@ -1373,7 +1738,16 @@ export default function ChatPage() {
1373
1738
  </>
1374
1739
  )}
1375
1740
  <div ref={messagesEndRef} />
1741
+ </div>
1742
+ </div>
1376
1743
  </div>
1744
+
1745
+ {/* Ops Canvas Panel */}
1746
+ {isOpsViewOpen && (
1747
+ <div className="w-2/5 min-w-[320px] border-l border-zinc-800 flex flex-col overflow-hidden">
1748
+ <OpsCanvas className="flex-1" />
1749
+ </div>
1750
+ )}
1377
1751
  </div>
1378
1752
 
1379
1753
  {/* Sleek Professional Input Area */}
@@ -1878,9 +2252,11 @@ function ToolCallComponent({ toolCall, tools }: { toolCall: ToolCall; tools: Too
1878
2252
  return (
1879
2253
  <div className="relative group/widget">
1880
2254
  {componentUri && widgetData && (
1881
- <div className="rounded-lg overflow-hidden max-w-5xl">
1882
- <WidgetRenderer uri={componentUri} data={widgetData} className="widget-in-chat" />
1883
- </div>
2255
+ <WidgetErrorBoundary>
2256
+ <div className="rounded-lg overflow-hidden max-w-5xl" style={{ minHeight: '100px' }}>
2257
+ <WidgetRenderer uri={componentUri} data={widgetData} className="widget-in-chat" />
2258
+ </div>
2259
+ </WidgetErrorBoundary>
1884
2260
  )}
1885
2261
  <button
1886
2262
  onClick={() => setShowArgs(!showArgs)}