ai-workflows 2.1.3 → 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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +8 -1
- package/README.md +2 -0
- package/dist/barrier.d.ts +6 -0
- package/dist/barrier.d.ts.map +1 -1
- package/dist/barrier.js +45 -7
- package/dist/barrier.js.map +1 -1
- package/dist/cascade-context.d.ts.map +1 -1
- package/dist/cascade-context.js +25 -25
- package/dist/cascade-context.js.map +1 -1
- package/dist/cascade-executor.d.ts.map +1 -1
- package/dist/cascade-executor.js +1 -1
- package/dist/cascade-executor.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +23 -7
- package/dist/context.js.map +1 -1
- package/dist/cron-parser.d.ts +65 -0
- package/dist/cron-parser.d.ts.map +1 -0
- package/dist/cron-parser.js +294 -0
- package/dist/cron-parser.js.map +1 -0
- package/dist/cron-scheduler.d.ts +117 -0
- package/dist/cron-scheduler.d.ts.map +1 -0
- package/dist/cron-scheduler.js +176 -0
- package/dist/cron-scheduler.js.map +1 -0
- package/dist/database-context.d.ts +184 -0
- package/dist/database-context.d.ts.map +1 -0
- package/dist/database-context.js +428 -0
- package/dist/database-context.js.map +1 -0
- package/dist/digital-objects-adapter.d.ts +159 -0
- package/dist/digital-objects-adapter.d.ts.map +1 -0
- package/dist/digital-objects-adapter.js +229 -0
- package/dist/digital-objects-adapter.js.map +1 -0
- package/dist/durable-execution-cloudflare.d.ts +427 -0
- package/dist/durable-execution-cloudflare.d.ts.map +1 -0
- package/dist/durable-execution-cloudflare.js +510 -0
- package/dist/durable-execution-cloudflare.js.map +1 -0
- package/dist/durable-execution.d.ts +482 -0
- package/dist/durable-execution.d.ts.map +1 -0
- package/dist/durable-execution.js +594 -0
- package/dist/durable-execution.js.map +1 -0
- package/dist/durable-workflow.d.ts +176 -0
- package/dist/durable-workflow.d.ts.map +1 -0
- package/dist/durable-workflow.js +552 -0
- package/dist/durable-workflow.js.map +1 -0
- package/dist/graph/topological-sort.d.ts.map +1 -1
- package/dist/graph/topological-sort.js +5 -5
- package/dist/graph/topological-sort.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +101 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +115 -0
- package/dist/logger.js.map +1 -0
- package/dist/on.d.ts.map +1 -1
- package/dist/on.js +3 -3
- package/dist/on.js.map +1 -1
- package/dist/runtime.d.ts +169 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +275 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts.map +1 -1
- package/dist/send.js +4 -3
- package/dist/send.js.map +1 -1
- package/dist/telemetry.d.ts +150 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +388 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/timer-registry.d.ts +25 -0
- package/dist/timer-registry.d.ts.map +1 -1
- package/dist/timer-registry.js +42 -8
- package/dist/timer-registry.js.map +1 -1
- package/dist/types.d.ts +17 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/dist/worker/durable-step.d.ts +481 -0
- package/dist/worker/durable-step.d.ts.map +1 -0
- package/dist/worker/durable-step.js +606 -0
- package/dist/worker/durable-step.js.map +1 -0
- package/dist/worker/index.d.ts +106 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +124 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/state-adapter.d.ts +230 -0
- package/dist/worker/state-adapter.d.ts.map +1 -0
- package/dist/worker/state-adapter.js +409 -0
- package/dist/worker/state-adapter.js.map +1 -0
- package/dist/worker/topological-executor.d.ts +282 -0
- package/dist/worker/topological-executor.d.ts.map +1 -0
- package/dist/worker/topological-executor.js +396 -0
- package/dist/worker/topological-executor.js.map +1 -0
- package/dist/worker/workflow-builder.d.ts +286 -0
- package/dist/worker/workflow-builder.d.ts.map +1 -0
- package/dist/worker/workflow-builder.js +565 -0
- package/dist/worker/workflow-builder.js.map +1 -0
- package/dist/worker.d.ts +800 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +2428 -0
- package/dist/worker.js.map +1 -0
- package/dist/workflow-builder.d.ts +287 -0
- package/dist/workflow-builder.d.ts.map +1 -0
- package/dist/workflow-builder.js +762 -0
- package/dist/workflow-builder.js.map +1 -0
- package/dist/workflow.d.ts +14 -30
- package/dist/workflow.d.ts.map +1 -1
- package/dist/workflow.js +132 -292
- package/dist/workflow.js.map +1 -1
- package/examples/01-ecommerce-order-pipeline.ts +358 -0
- package/examples/02-content-moderation-cascade.ts +454 -0
- package/examples/03-scheduled-reporting-dependencies.ts +479 -0
- package/examples/04-database-persistence.ts +518 -0
- package/examples/README.md +173 -0
- package/package.json +30 -13
- package/src/__tests__/digital-objects-adapter.test.ts +274 -0
- package/src/__tests__/durable-workflow.test.ts +297 -0
- package/src/barrier.ts +48 -7
- package/src/cascade-context.ts +36 -29
- package/src/cascade-executor.ts +3 -2
- package/src/context.ts +41 -12
- package/src/cron-parser.ts +347 -0
- package/src/cron-scheduler.ts +239 -0
- package/src/database-context.ts +658 -0
- package/src/digital-objects-adapter.ts +351 -0
- package/src/durable-execution-cloudflare.ts +855 -0
- package/src/durable-execution.ts +1042 -0
- package/src/durable-workflow.ts +717 -0
- package/src/graph/topological-sort.ts +6 -8
- package/src/index.ts +69 -0
- package/src/logger.ts +148 -0
- package/src/on.ts +8 -9
- package/src/runtime.ts +436 -0
- package/src/send.ts +4 -5
- package/src/telemetry.ts +577 -0
- package/src/timer-registry.ts +44 -10
- package/src/types.ts +32 -17
- package/src/worker/durable-step.ts +976 -0
- package/src/worker/index.ts +216 -0
- package/src/worker/state-adapter.ts +589 -0
- package/src/worker/topological-executor.ts +625 -0
- package/src/worker/workflow-builder.ts +871 -0
- package/src/worker.ts +2906 -0
- package/src/workflow-builder.ts +1068 -0
- package/src/workflow.ts +188 -351
- package/test/barrier-join.test.ts +32 -24
- package/test/cascade-executor.test.ts +9 -16
- package/test/cron-parser.test.ts +314 -0
- package/test/cron-scheduler.test.ts +291 -0
- package/test/database-context.test.ts +770 -0
- package/test/db-provider-adapter.test.ts +862 -0
- package/test/durable-execution-cloudflare.test.ts +606 -0
- package/test/durable-execution-in-process.test.ts +286 -0
- package/test/durable-execution.test.ts +247 -0
- package/test/e2e/workflow-scenarios.e2e.test.ts +1039 -0
- package/test/integration.test.ts +442 -0
- package/test/rpc-surface.test.ts +946 -0
- package/test/runtime.test.ts +262 -0
- package/test/schedule-timer-cleanup.test.ts +30 -21
- package/test/send-race-conditions.test.ts +30 -40
- package/test/worker/durable-cascade.test.ts +1117 -0
- package/test/worker/durable-step.test.ts +723 -0
- package/test/worker/topological-executor.test.ts +1240 -0
- package/test/worker/workflow-builder.test.ts +1067 -0
- package/test/worker.test.ts +608 -0
- package/test/workflow-builder.test.ts +1670 -0
- package/test/workflow-cron.test.ts +256 -0
- package/test/workflow-state-adapter.test.ts +923 -0
- package/test/workflow.test.ts +25 -22
- package/tsconfig.json +3 -1
- package/vitest.config.ts +38 -1
- package/vitest.workers.config.ts +44 -0
- package/wrangler.jsonc +22 -0
- package/.turbo/turbo-test.log +0 -169
- package/LICENSE +0 -21
- package/src/context.js +0 -83
- package/src/every.js +0 -267
- package/src/index.js +0 -71
- package/src/on.js +0 -79
- package/src/send.js +0 -111
- package/src/types.js +0 -4
- package/src/workflow.js +0 -455
- package/test/context.test.js +0 -116
- package/test/every.test.js +0 -282
- package/test/on.test.js +0 -80
- package/test/send.test.js +0 -89
- package/test/workflow.test.js +0 -224
- package/vitest.config.js +0 -7
package/src/telemetry.ts
ADDED
|
@@ -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'
|
package/src/timer-registry.ts
CHANGED
|
@@ -110,23 +110,57 @@ export const timerRegistry = {
|
|
|
110
110
|
getAll: () => Array.from(activeTimers.entries()),
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
//
|
|
114
|
-
// Register on both globalThis and global for maximum compatibility
|
|
113
|
+
// Global registration is opt-in to avoid namespace pollution
|
|
115
114
|
declare const global: typeof globalThis
|
|
116
115
|
|
|
116
|
+
let globalRegistrationEnabled = false
|
|
117
|
+
|
|
117
118
|
function registerGlobalFunctions(target: typeof globalThis) {
|
|
118
|
-
(target as unknown as Record<string, unknown>)
|
|
119
|
-
|
|
119
|
+
;(target as unknown as Record<string, unknown>)['getActiveWorkflowTimerCount'] =
|
|
120
|
+
getActiveTimerCount
|
|
121
|
+
;(target as unknown as Record<string, unknown>)['clearAllWorkflowTimers'] = clearAllTimers
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Enable global timer registry functions.
|
|
126
|
+
*
|
|
127
|
+
* This opt-in function registers `getActiveWorkflowTimerCount` and `clearAllWorkflowTimers`
|
|
128
|
+
* on the global scope for debugging and cleanup purposes.
|
|
129
|
+
*
|
|
130
|
+
* Call this function explicitly if you need global access to timer management:
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* import { enableGlobalTimerRegistry } from 'ai-workflows'
|
|
135
|
+
*
|
|
136
|
+
* // Enable global registration
|
|
137
|
+
* enableGlobalTimerRegistry()
|
|
138
|
+
*
|
|
139
|
+
* // Now these are available globally:
|
|
140
|
+
* // globalThis.getActiveWorkflowTimerCount()
|
|
141
|
+
* // globalThis.clearAllWorkflowTimers()
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export function enableGlobalTimerRegistry(): void {
|
|
145
|
+
if (globalRegistrationEnabled) return
|
|
146
|
+
globalRegistrationEnabled = true
|
|
147
|
+
|
|
148
|
+
// Register on globalThis (standard)
|
|
149
|
+
if (typeof globalThis !== 'undefined') {
|
|
150
|
+
registerGlobalFunctions(globalThis)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Also register on global (Node.js specific, used in some test environments)
|
|
154
|
+
if (typeof global !== 'undefined' && global !== globalThis) {
|
|
155
|
+
registerGlobalFunctions(global)
|
|
156
|
+
}
|
|
125
157
|
}
|
|
126
158
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Check if global timer registry is enabled
|
|
161
|
+
*/
|
|
162
|
+
export function isGlobalTimerRegistryEnabled(): boolean {
|
|
163
|
+
return globalRegistrationEnabled
|
|
130
164
|
}
|
|
131
165
|
|
|
132
166
|
// Register process exit handlers for cleanup
|