promptlayer 1.0.60 → 1.1.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 (37) hide show
  1. package/README.md +9 -0
  2. package/dist/esm/chunk-SWBNW72U.js +2 -0
  3. package/dist/esm/chunk-SWBNW72U.js.map +1 -0
  4. package/dist/esm/index.js +2 -2
  5. package/dist/esm/index.js.map +1 -1
  6. package/dist/esm/openai-agents.js +3 -0
  7. package/dist/esm/openai-agents.js.map +1 -0
  8. package/dist/index.d.mts +229 -9
  9. package/dist/index.d.ts +229 -9
  10. package/dist/index.js +2 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/openai-agents.d.mts +42 -0
  13. package/dist/openai-agents.d.ts +42 -0
  14. package/dist/openai-agents.js +3 -0
  15. package/dist/openai-agents.js.map +1 -0
  16. package/package.json +24 -3
  17. package/src/integrations/openai-agents/helpers.test.ts +254 -0
  18. package/src/integrations/openai-agents/ids.ts +27 -0
  19. package/src/integrations/openai-agents/index.ts +8 -0
  20. package/src/integrations/openai-agents/instrumentation.test.ts +46 -0
  21. package/src/integrations/openai-agents/instrumentation.ts +47 -0
  22. package/src/integrations/openai-agents/mapping.ts +714 -0
  23. package/src/integrations/openai-agents/otlp-json.ts +120 -0
  24. package/src/integrations/openai-agents/processor.test.ts +509 -0
  25. package/src/integrations/openai-agents/processor.ts +388 -0
  26. package/src/integrations/openai-agents/time.ts +56 -0
  27. package/src/integrations/openai-agents/types.ts +49 -0
  28. package/src/integrations/openai-agents/url.ts +9 -0
  29. package/src/openai-agents.ts +1 -0
  30. package/src/types.ts +302 -9
  31. package/src/utils/blueprint-builder.test.ts +727 -0
  32. package/src/utils/blueprint-builder.ts +957 -126
  33. package/src/utils/streaming.test.ts +498 -0
  34. package/src/utils/streaming.ts +471 -43
  35. package/src/utils/utils.ts +4 -0
  36. package/tsup.config.ts +4 -1
  37. package/vitest.config.ts +3 -0
@@ -0,0 +1,388 @@
1
+ import {
2
+ OTLP_STATUS_CODE_ERROR,
3
+ OTLP_STATUS_CODE_OK,
4
+ OTLP_STATUS_CODE_UNSET,
5
+ baseSpanAttributes,
6
+ baseTraceAttributes,
7
+ spanDataAttributes,
8
+ spanKindFor,
9
+ spanNameFor,
10
+ } from "@/integrations/openai-agents/mapping";
11
+ import { buildOtlpJsonPayload } from "@/integrations/openai-agents/otlp-json";
12
+ import { mapSpanId, mapTraceId, syntheticRootSpanId } from "@/integrations/openai-agents/ids";
13
+ import {
14
+ isoToUnixNano,
15
+ maxUnixNano,
16
+ minUnixNano,
17
+ nowUnixNano,
18
+ } from "@/integrations/openai-agents/time";
19
+ import { trimTrailingSlashes } from "@/integrations/openai-agents/url";
20
+ import type {
21
+ OtlpSpanRecord,
22
+ OtlpStatusRecord,
23
+ } from "@/integrations/openai-agents/types";
24
+ import { fetchWithRetry, getCommonHeaders } from "@/utils/utils";
25
+ import type {
26
+ Span as AgentsSpan,
27
+ Trace as AgentsTrace,
28
+ TracingProcessor,
29
+ } from "@openai/agents";
30
+
31
+ const TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
32
+ const ZERO_TRACE_ID = "0".repeat(32);
33
+ const ZERO_SPAN_ID = "0".repeat(16);
34
+
35
+ interface UpstreamTraceContext {
36
+ traceId: string;
37
+ parentSpanId: string;
38
+ traceState?: string;
39
+ }
40
+
41
+ type TraceMetadataRecord = Record<string, unknown>;
42
+
43
+ interface TraceState {
44
+ rootSpan: OtlpSpanRecord;
45
+ spans: Map<string, OtlpSpanRecord>;
46
+ }
47
+
48
+ export interface PromptLayerOpenAIAgentsProcessorOptions {
49
+ apiKey: string;
50
+ baseURL: string;
51
+ includeRawPayloads?: boolean;
52
+ }
53
+
54
+ export class PromptLayerOpenAIAgentsProcessor implements TracingProcessor {
55
+ private readonly apiKey: string;
56
+ private readonly baseURL: string;
57
+ private readonly includeRawPayloads: boolean;
58
+ private readonly traceStates = new Map<string, TraceState>();
59
+ private readonly completedTraceQueue = new Map<
60
+ string,
61
+ ReturnType<typeof buildOtlpJsonPayload>
62
+ >();
63
+ private readonly pendingExports = new Map<string, Promise<void>>();
64
+
65
+ constructor({
66
+ apiKey,
67
+ baseURL,
68
+ includeRawPayloads = true,
69
+ }: PromptLayerOpenAIAgentsProcessorOptions) {
70
+ this.apiKey = apiKey;
71
+ this.baseURL = trimTrailingSlashes(baseURL);
72
+ this.includeRawPayloads = includeRawPayloads;
73
+ }
74
+
75
+ start(): void {}
76
+
77
+ async onTraceStart(trace: AgentsTrace): Promise<void> {
78
+ if (this.traceStates.has(trace.traceId)) {
79
+ return;
80
+ }
81
+
82
+ const upstreamContext = this.resolveUpstreamTraceContext(trace.metadata);
83
+ const traceId = upstreamContext?.traceId ?? mapTraceId(trace.traceId);
84
+ this.traceStates.set(trace.traceId, {
85
+ rootSpan: {
86
+ traceId,
87
+ spanId: syntheticRootSpanId(trace.traceId),
88
+ name: trace.name || "OpenAI Agents Trace",
89
+ kind: 1,
90
+ startTimeUnixNano: nowUnixNano(),
91
+ parentSpanId: upstreamContext?.parentSpanId,
92
+ traceState: upstreamContext?.traceState,
93
+ attributes: baseTraceAttributes(trace, this.includeRawPayloads),
94
+ status: { code: OTLP_STATUS_CODE_UNSET },
95
+ events: [],
96
+ },
97
+ spans: new Map(),
98
+ });
99
+ }
100
+
101
+ async onTraceEnd(trace: AgentsTrace): Promise<void> {
102
+ const state = this.traceStates.get(trace.traceId);
103
+ if (!state) {
104
+ return;
105
+ }
106
+
107
+ const childSpans = Array.from(state.spans.values()).sort((left, right) => {
108
+ return BigInt(left.startTimeUnixNano) < BigInt(right.startTimeUnixNano)
109
+ ? -1
110
+ : 1;
111
+ });
112
+ const childStarts = childSpans.map((span) => span.startTimeUnixNano);
113
+ const childEnds = childSpans.map(
114
+ (span) => span.endTimeUnixNano ?? span.startTimeUnixNano
115
+ );
116
+ const rootEnd = maxUnixNano(nowUnixNano(), ...childEnds);
117
+
118
+ state.rootSpan.startTimeUnixNano = minUnixNano(
119
+ state.rootSpan.startTimeUnixNano,
120
+ ...childStarts
121
+ );
122
+ state.rootSpan.endTimeUnixNano = rootEnd;
123
+
124
+ const payload = buildOtlpJsonPayload([state.rootSpan, ...childSpans]);
125
+ this.traceStates.delete(trace.traceId);
126
+ this.completedTraceQueue.set(trace.traceId, payload);
127
+ this.startExportForTrace(trace.traceId);
128
+ }
129
+
130
+ async onSpanStart(span: AgentsSpan<any>): Promise<void> {
131
+ const state = this.ensureTraceStateForSpan(span);
132
+ const existing = state.spans.get(span.spanId);
133
+ const record = existing ?? this.createSpanRecord(span, state.rootSpan.spanId);
134
+
135
+ record.traceId = state.rootSpan.traceId;
136
+ record.traceState = state.rootSpan.traceState;
137
+ record.name = spanNameFor(span);
138
+ record.kind = spanKindFor(span);
139
+ record.startTimeUnixNano = isoToUnixNano(span.startedAt) ?? record.startTimeUnixNano;
140
+ record.parentSpanId = span.parentId
141
+ ? mapSpanId(span.parentId)
142
+ : state.rootSpan.spanId;
143
+ record.attributes = {
144
+ ...record.attributes,
145
+ ...baseSpanAttributes(span),
146
+ };
147
+
148
+ state.spans.set(span.spanId, record);
149
+ }
150
+
151
+ async onSpanEnd(span: AgentsSpan<any>): Promise<void> {
152
+ const state = this.ensureTraceStateForSpan(span);
153
+ const record =
154
+ state.spans.get(span.spanId) ??
155
+ this.createSpanRecord(span, state.rootSpan.spanId);
156
+
157
+ record.attributes = {
158
+ ...record.attributes,
159
+ ...spanDataAttributes(span.spanData, this.includeRawPayloads),
160
+ };
161
+ record.endTimeUnixNano =
162
+ isoToUnixNano(span.endedAt) ?? record.endTimeUnixNano ?? nowUnixNano();
163
+ record.status = this.statusForSpan(span);
164
+
165
+ if (span.error) {
166
+ record.events = [
167
+ ...(record.events ?? []),
168
+ {
169
+ name: "exception",
170
+ timeUnixNano: record.endTimeUnixNano,
171
+ attributes: {
172
+ "exception.type": "OpenAIAgentsError",
173
+ "exception.message": span.error.message,
174
+ "openai_agents.error_json": JSON.stringify(span.error),
175
+ },
176
+ },
177
+ ];
178
+ }
179
+
180
+ state.spans.set(span.spanId, record);
181
+ }
182
+
183
+ async shutdown(): Promise<void> {
184
+ await this.forceFlush();
185
+ }
186
+
187
+ async forceFlush(): Promise<void> {
188
+ for (let pass = 0; pass < 2; pass += 1) {
189
+ for (const traceId of this.completedTraceQueue.keys()) {
190
+ this.startExportForTrace(traceId);
191
+ }
192
+
193
+ if (this.pendingExports.size === 0) {
194
+ return;
195
+ }
196
+
197
+ await Promise.all(Array.from(this.pendingExports.values()));
198
+
199
+ if (this.completedTraceQueue.size === 0) {
200
+ return;
201
+ }
202
+ }
203
+ }
204
+
205
+ private traceLikeFromSpan(
206
+ span: AgentsSpan<any>
207
+ ): Pick<AgentsTrace, "traceId" | "name" | "groupId" | "metadata"> {
208
+ const rawMetadata = this.asRecord(span.traceMetadata);
209
+ const nestedMetadata = this.asRecord(rawMetadata?.metadata);
210
+
211
+ return {
212
+ traceId: span.traceId,
213
+ name:
214
+ this.readString(rawMetadata?.workflow_name) ??
215
+ this.readString(rawMetadata?.workflowName) ??
216
+ "OpenAI Agents Trace",
217
+ groupId:
218
+ this.readString(rawMetadata?.group_id) ??
219
+ this.readString(rawMetadata?.groupId) ??
220
+ null,
221
+ metadata: nestedMetadata ?? rawMetadata ?? {},
222
+ };
223
+ }
224
+
225
+ private ensureTraceStateForSpan(span: AgentsSpan<any>): TraceState {
226
+ const existing = this.traceStates.get(span.traceId);
227
+ if (existing) {
228
+ return existing;
229
+ }
230
+
231
+ const traceLike = this.traceLikeFromSpan(span);
232
+
233
+ const upstreamContext = this.resolveUpstreamTraceContext(traceLike.metadata);
234
+ const traceId = upstreamContext?.traceId ?? mapTraceId(span.traceId);
235
+ const state: TraceState = {
236
+ rootSpan: {
237
+ traceId,
238
+ spanId: syntheticRootSpanId(span.traceId),
239
+ name: traceLike.name,
240
+ kind: 1,
241
+ startTimeUnixNano: isoToUnixNano(span.startedAt) ?? nowUnixNano(),
242
+ parentSpanId: upstreamContext?.parentSpanId,
243
+ traceState: upstreamContext?.traceState,
244
+ attributes: baseTraceAttributes(traceLike, this.includeRawPayloads),
245
+ status: { code: OTLP_STATUS_CODE_UNSET },
246
+ events: [],
247
+ },
248
+ spans: new Map(),
249
+ };
250
+
251
+ this.traceStates.set(span.traceId, state);
252
+ return state;
253
+ }
254
+
255
+ private createSpanRecord(
256
+ span: AgentsSpan<any>,
257
+ rootSpanId: string
258
+ ): OtlpSpanRecord {
259
+ return {
260
+ traceId:
261
+ this.traceStates.get(span.traceId)?.rootSpan.traceId ?? mapTraceId(span.traceId),
262
+ spanId: mapSpanId(span.spanId),
263
+ parentSpanId: span.parentId ? mapSpanId(span.parentId) : rootSpanId,
264
+ name: spanNameFor(span),
265
+ kind: spanKindFor(span),
266
+ startTimeUnixNano: isoToUnixNano(span.startedAt) ?? nowUnixNano(),
267
+ traceState: this.traceStates.get(span.traceId)?.rootSpan.traceState,
268
+ attributes: {
269
+ ...baseSpanAttributes(span),
270
+ },
271
+ status: { code: OTLP_STATUS_CODE_UNSET },
272
+ events: [],
273
+ };
274
+ }
275
+
276
+ private statusForSpan(span: AgentsSpan<any>): OtlpStatusRecord {
277
+ if (span.error) {
278
+ return {
279
+ code: OTLP_STATUS_CODE_ERROR,
280
+ message: span.error.message,
281
+ };
282
+ }
283
+
284
+ return {
285
+ code: OTLP_STATUS_CODE_OK,
286
+ };
287
+ }
288
+
289
+ private async exportPayload(payload: ReturnType<typeof buildOtlpJsonPayload>) {
290
+ const response = await fetchWithRetry(`${this.baseURL}/v1/traces`, {
291
+ method: "POST",
292
+ headers: {
293
+ "Content-Type": "application/json",
294
+ "X-API-KEY": this.apiKey,
295
+ "X-PromptLayer-Integration": "openai-agents-js",
296
+ ...getCommonHeaders(),
297
+ },
298
+ body: JSON.stringify(payload),
299
+ });
300
+
301
+ if (!response.ok) {
302
+ throw new Error(
303
+ `Failed to export OpenAI Agents traces: ${response.status} ${response.statusText}`
304
+ );
305
+ }
306
+ }
307
+
308
+ private startExportForTrace(traceId: string): void {
309
+ if (this.pendingExports.has(traceId)) {
310
+ return;
311
+ }
312
+
313
+ const payload = this.completedTraceQueue.get(traceId);
314
+ if (!payload) {
315
+ return;
316
+ }
317
+
318
+ const exportPromise = this.exportPayload(payload)
319
+ .then(() => {
320
+ this.completedTraceQueue.delete(traceId);
321
+ })
322
+ .catch((error) => {
323
+ console.error(
324
+ `Failed to export OpenAI Agents trace '${traceId}'.`,
325
+ error
326
+ );
327
+ })
328
+ .finally(() => {
329
+ this.pendingExports.delete(traceId);
330
+ });
331
+
332
+ this.pendingExports.set(traceId, exportPromise);
333
+ }
334
+
335
+ private resolveUpstreamTraceContext(
336
+ metadata: AgentsTrace["metadata"] | AgentsSpan<any>["traceMetadata"]
337
+ ): UpstreamTraceContext | null {
338
+ const metadataRecord = this.asRecord(metadata);
339
+ if (!metadataRecord) {
340
+ return null;
341
+ }
342
+
343
+ const traceparent = metadataRecord.traceparent;
344
+ if (typeof traceparent !== "string" || !traceparent.trim()) {
345
+ return null;
346
+ }
347
+
348
+ const match = traceparent.trim().match(TRACEPARENT_RE);
349
+ if (!match) {
350
+ return null;
351
+ }
352
+
353
+ const [, version, traceId, parentSpanId] = match;
354
+ const normalizedVersion = version.toLowerCase();
355
+ const normalizedTraceId = traceId.toLowerCase();
356
+ const normalizedParentSpanId = parentSpanId.toLowerCase();
357
+ if (
358
+ normalizedVersion === "ff" ||
359
+ normalizedTraceId === ZERO_TRACE_ID ||
360
+ normalizedParentSpanId === ZERO_SPAN_ID
361
+ ) {
362
+ return null;
363
+ }
364
+
365
+ const traceState =
366
+ typeof metadataRecord.tracestate === "string" && metadataRecord.tracestate.trim()
367
+ ? metadataRecord.tracestate.trim()
368
+ : undefined;
369
+
370
+ return {
371
+ traceId: normalizedTraceId,
372
+ parentSpanId: normalizedParentSpanId,
373
+ traceState,
374
+ };
375
+ }
376
+
377
+ private asRecord(value: unknown): TraceMetadataRecord | null {
378
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
379
+ return null;
380
+ }
381
+
382
+ return value as TraceMetadataRecord;
383
+ }
384
+
385
+ private readString(value: unknown): string | undefined {
386
+ return typeof value === "string" && value.trim() ? value : undefined;
387
+ }
388
+ }
@@ -0,0 +1,56 @@
1
+ const ISO_TIMESTAMP_RE =
2
+ /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})?$/;
3
+
4
+ export const nowUnixNano = (): string => {
5
+ return (BigInt(Date.now()) * BigInt(1_000_000)).toString();
6
+ };
7
+
8
+ export const isoToUnixNano = (
9
+ timestamp: string | null | undefined
10
+ ): string | undefined => {
11
+ if (!timestamp) {
12
+ return undefined;
13
+ }
14
+
15
+ const match = ISO_TIMESTAMP_RE.exec(timestamp);
16
+ if (!match) {
17
+ const millis = Date.parse(timestamp);
18
+ if (Number.isNaN(millis)) {
19
+ return undefined;
20
+ }
21
+ return (BigInt(millis) * BigInt(1_000_000)).toString();
22
+ }
23
+
24
+ const [, base, fraction = "", timezone = "Z"] = match;
25
+ const millis = Date.parse(`${base}${timezone}`);
26
+ if (Number.isNaN(millis)) {
27
+ return undefined;
28
+ }
29
+
30
+ const fractionalNanos = BigInt(
31
+ (fraction + "000000000").slice(0, 9)
32
+ );
33
+ return (BigInt(millis) * BigInt(1_000_000) + fractionalNanos).toString();
34
+ };
35
+
36
+ export const minUnixNano = (...values: Array<string | undefined>): string => {
37
+ const filtered = values.filter((value): value is string => value !== undefined);
38
+ if (filtered.length === 0) {
39
+ return nowUnixNano();
40
+ }
41
+
42
+ return filtered.reduce((min, current) =>
43
+ BigInt(current) < BigInt(min) ? current : min
44
+ );
45
+ };
46
+
47
+ export const maxUnixNano = (...values: Array<string | undefined>): string => {
48
+ const filtered = values.filter((value): value is string => value !== undefined);
49
+ if (filtered.length === 0) {
50
+ return nowUnixNano();
51
+ }
52
+
53
+ return filtered.reduce((max, current) =>
54
+ BigInt(current) > BigInt(max) ? current : max
55
+ );
56
+ };
@@ -0,0 +1,49 @@
1
+ export type AttributePrimitive = string | number | boolean;
2
+ export type AttributeValue =
3
+ | AttributePrimitive
4
+ | null
5
+ | AttributeValue[]
6
+ | { [key: string]: AttributeValue };
7
+
8
+ export interface OtlpEventRecord {
9
+ name: string;
10
+ timeUnixNano: string;
11
+ attributes?: Record<string, AttributeValue>;
12
+ }
13
+
14
+ export interface OtlpStatusRecord {
15
+ code: number;
16
+ message?: string;
17
+ }
18
+
19
+ export interface OtlpSpanRecord {
20
+ traceId: string;
21
+ spanId: string;
22
+ name: string;
23
+ kind: number;
24
+ startTimeUnixNano: string;
25
+ endTimeUnixNano?: string;
26
+ parentSpanId?: string;
27
+ traceState?: string;
28
+ attributes: Record<string, AttributeValue>;
29
+ events?: OtlpEventRecord[];
30
+ status?: OtlpStatusRecord;
31
+ }
32
+
33
+ export interface OtlpJsonPayload {
34
+ resourceSpans: Array<{
35
+ resource: {
36
+ attributes: Array<{
37
+ key: string;
38
+ value: Record<string, unknown>;
39
+ }>;
40
+ };
41
+ scopeSpans: Array<{
42
+ scope: {
43
+ name: string;
44
+ version?: string;
45
+ };
46
+ spans: Array<Record<string, unknown>>;
47
+ }>;
48
+ }>;
49
+ }
@@ -0,0 +1,9 @@
1
+ export const trimTrailingSlashes = (value: string): string => {
2
+ let end = value.length;
3
+
4
+ while (end > 0 && value.charCodeAt(end - 1) === 47) {
5
+ end -= 1;
6
+ }
7
+
8
+ return value.slice(0, end);
9
+ };
@@ -0,0 +1 @@
1
+ export * from "@/integrations/openai-agents";