ai-workflows 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +17 -1
  3. package/README.md +305 -184
  4. package/dist/barrier.d.ts +159 -0
  5. package/dist/barrier.d.ts.map +1 -0
  6. package/dist/barrier.js +377 -0
  7. package/dist/barrier.js.map +1 -0
  8. package/dist/cascade-context.d.ts +149 -0
  9. package/dist/cascade-context.d.ts.map +1 -0
  10. package/dist/cascade-context.js +324 -0
  11. package/dist/cascade-context.js.map +1 -0
  12. package/dist/cascade-executor.d.ts +196 -0
  13. package/dist/cascade-executor.d.ts.map +1 -0
  14. package/dist/cascade-executor.js +384 -0
  15. package/dist/cascade-executor.js.map +1 -0
  16. package/dist/context.d.ts.map +1 -1
  17. package/dist/context.js +27 -8
  18. package/dist/context.js.map +1 -1
  19. package/dist/cron-parser.d.ts +65 -0
  20. package/dist/cron-parser.d.ts.map +1 -0
  21. package/dist/cron-parser.js +294 -0
  22. package/dist/cron-parser.js.map +1 -0
  23. package/dist/cron-scheduler.d.ts +117 -0
  24. package/dist/cron-scheduler.d.ts.map +1 -0
  25. package/dist/cron-scheduler.js +176 -0
  26. package/dist/cron-scheduler.js.map +1 -0
  27. package/dist/database-context.d.ts +184 -0
  28. package/dist/database-context.d.ts.map +1 -0
  29. package/dist/database-context.js +428 -0
  30. package/dist/database-context.js.map +1 -0
  31. package/dist/dependency-graph.d.ts +157 -0
  32. package/dist/dependency-graph.d.ts.map +1 -0
  33. package/dist/dependency-graph.js +382 -0
  34. package/dist/dependency-graph.js.map +1 -0
  35. package/dist/digital-objects-adapter.d.ts +159 -0
  36. package/dist/digital-objects-adapter.d.ts.map +1 -0
  37. package/dist/digital-objects-adapter.js +229 -0
  38. package/dist/digital-objects-adapter.js.map +1 -0
  39. package/dist/durable-execution-cloudflare.d.ts +427 -0
  40. package/dist/durable-execution-cloudflare.d.ts.map +1 -0
  41. package/dist/durable-execution-cloudflare.js +510 -0
  42. package/dist/durable-execution-cloudflare.js.map +1 -0
  43. package/dist/durable-execution.d.ts +482 -0
  44. package/dist/durable-execution.d.ts.map +1 -0
  45. package/dist/durable-execution.js +594 -0
  46. package/dist/durable-execution.js.map +1 -0
  47. package/dist/durable-workflow.d.ts +176 -0
  48. package/dist/durable-workflow.d.ts.map +1 -0
  49. package/dist/durable-workflow.js +552 -0
  50. package/dist/durable-workflow.js.map +1 -0
  51. package/dist/every.d.ts +31 -2
  52. package/dist/every.d.ts.map +1 -1
  53. package/dist/every.js +63 -32
  54. package/dist/every.js.map +1 -1
  55. package/dist/graph/index.d.ts +8 -0
  56. package/dist/graph/index.d.ts.map +1 -0
  57. package/dist/graph/index.js +8 -0
  58. package/dist/graph/index.js.map +1 -0
  59. package/dist/graph/topological-sort.d.ts +121 -0
  60. package/dist/graph/topological-sort.d.ts.map +1 -0
  61. package/dist/graph/topological-sort.js +292 -0
  62. package/dist/graph/topological-sort.js.map +1 -0
  63. package/dist/index.d.ts +10 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +25 -0
  66. package/dist/index.js.map +1 -1
  67. package/dist/logger.d.ts +101 -0
  68. package/dist/logger.d.ts.map +1 -0
  69. package/dist/logger.js +115 -0
  70. package/dist/logger.js.map +1 -0
  71. package/dist/on.d.ts +35 -10
  72. package/dist/on.d.ts.map +1 -1
  73. package/dist/on.js +53 -19
  74. package/dist/on.js.map +1 -1
  75. package/dist/runtime.d.ts +169 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +275 -0
  78. package/dist/runtime.js.map +1 -0
  79. package/dist/send.d.ts.map +1 -1
  80. package/dist/send.js +4 -3
  81. package/dist/send.js.map +1 -1
  82. package/dist/telemetry.d.ts +150 -0
  83. package/dist/telemetry.d.ts.map +1 -0
  84. package/dist/telemetry.js +388 -0
  85. package/dist/telemetry.js.map +1 -0
  86. package/dist/timer-registry.d.ts +77 -0
  87. package/dist/timer-registry.d.ts.map +1 -0
  88. package/dist/timer-registry.js +154 -0
  89. package/dist/timer-registry.js.map +1 -0
  90. package/dist/types.d.ts +105 -6
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +17 -1
  93. package/dist/types.js.map +1 -1
  94. package/dist/worker/durable-step.d.ts +481 -0
  95. package/dist/worker/durable-step.d.ts.map +1 -0
  96. package/dist/worker/durable-step.js +606 -0
  97. package/dist/worker/durable-step.js.map +1 -0
  98. package/dist/worker/index.d.ts +106 -0
  99. package/dist/worker/index.d.ts.map +1 -0
  100. package/dist/worker/index.js +124 -0
  101. package/dist/worker/index.js.map +1 -0
  102. package/dist/worker/state-adapter.d.ts +230 -0
  103. package/dist/worker/state-adapter.d.ts.map +1 -0
  104. package/dist/worker/state-adapter.js +409 -0
  105. package/dist/worker/state-adapter.js.map +1 -0
  106. package/dist/worker/topological-executor.d.ts +282 -0
  107. package/dist/worker/topological-executor.d.ts.map +1 -0
  108. package/dist/worker/topological-executor.js +396 -0
  109. package/dist/worker/topological-executor.js.map +1 -0
  110. package/dist/worker/workflow-builder.d.ts +286 -0
  111. package/dist/worker/workflow-builder.d.ts.map +1 -0
  112. package/dist/worker/workflow-builder.js +565 -0
  113. package/dist/worker/workflow-builder.js.map +1 -0
  114. package/dist/worker.d.ts +800 -0
  115. package/dist/worker.d.ts.map +1 -0
  116. package/dist/worker.js +2428 -0
  117. package/dist/worker.js.map +1 -0
  118. package/dist/workflow-builder.d.ts +287 -0
  119. package/dist/workflow-builder.d.ts.map +1 -0
  120. package/dist/workflow-builder.js +762 -0
  121. package/dist/workflow-builder.js.map +1 -0
  122. package/dist/workflow.d.ts +14 -30
  123. package/dist/workflow.d.ts.map +1 -1
  124. package/dist/workflow.js +136 -292
  125. package/dist/workflow.js.map +1 -1
  126. package/examples/01-ecommerce-order-pipeline.ts +358 -0
  127. package/examples/02-content-moderation-cascade.ts +454 -0
  128. package/examples/03-scheduled-reporting-dependencies.ts +479 -0
  129. package/examples/04-database-persistence.ts +518 -0
  130. package/examples/README.md +173 -0
  131. package/package.json +21 -4
  132. package/src/__tests__/digital-objects-adapter.test.ts +274 -0
  133. package/src/__tests__/durable-workflow.test.ts +297 -0
  134. package/src/barrier.ts +507 -0
  135. package/src/cascade-context.ts +495 -0
  136. package/src/cascade-executor.ts +588 -0
  137. package/src/context.ts +51 -17
  138. package/src/cron-parser.ts +347 -0
  139. package/src/cron-scheduler.ts +239 -0
  140. package/src/database-context.ts +658 -0
  141. package/src/dependency-graph.ts +518 -0
  142. package/src/digital-objects-adapter.ts +351 -0
  143. package/src/durable-execution-cloudflare.ts +855 -0
  144. package/src/durable-execution.ts +1042 -0
  145. package/src/durable-workflow.ts +717 -0
  146. package/src/every.ts +104 -35
  147. package/src/graph/index.ts +19 -0
  148. package/src/graph/topological-sort.ts +412 -0
  149. package/src/index.ts +147 -0
  150. package/src/logger.ts +148 -0
  151. package/src/on.ts +81 -26
  152. package/src/runtime.ts +436 -0
  153. package/src/send.ts +4 -5
  154. package/src/telemetry.ts +577 -0
  155. package/src/timer-registry.ts +179 -0
  156. package/src/types.ts +146 -10
  157. package/src/worker/durable-step.ts +976 -0
  158. package/src/worker/index.ts +216 -0
  159. package/src/worker/state-adapter.ts +589 -0
  160. package/src/worker/topological-executor.ts +625 -0
  161. package/src/worker/workflow-builder.ts +871 -0
  162. package/src/worker.ts +2906 -0
  163. package/src/workflow-builder.ts +1068 -0
  164. package/src/workflow.ts +199 -355
  165. package/test/barrier-join.test.ts +442 -0
  166. package/test/barrier-unhandled-rejections.test.ts +359 -0
  167. package/test/cascade-context.test.ts +390 -0
  168. package/test/cascade-executor.test.ts +852 -0
  169. package/test/cron-parser.test.ts +314 -0
  170. package/test/cron-scheduler.test.ts +291 -0
  171. package/test/database-context.test.ts +770 -0
  172. package/test/db-provider-adapter.test.ts +862 -0
  173. package/test/dependency-graph.test.ts +512 -0
  174. package/test/durable-execution-cloudflare.test.ts +606 -0
  175. package/test/durable-execution-in-process.test.ts +286 -0
  176. package/test/durable-execution.test.ts +247 -0
  177. package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
  178. package/test/graph/topological-sort.test.ts +586 -0
  179. package/test/integration.test.ts +442 -0
  180. package/test/rpc-surface.test.ts +946 -0
  181. package/test/runtime.test.ts +262 -0
  182. package/test/schedule-timer-cleanup.test.ts +353 -0
  183. package/test/send-race-conditions.test.ts +400 -0
  184. package/test/type-safety-every.test.ts +303 -0
  185. package/test/worker/durable-cascade.test.ts +1117 -0
  186. package/test/worker/durable-step.test.ts +723 -0
  187. package/test/worker/topological-executor.test.ts +1240 -0
  188. package/test/worker/workflow-builder.test.ts +1067 -0
  189. package/test/worker.test.ts +608 -0
  190. package/test/workflow-builder.test.ts +1670 -0
  191. package/test/workflow-cron.test.ts +256 -0
  192. package/test/workflow-state-adapter.test.ts +923 -0
  193. package/test/workflow.test.ts +25 -22
  194. package/tsconfig.json +3 -1
  195. package/vitest.config.ts +38 -1
  196. package/vitest.workers.config.ts +44 -0
  197. package/wrangler.jsonc +22 -0
  198. package/.turbo/turbo-test.log +0 -7
  199. package/src/context.js +0 -83
  200. package/src/every.js +0 -267
  201. package/src/index.js +0 -71
  202. package/src/on.js +0 -79
  203. package/src/send.js +0 -111
  204. package/src/types.js +0 -4
  205. package/src/workflow.js +0 -455
  206. package/test/context.test.js +0 -116
  207. package/test/every.test.js +0 -282
  208. package/test/on.test.js +0 -80
  209. package/test/send.test.js +0 -89
  210. package/test/workflow.test.js +0 -224
  211. package/vitest.config.js +0 -7
@@ -0,0 +1,577 @@
1
+ /**
2
+ * OpenTelemetry Integration for ai-workflows
3
+ *
4
+ * Provides instrumented wrappers and telemetry utilities for workflow execution.
5
+ * Integrates with cascade-context for distributed tracing support.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { Workflow } from 'ai-workflows'
10
+ * import { withWorkflowTelemetry, instrumentWorkflow } from 'ai-workflows/telemetry'
11
+ *
12
+ * // Enable telemetry globally
13
+ * withWorkflowTelemetry({ provider: createConsoleTelemetryProvider() }, async () => {
14
+ * const workflow = Workflow($ => {
15
+ * $.on.Customer.created(async (customer) => {
16
+ * // Traced automatically
17
+ * })
18
+ * })
19
+ * await workflow.start()
20
+ * })
21
+ * ```
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+
26
+ import {
27
+ getTracer,
28
+ getMeter,
29
+ getLogger,
30
+ setTelemetryProvider,
31
+ getTelemetryProvider,
32
+ createHandlerMetrics,
33
+ SemanticAttributes,
34
+ MetricNames,
35
+ createTraceparent,
36
+ parseTraceparent,
37
+ generateTraceId,
38
+ generateSpanId,
39
+ type Tracer,
40
+ type Meter,
41
+ type Logger,
42
+ type Span,
43
+ type SpanAttributes,
44
+ type TelemetryProvider,
45
+ type TraceContext,
46
+ type Counter,
47
+ type Histogram,
48
+ } from '@org.ai/types'
49
+
50
+ import type { CascadeContext, CascadeStep } from './cascade-context.js'
51
+
52
+ // Package info
53
+ const PACKAGE_NAME = 'ai-workflows'
54
+ const PACKAGE_VERSION = '2.1.4'
55
+
56
+ // ============================================================================
57
+ // Package-level Telemetry
58
+ // ============================================================================
59
+
60
+ let packageTracer: Tracer | undefined
61
+ let packageMeter: Meter | undefined
62
+ let packageLogger: Logger | undefined
63
+ let workflowMetrics: ReturnType<typeof createWorkflowMetrics> | undefined
64
+
65
+ /**
66
+ * Get the tracer for ai-workflows
67
+ */
68
+ export function getWorkflowTracer(): Tracer {
69
+ if (!packageTracer) {
70
+ packageTracer = getTracer(PACKAGE_NAME, PACKAGE_VERSION)
71
+ }
72
+ return packageTracer
73
+ }
74
+
75
+ /**
76
+ * Get the meter for ai-workflows
77
+ */
78
+ export function getWorkflowMeter(): Meter {
79
+ if (!packageMeter) {
80
+ packageMeter = getMeter(PACKAGE_NAME, PACKAGE_VERSION)
81
+ }
82
+ return packageMeter
83
+ }
84
+
85
+ /**
86
+ * Get the logger for ai-workflows
87
+ */
88
+ export function getWorkflowLogger(): Logger {
89
+ if (!packageLogger) {
90
+ packageLogger = getLogger(PACKAGE_NAME)
91
+ }
92
+ return packageLogger
93
+ }
94
+
95
+ /**
96
+ * Create workflow-specific metrics
97
+ */
98
+ export function createWorkflowMetrics(meter: Meter) {
99
+ return {
100
+ stepDuration: meter.createHistogram(
101
+ MetricNames.WORKFLOW_STEP_DURATION,
102
+ 'Duration of workflow steps',
103
+ 'ms'
104
+ ),
105
+ stepTotal: meter.createCounter(
106
+ MetricNames.WORKFLOW_STEP_TOTAL,
107
+ 'Total number of workflow steps executed'
108
+ ),
109
+ stepErrors: meter.createCounter(
110
+ MetricNames.WORKFLOW_STEP_ERRORS,
111
+ 'Number of failed workflow steps'
112
+ ),
113
+ eventHandlers: meter.createCounter('workflow.event.handlers', 'Event handler invocations'),
114
+ scheduleHandlers: meter.createCounter(
115
+ 'workflow.schedule.handlers',
116
+ 'Schedule handler invocations'
117
+ ),
118
+ cascadeDepth: meter.createHistogram('workflow.cascade.depth', 'Cascade context depth'),
119
+ cascadeDuration: meter.createHistogram(
120
+ 'workflow.cascade.duration',
121
+ 'Total cascade execution duration',
122
+ 'ms'
123
+ ),
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Get workflow metrics for the package
129
+ */
130
+ export function getWorkflowMetrics() {
131
+ if (!workflowMetrics) {
132
+ workflowMetrics = createWorkflowMetrics(getWorkflowMeter())
133
+ }
134
+ return workflowMetrics
135
+ }
136
+
137
+ /**
138
+ * Reset cached telemetry instances
139
+ */
140
+ export function resetTelemetry(): void {
141
+ packageTracer = undefined
142
+ packageMeter = undefined
143
+ packageLogger = undefined
144
+ workflowMetrics = undefined
145
+ }
146
+
147
+ // ============================================================================
148
+ // Telemetry Context
149
+ // ============================================================================
150
+
151
+ /**
152
+ * Options for withWorkflowTelemetry
153
+ */
154
+ export interface WithWorkflowTelemetryOptions {
155
+ /** Telemetry provider to use */
156
+ provider?: TelemetryProvider
157
+ /** Workflow name for labeling */
158
+ workflowName?: string
159
+ }
160
+
161
+ /**
162
+ * Execute a function with workflow telemetry enabled
163
+ */
164
+ export async function withWorkflowTelemetry<T>(
165
+ options: WithWorkflowTelemetryOptions,
166
+ fn: () => Promise<T>
167
+ ): Promise<T> {
168
+ const previousProvider = getTelemetryProvider()
169
+
170
+ if (options.provider) {
171
+ setTelemetryProvider(options.provider)
172
+ resetTelemetry()
173
+ }
174
+
175
+ try {
176
+ return await fn()
177
+ } finally {
178
+ if (options.provider) {
179
+ setTelemetryProvider(previousProvider)
180
+ resetTelemetry()
181
+ }
182
+ }
183
+ }
184
+
185
+ // ============================================================================
186
+ // Cascade Context Integration
187
+ // ============================================================================
188
+
189
+ /**
190
+ * Create a span from a CascadeContext
191
+ *
192
+ * This bridges the cascade-context W3C Trace Context support with OpenTelemetry.
193
+ */
194
+ export function spanFromCascadeContext(ctx: CascadeContext, name?: string): Span {
195
+ const tracer = getWorkflowTracer()
196
+ const traceContext = ctx.toTraceContext()
197
+
198
+ return tracer.startSpan(name || ctx.name || 'cascade', {
199
+ kind: 'internal',
200
+ parent: traceContext,
201
+ attributes: {
202
+ [SemanticAttributes.WORKFLOW_NAME]: ctx.name || 'cascade',
203
+ 'cascade.correlationId': ctx.correlationId,
204
+ 'cascade.spanId': ctx.spanId,
205
+ 'cascade.depth': ctx.depth,
206
+ ...(ctx.parentId && { 'cascade.parentId': ctx.parentId }),
207
+ },
208
+ })
209
+ }
210
+
211
+ /**
212
+ * Create a TraceContext from a span
213
+ *
214
+ * Allows propagating span context to cascade-context.
215
+ */
216
+ export function traceContextFromSpan(span: Span): TraceContext {
217
+ return span.getTraceContext()
218
+ }
219
+
220
+ /**
221
+ * Record a CascadeStep as a span event
222
+ */
223
+ export function recordStepAsSpanEvent(span: Span, step: CascadeStep): void {
224
+ const attrs: SpanAttributes = {
225
+ 'step.status': step.status,
226
+ ...(step.duration !== undefined && { 'step.duration': step.duration }),
227
+ ...(step.metadata &&
228
+ Object.keys(step.metadata).length > 0 && { 'step.metadata': JSON.stringify(step.metadata) }),
229
+ }
230
+
231
+ span.addEvent(step.name, attrs)
232
+ }
233
+
234
+ /**
235
+ * Convert CascadeContext steps to span events
236
+ */
237
+ export function cascadeStepsToSpanEvents(span: Span, ctx: CascadeContext): void {
238
+ for (const step of ctx.steps) {
239
+ recordStepAsSpanEvent(span, step)
240
+ }
241
+ }
242
+
243
+ // ============================================================================
244
+ // Event Handler Instrumentation
245
+ // ============================================================================
246
+
247
+ /**
248
+ * Record an event handler invocation
249
+ */
250
+ export function recordEventHandler(params: {
251
+ event: string
252
+ durationMs: number
253
+ success: boolean
254
+ }): void {
255
+ const metrics = getWorkflowMetrics()
256
+ const labels = {
257
+ event: params.event,
258
+ status: params.success ? 'success' : 'error',
259
+ }
260
+
261
+ metrics.eventHandlers.add(1, labels)
262
+ metrics.stepDuration.record(params.durationMs, { ...labels, type: 'event' })
263
+ metrics.stepTotal.add(1, { ...labels, type: 'event' })
264
+
265
+ if (!params.success) {
266
+ metrics.stepErrors.add(1, { ...labels, type: 'event' })
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Record a schedule handler invocation
272
+ */
273
+ export function recordScheduleHandler(params: {
274
+ schedule: string
275
+ durationMs: number
276
+ success: boolean
277
+ }): void {
278
+ const metrics = getWorkflowMetrics()
279
+ const labels = {
280
+ schedule: params.schedule,
281
+ status: params.success ? 'success' : 'error',
282
+ }
283
+
284
+ metrics.scheduleHandlers.add(1, labels)
285
+ metrics.stepDuration.record(params.durationMs, { ...labels, type: 'schedule' })
286
+ metrics.stepTotal.add(1, { ...labels, type: 'schedule' })
287
+
288
+ if (!params.success) {
289
+ metrics.stepErrors.add(1, { ...labels, type: 'schedule' })
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Create a traced event handler
295
+ */
296
+ export function tracedEventHandler<TData, TResult>(
297
+ event: string,
298
+ handler: (data: TData, ctx: any) => Promise<TResult>
299
+ ): (data: TData, ctx: any) => Promise<TResult> {
300
+ return async (data: TData, ctx: any): Promise<TResult> => {
301
+ const tracer = getWorkflowTracer()
302
+ const logger = getWorkflowLogger()
303
+ const startTime = Date.now()
304
+
305
+ return tracer.withSpan(
306
+ `workflow.event.${event}`,
307
+ {
308
+ kind: 'consumer',
309
+ attributes: {
310
+ [SemanticAttributes.WORKFLOW_NAME]: event,
311
+ 'workflow.event': event,
312
+ },
313
+ },
314
+ async (span) => {
315
+ logger.info(`Event handler ${event} started`)
316
+
317
+ try {
318
+ const result = await handler(data, ctx)
319
+ const durationMs = Date.now() - startTime
320
+
321
+ recordEventHandler({
322
+ event,
323
+ durationMs,
324
+ success: true,
325
+ })
326
+
327
+ span.setStatus('ok')
328
+ logger.info(`Event handler ${event} completed`, { durationMs })
329
+
330
+ return result
331
+ } catch (error) {
332
+ const durationMs = Date.now() - startTime
333
+ const message = error instanceof Error ? error.message : String(error)
334
+
335
+ recordEventHandler({
336
+ event,
337
+ durationMs,
338
+ success: false,
339
+ })
340
+
341
+ span.setStatus('error', message)
342
+ logger.error(
343
+ `Event handler ${event} failed`,
344
+ error instanceof Error ? error : undefined,
345
+ { durationMs }
346
+ )
347
+
348
+ throw error
349
+ }
350
+ }
351
+ ) as Promise<TResult>
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Create a traced schedule handler
357
+ */
358
+ export function tracedScheduleHandler<TResult>(
359
+ schedule: string,
360
+ handler: (ctx: any) => Promise<TResult>
361
+ ): (ctx: any) => Promise<TResult> {
362
+ return async (ctx: any): Promise<TResult> => {
363
+ const tracer = getWorkflowTracer()
364
+ const logger = getWorkflowLogger()
365
+ const startTime = Date.now()
366
+
367
+ return tracer.withSpan(
368
+ `workflow.schedule.${schedule}`,
369
+ {
370
+ kind: 'internal',
371
+ attributes: {
372
+ [SemanticAttributes.WORKFLOW_NAME]: schedule,
373
+ 'workflow.schedule': schedule,
374
+ },
375
+ },
376
+ async (span) => {
377
+ logger.info(`Schedule handler ${schedule} started`)
378
+
379
+ try {
380
+ const result = await handler(ctx)
381
+ const durationMs = Date.now() - startTime
382
+
383
+ recordScheduleHandler({
384
+ schedule,
385
+ durationMs,
386
+ success: true,
387
+ })
388
+
389
+ span.setStatus('ok')
390
+ logger.info(`Schedule handler ${schedule} completed`, { durationMs })
391
+
392
+ return result
393
+ } catch (error) {
394
+ const durationMs = Date.now() - startTime
395
+ const message = error instanceof Error ? error.message : String(error)
396
+
397
+ recordScheduleHandler({
398
+ schedule,
399
+ durationMs,
400
+ success: false,
401
+ })
402
+
403
+ span.setStatus('error', message)
404
+ logger.error(
405
+ `Schedule handler ${schedule} failed`,
406
+ error instanceof Error ? error : undefined,
407
+ { durationMs }
408
+ )
409
+
410
+ throw error
411
+ }
412
+ }
413
+ ) as Promise<TResult>
414
+ }
415
+ }
416
+
417
+ // ============================================================================
418
+ // Workflow Step Instrumentation
419
+ // ============================================================================
420
+
421
+ /**
422
+ * Record a workflow step
423
+ */
424
+ export function recordWorkflowStep(params: {
425
+ step: string
426
+ workflow?: string | undefined
427
+ durationMs: number
428
+ success: boolean
429
+ tier?: string | undefined
430
+ }): void {
431
+ const metrics = getWorkflowMetrics()
432
+ const labels = {
433
+ step: params.step,
434
+ workflow: params.workflow || 'default',
435
+ status: params.success ? 'success' : 'error',
436
+ ...(params.tier && { tier: params.tier }),
437
+ }
438
+
439
+ metrics.stepTotal.add(1, labels)
440
+ metrics.stepDuration.record(params.durationMs, labels)
441
+
442
+ if (!params.success) {
443
+ metrics.stepErrors.add(1, labels)
444
+ }
445
+ }
446
+
447
+ /**
448
+ * Create a traced workflow step
449
+ */
450
+ export function tracedStep<TArgs extends unknown[], TResult>(
451
+ stepName: string,
452
+ fn: (...args: TArgs) => Promise<TResult>,
453
+ options: {
454
+ workflowName?: string
455
+ tier?: string
456
+ } = {}
457
+ ): (...args: TArgs) => Promise<TResult> {
458
+ return async (...args: TArgs): Promise<TResult> => {
459
+ const tracer = getWorkflowTracer()
460
+ const logger = getWorkflowLogger()
461
+ const startTime = Date.now()
462
+
463
+ const attributes: SpanAttributes = {
464
+ [SemanticAttributes.WORKFLOW_STEP]: stepName,
465
+ ...(options.workflowName && { [SemanticAttributes.WORKFLOW_NAME]: options.workflowName }),
466
+ ...(options.tier && { [SemanticAttributes.AI_TIER]: options.tier }),
467
+ }
468
+
469
+ return tracer.withSpan(
470
+ `workflow.step.${stepName}`,
471
+ { kind: 'internal', attributes },
472
+ async (span) => {
473
+ logger.debug(`Step ${stepName} started`, {
474
+ workflow: options.workflowName,
475
+ tier: options.tier,
476
+ })
477
+
478
+ try {
479
+ const result = await fn(...args)
480
+ const durationMs = Date.now() - startTime
481
+
482
+ recordWorkflowStep({
483
+ step: stepName,
484
+ workflow: options.workflowName,
485
+ durationMs,
486
+ success: true,
487
+ tier: options.tier,
488
+ })
489
+
490
+ span.setStatus('ok')
491
+ return result
492
+ } catch (error) {
493
+ const durationMs = Date.now() - startTime
494
+ const message = error instanceof Error ? error.message : String(error)
495
+
496
+ recordWorkflowStep({
497
+ step: stepName,
498
+ workflow: options.workflowName,
499
+ durationMs,
500
+ success: false,
501
+ tier: options.tier,
502
+ })
503
+
504
+ span.setStatus('error', message)
505
+ throw error
506
+ }
507
+ }
508
+ ) as Promise<TResult>
509
+ }
510
+ }
511
+
512
+ // ============================================================================
513
+ // Span Helpers
514
+ // ============================================================================
515
+
516
+ /**
517
+ * Start a span for a workflow operation
518
+ */
519
+ export function startWorkflowSpan(name: string, attributes?: SpanAttributes): Span {
520
+ const tracer = getWorkflowTracer()
521
+ return tracer.startSpan(name, {
522
+ kind: 'internal',
523
+ attributes: {
524
+ [SemanticAttributes.SERVICE_NAME]: PACKAGE_NAME,
525
+ ...attributes,
526
+ },
527
+ })
528
+ }
529
+
530
+ /**
531
+ * Execute code within a workflow span
532
+ */
533
+ export async function withWorkflowSpan<T>(
534
+ name: string,
535
+ fn: (span: Span) => Promise<T>,
536
+ attributes?: SpanAttributes
537
+ ): Promise<T> {
538
+ const tracer = getWorkflowTracer()
539
+ return tracer.withSpan(name, { kind: 'internal', attributes }, fn) as Promise<T>
540
+ }
541
+
542
+ // ============================================================================
543
+ // Re-exports from @org.ai/types
544
+ // ============================================================================
545
+
546
+ export {
547
+ // Core types
548
+ type Tracer,
549
+ type Meter,
550
+ type Logger,
551
+ type Span,
552
+ type SpanAttributes,
553
+ type TelemetryProvider,
554
+ type TraceContext,
555
+ type Counter,
556
+ type Histogram,
557
+
558
+ // Global functions
559
+ getTracer,
560
+ getMeter,
561
+ getLogger,
562
+ setTelemetryProvider,
563
+ getTelemetryProvider,
564
+
565
+ // W3C Trace Context
566
+ createTraceparent,
567
+ parseTraceparent,
568
+ generateTraceId,
569
+ generateSpanId,
570
+
571
+ // Constants
572
+ SemanticAttributes,
573
+ MetricNames,
574
+
575
+ // Utilities
576
+ createHandlerMetrics,
577
+ } from '@org.ai/types'