@nextsparkjs/theme-default 0.1.0-beta.20 → 0.1.0-beta.21

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 (220) hide show
  1. package/package.json +1 -1
  2. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  3. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  4. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  5. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  6. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  7. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  8. package/tests/cypress/e2e/_docs/README.md +215 -0
  9. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  11. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  13. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  14. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  15. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  19. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  20. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  21. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  22. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  23. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  24. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  25. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  26. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  27. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  28. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  29. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  30. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  31. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  32. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  33. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  34. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  36. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  38. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  40. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  42. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  43. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  44. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  45. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  46. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  47. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  48. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  49. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  50. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  51. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  52. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  53. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  54. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  55. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  56. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  57. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  58. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  59. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  60. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  61. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  62. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  63. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  64. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  65. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  66. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  67. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  68. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  69. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  70. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  71. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  72. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  78. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  79. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  83. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  85. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  87. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  89. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  95. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  96. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  97. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  99. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  100. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  101. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  102. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  103. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  105. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  106. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  107. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  109. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  110. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  112. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  114. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  118. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  120. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  122. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  126. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  128. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  130. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  132. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  136. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  138. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  140. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  142. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  144. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  146. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  148. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  149. package/tests/cypress/fixtures/blocks.json +218 -0
  150. package/tests/cypress/fixtures/entities.json +78 -0
  151. package/tests/cypress/fixtures/page-builder.json +21 -0
  152. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  153. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  154. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  155. package/tests/cypress/src/components/EntityForm.ts +375 -0
  156. package/tests/cypress/src/components/EntityList.ts +389 -0
  157. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  158. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  159. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  160. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  161. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  162. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  163. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  164. package/tests/cypress/src/components/index.ts +21 -0
  165. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  166. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  167. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  168. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  169. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  170. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  171. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  172. package/tests/cypress/src/controllers/index.js +25 -0
  173. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  174. package/tests/cypress/src/core/BasePOM.ts +86 -0
  175. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  176. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  177. package/tests/cypress/src/core/index.ts +14 -0
  178. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  179. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  180. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  181. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  182. package/tests/cypress/src/entities/index.ts +14 -0
  183. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  184. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  185. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  186. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  187. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  188. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  189. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  190. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  191. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  192. package/tests/cypress/src/features/index.ts +28 -0
  193. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  194. package/tests/cypress/src/index.ts +101 -0
  195. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  196. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  198. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  199. package/tests/cypress/src/pages/index.js +5 -0
  200. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  201. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  202. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  203. package/tests/cypress/src/pages/public/index.js +6 -0
  204. package/tests/cypress/src/selectors.ts +46 -0
  205. package/tests/cypress/src/session-helpers.ts +500 -0
  206. package/tests/cypress/support/doc-commands.ts +260 -0
  207. package/tests/cypress.config.ts +150 -0
  208. package/tests/jest/components/post-header.test.tsx +377 -0
  209. package/tests/jest/config/role-config.test.ts +529 -0
  210. package/tests/jest/jest.config.ts +81 -0
  211. package/tests/jest/langchain/COVERAGE.md +372 -0
  212. package/tests/jest/langchain/guardrails.test.ts +465 -0
  213. package/tests/jest/langchain/streaming.test.ts +367 -0
  214. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  215. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  216. package/tests/jest/langchain/tracer.test.ts +823 -0
  217. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  218. package/tests/jest/validation/categories.test.ts +429 -0
  219. package/tests/jest/validation/posts.test.ts +546 -0
  220. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,881 @@
1
+ /**
2
+ * Unit Tests - Tracing Callback Handler
3
+ *
4
+ * Tests LangChain callback handler integration:
5
+ * - LLM event handling (start, end, error)
6
+ * - Tool event handling (start, end, error)
7
+ * - Chain event handling (start, end, error)
8
+ * - Parent-child span relationships
9
+ * - Depth tracking
10
+ *
11
+ * Focus: Event transformation and span lifecycle.
12
+ */
13
+
14
+ import {
15
+ TracingCallbackHandler,
16
+ createTracingCallbacks,
17
+ } from '@/plugins/langchain/lib/tracer-callbacks'
18
+ import { tracer } from '@/plugins/langchain/lib/tracer'
19
+ import type { Serialized } from '@langchain/core/load/serializable'
20
+
21
+ // Mock tracer service
22
+ jest.mock('@/contents/plugins/langchain/lib/tracer', () => ({
23
+ tracer: {
24
+ startSpan: jest.fn(),
25
+ endSpan: jest.fn(),
26
+ },
27
+ }))
28
+
29
+ const mockStartSpan = tracer.startSpan as jest.MockedFunction<typeof tracer.startSpan>
30
+ const mockEndSpan = tracer.endSpan as jest.MockedFunction<typeof tracer.endSpan>
31
+
32
+ describe('TracingCallbackHandler', () => {
33
+ const context = { userId: 'user-123', teamId: 'team-456' }
34
+ const traceId = 'trace-abc'
35
+ let handler: TracingCallbackHandler
36
+
37
+ beforeEach(() => {
38
+ jest.clearAllMocks()
39
+ handler = new TracingCallbackHandler({ context, traceId })
40
+ })
41
+
42
+ describe('initialization', () => {
43
+ it('should initialize with correct name', () => {
44
+ expect(handler.name).toBe('tracing_callback_handler')
45
+ })
46
+
47
+ it('should store context and traceId', () => {
48
+ // Context and traceId are private, so we test indirectly via method calls
49
+ mockStartSpan.mockResolvedValue({
50
+ spanId: 'span-123',
51
+ traceId,
52
+ name: 'test',
53
+ type: 'llm',
54
+ depth: 0,
55
+ startedAt: new Date(),
56
+ })
57
+
58
+ handler.handleLLMStart(
59
+ { id: ['langchain', 'llms', 'openai'] } as Serialized,
60
+ ['test prompt'],
61
+ 'run-123'
62
+ )
63
+
64
+ expect(mockStartSpan).toHaveBeenCalledWith(
65
+ context,
66
+ traceId,
67
+ expect.any(Object)
68
+ )
69
+ })
70
+ })
71
+
72
+ describe('handleLLMStart', () => {
73
+ it('should create span for LLM call', async () => {
74
+ const llm: Serialized = {
75
+ id: ['langchain', 'llms', 'openai'],
76
+ kwargs: { model: 'gpt-4o' },
77
+ }
78
+ mockStartSpan.mockResolvedValue({
79
+ spanId: 'span-123',
80
+ traceId,
81
+ name: 'LLM: gpt-4o',
82
+ type: 'llm',
83
+ depth: 0,
84
+ startedAt: new Date(),
85
+ })
86
+
87
+ await handler.handleLLMStart(llm, ['test prompt'], 'run-123')
88
+
89
+ expect(mockStartSpan).toHaveBeenCalledWith(context, traceId, {
90
+ name: 'LLM: gpt-4o',
91
+ type: 'llm',
92
+ provider: 'openai',
93
+ model: 'gpt-4o',
94
+ parentSpanId: undefined,
95
+ depth: 0,
96
+ input: { prompts: ['test prompt'] },
97
+ })
98
+ })
99
+
100
+ it('should extract provider from LLM serialized id', async () => {
101
+ const llm: Serialized = {
102
+ id: ['langchain', 'chat_models', 'anthropic'],
103
+ kwargs: { model: 'claude-3-5-sonnet' },
104
+ }
105
+ mockStartSpan.mockResolvedValue(null)
106
+
107
+ await handler.handleLLMStart(llm, ['test'], 'run-123')
108
+
109
+ expect(mockStartSpan).toHaveBeenCalledWith(
110
+ context,
111
+ traceId,
112
+ expect.objectContaining({
113
+ provider: 'anthropic',
114
+ })
115
+ )
116
+ })
117
+
118
+ it('should use "unknown" for missing model', async () => {
119
+ const llm: Serialized = {
120
+ id: ['langchain', 'llms', 'custom'],
121
+ }
122
+ mockStartSpan.mockResolvedValue(null)
123
+
124
+ await handler.handleLLMStart(llm, ['test'], 'run-123')
125
+
126
+ expect(mockStartSpan).toHaveBeenCalledWith(
127
+ context,
128
+ traceId,
129
+ expect.objectContaining({
130
+ model: 'unknown',
131
+ })
132
+ )
133
+ })
134
+
135
+ it('should handle nested LLM calls with depth', async () => {
136
+ const llm: Serialized = {
137
+ id: ['langchain', 'llms', 'openai'],
138
+ kwargs: { model: 'gpt-4o' },
139
+ }
140
+
141
+ // First, create a parent span from a different parent to set up parentSpans map
142
+ mockStartSpan.mockResolvedValueOnce({
143
+ spanId: 'span-grandparent',
144
+ traceId,
145
+ name: 'LLM: gpt-4o',
146
+ type: 'llm',
147
+ depth: 0,
148
+ startedAt: new Date(),
149
+ })
150
+ await handler.handleLLMStart(llm, ['prompt 0'], 'run-grandparent')
151
+
152
+ // Second call with grandparent as parent (depth 1)
153
+ mockStartSpan.mockResolvedValueOnce({
154
+ spanId: 'span-parent',
155
+ traceId,
156
+ name: 'LLM: gpt-4o',
157
+ type: 'llm',
158
+ depth: 1,
159
+ startedAt: new Date(),
160
+ })
161
+ await handler.handleLLMStart(llm, ['prompt 1'], 'run-parent', 'run-grandparent')
162
+
163
+ // Third call nested under parent (depth 2)
164
+ mockStartSpan.mockResolvedValueOnce({
165
+ spanId: 'span-child',
166
+ traceId,
167
+ name: 'LLM: gpt-4o',
168
+ type: 'llm',
169
+ depth: 2,
170
+ startedAt: new Date(),
171
+ })
172
+ await handler.handleLLMStart(llm, ['prompt 2'], 'run-child', 'run-parent')
173
+
174
+ expect(mockStartSpan).toHaveBeenLastCalledWith(
175
+ context,
176
+ traceId,
177
+ expect.objectContaining({
178
+ depth: 2,
179
+ parentSpanId: 'span-parent',
180
+ })
181
+ )
182
+ })
183
+
184
+ it('should handle errors gracefully', async () => {
185
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
186
+ mockStartSpan.mockRejectedValue(new Error('Database error'))
187
+
188
+ await handler.handleLLMStart(
189
+ { id: ['langchain', 'llms', 'openai'] } as Serialized,
190
+ ['test'],
191
+ 'run-123'
192
+ )
193
+
194
+ expect(consoleSpy).toHaveBeenCalledWith(
195
+ '[TracingCallbackHandler] handleLLMStart error:',
196
+ expect.any(Error)
197
+ )
198
+ consoleSpy.mockRestore()
199
+ })
200
+ })
201
+
202
+ describe('handleLLMEnd', () => {
203
+ beforeEach(async () => {
204
+ // Setup: Start an LLM span first
205
+ mockStartSpan.mockResolvedValue({
206
+ spanId: 'span-123',
207
+ traceId,
208
+ name: 'LLM: gpt-4o',
209
+ type: 'llm',
210
+ depth: 0,
211
+ startedAt: new Date(),
212
+ })
213
+ await handler.handleLLMStart(
214
+ { id: ['langchain', 'llms', 'openai'], kwargs: { model: 'gpt-4o' } } as Serialized,
215
+ ['test prompt'],
216
+ 'run-123'
217
+ )
218
+ jest.clearAllMocks()
219
+ })
220
+
221
+ it('should end span with output and token usage', async () => {
222
+ mockEndSpan.mockResolvedValue(undefined)
223
+
224
+ await handler.handleLLMEnd(
225
+ {
226
+ generations: [{ text: 'Generated response' }],
227
+ llmOutput: {
228
+ tokenUsage: {
229
+ promptTokens: 100,
230
+ completionTokens: 50,
231
+ },
232
+ },
233
+ },
234
+ 'run-123'
235
+ )
236
+
237
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
238
+ output: {
239
+ generations: ['Generated response'],
240
+ },
241
+ tokens: {
242
+ input: 100,
243
+ output: 50,
244
+ },
245
+ })
246
+ })
247
+
248
+ it('should handle alternate token usage format (input_tokens/output_tokens)', async () => {
249
+ mockEndSpan.mockResolvedValue(undefined)
250
+
251
+ await handler.handleLLMEnd(
252
+ {
253
+ generations: [{ text: 'response' }],
254
+ llmOutput: {
255
+ tokenUsage: {
256
+ input_tokens: 200,
257
+ output_tokens: 75,
258
+ },
259
+ },
260
+ },
261
+ 'run-123'
262
+ )
263
+
264
+ expect(mockEndSpan).toHaveBeenCalledWith(
265
+ context,
266
+ traceId,
267
+ 'span-123',
268
+ expect.objectContaining({
269
+ tokens: {
270
+ input: 200,
271
+ output: 75,
272
+ },
273
+ })
274
+ )
275
+ })
276
+
277
+ it('should handle message format generations', async () => {
278
+ mockEndSpan.mockResolvedValue(undefined)
279
+
280
+ await handler.handleLLMEnd(
281
+ {
282
+ generations: [{ message: { content: 'Message response' } }],
283
+ llmOutput: { tokenUsage: {} },
284
+ },
285
+ 'run-123'
286
+ )
287
+
288
+ expect(mockEndSpan).toHaveBeenCalledWith(
289
+ context,
290
+ traceId,
291
+ 'span-123',
292
+ expect.objectContaining({
293
+ output: {
294
+ generations: ['Message response'],
295
+ },
296
+ })
297
+ )
298
+ })
299
+
300
+ it('should handle missing token usage', async () => {
301
+ mockEndSpan.mockResolvedValue(undefined)
302
+
303
+ await handler.handleLLMEnd(
304
+ {
305
+ generations: [{ text: 'response' }],
306
+ llmOutput: {},
307
+ },
308
+ 'run-123'
309
+ )
310
+
311
+ expect(mockEndSpan).toHaveBeenCalledWith(
312
+ context,
313
+ traceId,
314
+ 'span-123',
315
+ expect.objectContaining({
316
+ tokens: undefined,
317
+ })
318
+ )
319
+ })
320
+
321
+ it('should do nothing if span was not started', async () => {
322
+ await handler.handleLLMEnd({ generations: [] }, 'unknown-run-id')
323
+
324
+ expect(mockEndSpan).not.toHaveBeenCalled()
325
+ })
326
+
327
+ it('should handle errors gracefully', async () => {
328
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
329
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
330
+
331
+ await handler.handleLLMEnd({ generations: [] }, 'run-123')
332
+
333
+ expect(consoleSpy).toHaveBeenCalledWith(
334
+ '[TracingCallbackHandler] handleLLMEnd error:',
335
+ expect.any(Error)
336
+ )
337
+ consoleSpy.mockRestore()
338
+ })
339
+ })
340
+
341
+ describe('handleLLMError', () => {
342
+ beforeEach(async () => {
343
+ // Setup: Start an LLM span first
344
+ mockStartSpan.mockResolvedValue({
345
+ spanId: 'span-123',
346
+ traceId,
347
+ name: 'LLM: gpt-4o',
348
+ type: 'llm',
349
+ depth: 0,
350
+ startedAt: new Date(),
351
+ })
352
+ await handler.handleLLMStart(
353
+ { id: ['langchain', 'llms', 'openai'] } as Serialized,
354
+ ['test'],
355
+ 'run-123'
356
+ )
357
+ jest.clearAllMocks()
358
+ })
359
+
360
+ it('should end span with error', async () => {
361
+ mockEndSpan.mockResolvedValue(undefined)
362
+ const error = new Error('LLM execution failed')
363
+
364
+ await handler.handleLLMError(error, 'run-123')
365
+
366
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
367
+ error,
368
+ })
369
+ })
370
+
371
+ it('should do nothing if span was not started', async () => {
372
+ await handler.handleLLMError(new Error('test'), 'unknown-run-id')
373
+
374
+ expect(mockEndSpan).not.toHaveBeenCalled()
375
+ })
376
+
377
+ it('should handle errors in error handling', async () => {
378
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
379
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
380
+
381
+ await handler.handleLLMError(new Error('LLM error'), 'run-123')
382
+
383
+ expect(consoleSpy).toHaveBeenCalledWith(
384
+ '[TracingCallbackHandler] handleLLMError error:',
385
+ expect.any(Error)
386
+ )
387
+ consoleSpy.mockRestore()
388
+ })
389
+ })
390
+
391
+ describe('handleToolStart', () => {
392
+ it('should create span for tool call', async () => {
393
+ const tool: Serialized = {
394
+ id: ['langchain', 'tools', 'search'],
395
+ }
396
+ mockStartSpan.mockResolvedValue({
397
+ spanId: 'span-123',
398
+ traceId,
399
+ name: 'Tool: search',
400
+ type: 'tool',
401
+ depth: 0,
402
+ startedAt: new Date(),
403
+ })
404
+
405
+ await handler.handleToolStart(tool, 'query term', 'run-123')
406
+
407
+ expect(mockStartSpan).toHaveBeenCalledWith(context, traceId, {
408
+ name: 'Tool: search',
409
+ type: 'tool',
410
+ toolName: 'search',
411
+ parentSpanId: undefined,
412
+ depth: 0,
413
+ input: { toolInput: 'query term' },
414
+ })
415
+ })
416
+
417
+ it('should extract tool name from serialized id', async () => {
418
+ const tool: Serialized = {
419
+ id: ['langchain', 'tools', 'calculator'],
420
+ }
421
+ mockStartSpan.mockResolvedValue(null)
422
+
423
+ await handler.handleToolStart(tool, '2 + 2', 'run-123')
424
+
425
+ expect(mockStartSpan).toHaveBeenCalledWith(
426
+ context,
427
+ traceId,
428
+ expect.objectContaining({
429
+ toolName: 'calculator',
430
+ })
431
+ )
432
+ })
433
+
434
+ it('should use "unknown" for missing tool name', async () => {
435
+ const tool: Serialized = { id: [] }
436
+ mockStartSpan.mockResolvedValue(null)
437
+
438
+ await handler.handleToolStart(tool, 'input', 'run-123')
439
+
440
+ expect(mockStartSpan).toHaveBeenCalledWith(
441
+ context,
442
+ traceId,
443
+ expect.objectContaining({
444
+ toolName: 'unknown',
445
+ })
446
+ )
447
+ })
448
+
449
+ it('should handle nested tool calls with depth', async () => {
450
+ // Grandparent span
451
+ mockStartSpan.mockResolvedValueOnce({
452
+ spanId: 'span-grandparent',
453
+ traceId,
454
+ name: 'Tool: search',
455
+ type: 'tool',
456
+ depth: 0,
457
+ startedAt: new Date(),
458
+ })
459
+ await handler.handleToolStart(
460
+ { id: ['langchain', 'tools', 'search'] } as Serialized,
461
+ 'query1',
462
+ 'run-grandparent'
463
+ )
464
+
465
+ // Parent span
466
+ mockStartSpan.mockResolvedValueOnce({
467
+ spanId: 'span-parent',
468
+ traceId,
469
+ name: 'Tool: search',
470
+ type: 'tool',
471
+ depth: 1,
472
+ startedAt: new Date(),
473
+ })
474
+ await handler.handleToolStart(
475
+ { id: ['langchain', 'tools', 'search'] } as Serialized,
476
+ 'query',
477
+ 'run-parent',
478
+ 'run-grandparent'
479
+ )
480
+
481
+ // Child span
482
+ mockStartSpan.mockResolvedValueOnce({
483
+ spanId: 'span-child',
484
+ traceId,
485
+ name: 'Tool: calculator',
486
+ type: 'tool',
487
+ depth: 2,
488
+ startedAt: new Date(),
489
+ })
490
+ await handler.handleToolStart(
491
+ { id: ['langchain', 'tools', 'calculator'] } as Serialized,
492
+ '2 + 2',
493
+ 'run-child',
494
+ 'run-parent'
495
+ )
496
+
497
+ expect(mockStartSpan).toHaveBeenLastCalledWith(
498
+ context,
499
+ traceId,
500
+ expect.objectContaining({
501
+ depth: 2,
502
+ parentSpanId: 'span-parent',
503
+ })
504
+ )
505
+ })
506
+
507
+ it('should handle errors gracefully', async () => {
508
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
509
+ mockStartSpan.mockRejectedValue(new Error('Database error'))
510
+
511
+ await handler.handleToolStart(
512
+ { id: ['langchain', 'tools', 'search'] } as Serialized,
513
+ 'query',
514
+ 'run-123'
515
+ )
516
+
517
+ expect(consoleSpy).toHaveBeenCalledWith(
518
+ '[TracingCallbackHandler] handleToolStart error:',
519
+ expect.any(Error)
520
+ )
521
+ consoleSpy.mockRestore()
522
+ })
523
+ })
524
+
525
+ describe('handleToolEnd', () => {
526
+ beforeEach(async () => {
527
+ // Setup: Start a tool span first
528
+ mockStartSpan.mockResolvedValue({
529
+ spanId: 'span-123',
530
+ traceId,
531
+ name: 'Tool: search',
532
+ type: 'tool',
533
+ depth: 0,
534
+ startedAt: new Date(),
535
+ })
536
+ await handler.handleToolStart(
537
+ { id: ['langchain', 'tools', 'search'] } as Serialized,
538
+ 'query',
539
+ 'run-123'
540
+ )
541
+ jest.clearAllMocks()
542
+ })
543
+
544
+ it('should end span with tool output', async () => {
545
+ mockEndSpan.mockResolvedValue(undefined)
546
+
547
+ await handler.handleToolEnd('Search results: item1, item2', 'run-123')
548
+
549
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
550
+ toolOutput: 'Search results: item1, item2',
551
+ })
552
+ })
553
+
554
+ it('should do nothing if span was not started', async () => {
555
+ await handler.handleToolEnd('output', 'unknown-run-id')
556
+
557
+ expect(mockEndSpan).not.toHaveBeenCalled()
558
+ })
559
+
560
+ it('should handle errors gracefully', async () => {
561
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
562
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
563
+
564
+ await handler.handleToolEnd('output', 'run-123')
565
+
566
+ expect(consoleSpy).toHaveBeenCalledWith(
567
+ '[TracingCallbackHandler] handleToolEnd error:',
568
+ expect.any(Error)
569
+ )
570
+ consoleSpy.mockRestore()
571
+ })
572
+ })
573
+
574
+ describe('handleToolError', () => {
575
+ beforeEach(async () => {
576
+ // Setup: Start a tool span first
577
+ mockStartSpan.mockResolvedValue({
578
+ spanId: 'span-123',
579
+ traceId,
580
+ name: 'Tool: search',
581
+ type: 'tool',
582
+ depth: 0,
583
+ startedAt: new Date(),
584
+ })
585
+ await handler.handleToolStart(
586
+ { id: ['langchain', 'tools', 'search'] } as Serialized,
587
+ 'query',
588
+ 'run-123'
589
+ )
590
+ jest.clearAllMocks()
591
+ })
592
+
593
+ it('should end span with error', async () => {
594
+ mockEndSpan.mockResolvedValue(undefined)
595
+ const error = new Error('Tool execution failed')
596
+
597
+ await handler.handleToolError(error, 'run-123')
598
+
599
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
600
+ error,
601
+ })
602
+ })
603
+
604
+ it('should do nothing if span was not started', async () => {
605
+ await handler.handleToolError(new Error('test'), 'unknown-run-id')
606
+
607
+ expect(mockEndSpan).not.toHaveBeenCalled()
608
+ })
609
+
610
+ it('should handle errors in error handling', async () => {
611
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
612
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
613
+
614
+ await handler.handleToolError(new Error('Tool error'), 'run-123')
615
+
616
+ expect(consoleSpy).toHaveBeenCalledWith(
617
+ '[TracingCallbackHandler] handleToolError error:',
618
+ expect.any(Error)
619
+ )
620
+ consoleSpy.mockRestore()
621
+ })
622
+ })
623
+
624
+ describe('handleChainStart', () => {
625
+ it('should create span for chain execution', async () => {
626
+ const chain: Serialized = {
627
+ id: ['langchain', 'chains', 'llm_chain'],
628
+ }
629
+ mockStartSpan.mockResolvedValue({
630
+ spanId: 'span-123',
631
+ traceId,
632
+ name: 'Chain: llm_chain',
633
+ type: 'chain',
634
+ depth: 0,
635
+ startedAt: new Date(),
636
+ })
637
+
638
+ await handler.handleChainStart(chain, { input: 'test' }, 'run-123')
639
+
640
+ expect(mockStartSpan).toHaveBeenCalledWith(context, traceId, {
641
+ name: 'Chain: llm_chain',
642
+ type: 'chain',
643
+ parentSpanId: undefined,
644
+ depth: 0,
645
+ input: { input: 'test' },
646
+ })
647
+ })
648
+
649
+ it('should extract chain name from serialized id', async () => {
650
+ const chain: Serialized = {
651
+ id: ['langchain', 'chains', 'sequential_chain'],
652
+ }
653
+ mockStartSpan.mockResolvedValue(null)
654
+
655
+ await handler.handleChainStart(chain, {}, 'run-123')
656
+
657
+ expect(mockStartSpan).toHaveBeenCalledWith(
658
+ context,
659
+ traceId,
660
+ expect.objectContaining({
661
+ name: 'Chain: sequential_chain',
662
+ })
663
+ )
664
+ })
665
+
666
+ it('should use "unknown" for missing chain name', async () => {
667
+ const chain: Serialized = { id: [] }
668
+ mockStartSpan.mockResolvedValue(null)
669
+
670
+ await handler.handleChainStart(chain, {}, 'run-123')
671
+
672
+ expect(mockStartSpan).toHaveBeenCalledWith(
673
+ context,
674
+ traceId,
675
+ expect.objectContaining({
676
+ name: 'Chain: unknown',
677
+ })
678
+ )
679
+ })
680
+
681
+ it('should handle nested chains with depth', async () => {
682
+ // Grandparent chain
683
+ mockStartSpan.mockResolvedValueOnce({
684
+ spanId: 'span-grandparent',
685
+ traceId,
686
+ name: 'Chain: root',
687
+ type: 'chain',
688
+ depth: 0,
689
+ startedAt: new Date(),
690
+ })
691
+ await handler.handleChainStart(
692
+ { id: ['langchain', 'chains', 'root'] } as Serialized,
693
+ {},
694
+ 'run-grandparent'
695
+ )
696
+
697
+ // Parent chain
698
+ mockStartSpan.mockResolvedValueOnce({
699
+ spanId: 'span-parent',
700
+ traceId,
701
+ name: 'Chain: main',
702
+ type: 'chain',
703
+ depth: 1,
704
+ startedAt: new Date(),
705
+ })
706
+ await handler.handleChainStart(
707
+ { id: ['langchain', 'chains', 'main'] } as Serialized,
708
+ {},
709
+ 'run-parent',
710
+ 'run-grandparent'
711
+ )
712
+
713
+ // Child chain
714
+ mockStartSpan.mockResolvedValueOnce({
715
+ spanId: 'span-child',
716
+ traceId,
717
+ name: 'Chain: sub',
718
+ type: 'chain',
719
+ depth: 2,
720
+ startedAt: new Date(),
721
+ })
722
+ await handler.handleChainStart(
723
+ { id: ['langchain', 'chains', 'sub'] } as Serialized,
724
+ {},
725
+ 'run-child',
726
+ 'run-parent'
727
+ )
728
+
729
+ expect(mockStartSpan).toHaveBeenLastCalledWith(
730
+ context,
731
+ traceId,
732
+ expect.objectContaining({
733
+ depth: 2,
734
+ parentSpanId: 'span-parent',
735
+ })
736
+ )
737
+ })
738
+
739
+ it('should handle errors gracefully', async () => {
740
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
741
+ mockStartSpan.mockRejectedValue(new Error('Database error'))
742
+
743
+ await handler.handleChainStart(
744
+ { id: ['langchain', 'chains', 'test'] } as Serialized,
745
+ {},
746
+ 'run-123'
747
+ )
748
+
749
+ expect(consoleSpy).toHaveBeenCalledWith(
750
+ '[TracingCallbackHandler] handleChainStart error:',
751
+ expect.any(Error)
752
+ )
753
+ consoleSpy.mockRestore()
754
+ })
755
+ })
756
+
757
+ describe('handleChainEnd', () => {
758
+ beforeEach(async () => {
759
+ // Setup: Start a chain span first
760
+ mockStartSpan.mockResolvedValue({
761
+ spanId: 'span-123',
762
+ traceId,
763
+ name: 'Chain: test',
764
+ type: 'chain',
765
+ depth: 0,
766
+ startedAt: new Date(),
767
+ })
768
+ await handler.handleChainStart(
769
+ { id: ['langchain', 'chains', 'test'] } as Serialized,
770
+ {},
771
+ 'run-123'
772
+ )
773
+ jest.clearAllMocks()
774
+ })
775
+
776
+ it('should end span with chain outputs', async () => {
777
+ mockEndSpan.mockResolvedValue(undefined)
778
+
779
+ await handler.handleChainEnd({ output: 'result', metadata: { duration: 100 } }, 'run-123')
780
+
781
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
782
+ output: { output: 'result', metadata: { duration: 100 } },
783
+ })
784
+ })
785
+
786
+ it('should do nothing if span was not started', async () => {
787
+ await handler.handleChainEnd({}, 'unknown-run-id')
788
+
789
+ expect(mockEndSpan).not.toHaveBeenCalled()
790
+ })
791
+
792
+ it('should handle errors gracefully', async () => {
793
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
794
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
795
+
796
+ await handler.handleChainEnd({}, 'run-123')
797
+
798
+ expect(consoleSpy).toHaveBeenCalledWith(
799
+ '[TracingCallbackHandler] handleChainEnd error:',
800
+ expect.any(Error)
801
+ )
802
+ consoleSpy.mockRestore()
803
+ })
804
+ })
805
+
806
+ describe('handleChainError', () => {
807
+ beforeEach(async () => {
808
+ // Setup: Start a chain span first
809
+ mockStartSpan.mockResolvedValue({
810
+ spanId: 'span-123',
811
+ traceId,
812
+ name: 'Chain: test',
813
+ type: 'chain',
814
+ depth: 0,
815
+ startedAt: new Date(),
816
+ })
817
+ await handler.handleChainStart(
818
+ { id: ['langchain', 'chains', 'test'] } as Serialized,
819
+ {},
820
+ 'run-123'
821
+ )
822
+ jest.clearAllMocks()
823
+ })
824
+
825
+ it('should end span with error', async () => {
826
+ mockEndSpan.mockResolvedValue(undefined)
827
+ const error = new Error('Chain execution failed')
828
+
829
+ await handler.handleChainError(error, 'run-123')
830
+
831
+ expect(mockEndSpan).toHaveBeenCalledWith(context, traceId, 'span-123', {
832
+ error,
833
+ })
834
+ })
835
+
836
+ it('should do nothing if span was not started', async () => {
837
+ await handler.handleChainError(new Error('test'), 'unknown-run-id')
838
+
839
+ expect(mockEndSpan).not.toHaveBeenCalled()
840
+ })
841
+
842
+ it('should handle errors in error handling', async () => {
843
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation()
844
+ mockEndSpan.mockRejectedValue(new Error('Database error'))
845
+
846
+ await handler.handleChainError(new Error('Chain error'), 'run-123')
847
+
848
+ expect(consoleSpy).toHaveBeenCalledWith(
849
+ '[TracingCallbackHandler] handleChainError error:',
850
+ expect.any(Error)
851
+ )
852
+ consoleSpy.mockRestore()
853
+ })
854
+ })
855
+
856
+ describe('createTracingCallbacks factory', () => {
857
+ it('should create TracingCallbackHandler instance', () => {
858
+ const callbacks = createTracingCallbacks(context, traceId)
859
+
860
+ expect(callbacks).toBeInstanceOf(TracingCallbackHandler)
861
+ expect(callbacks.name).toBe('tracing_callback_handler')
862
+ })
863
+
864
+ it('should pass context and traceId to handler', async () => {
865
+ const callbacks = createTracingCallbacks(context, 'trace-xyz')
866
+ mockStartSpan.mockResolvedValue(null)
867
+
868
+ await callbacks.handleLLMStart(
869
+ { id: ['langchain', 'llms', 'openai'] } as Serialized,
870
+ ['test'],
871
+ 'run-123'
872
+ )
873
+
874
+ expect(mockStartSpan).toHaveBeenCalledWith(
875
+ context,
876
+ 'trace-xyz',
877
+ expect.any(Object)
878
+ )
879
+ })
880
+ })
881
+ })