@ai-sdk/otel 1.0.0-beta.59 → 1.0.0-beta.60

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.
@@ -1,13 +1,11 @@
1
1
  import {
2
- Attributes,
3
- AttributeValue,
4
2
  context,
5
- Context as OpenTelemetryContext,
6
- Span,
7
3
  SpanKind,
8
- SpanStatusCode,
9
4
  trace,
10
- Tracer,
5
+ type Attributes,
6
+ type Context as OpenTelemetryContext,
7
+ type Span,
8
+ type Tracer,
11
9
  } from '@opentelemetry/api';
12
10
  import type {
13
11
  EmbeddingModelCallEndEvent,
@@ -45,74 +43,22 @@ import {
45
43
  mapOperationName,
46
44
  mapProviderName,
47
45
  } from './gen-ai-format-messages';
48
-
49
- function recordSpanError(span: Span, error: unknown): void {
50
- if (error instanceof Error) {
51
- span.recordException({
52
- name: error.name,
53
- message: error.message,
54
- stack: error.stack,
55
- });
56
- span.setStatus({
57
- code: SpanStatusCode.ERROR,
58
- message: error.message,
59
- });
60
- } else {
61
- span.setStatus({ code: SpanStatusCode.ERROR });
62
- }
63
- }
64
-
65
- function shouldRecord(
66
- telemetry: TelemetryOptions | undefined,
67
- ): telemetry is TelemetryOptions {
68
- return telemetry?.isEnabled !== false;
69
- }
70
-
71
- function selectAttributes(
72
- telemetry: TelemetryOptions | undefined,
73
- attributes: Record<
74
- string,
75
- | AttributeValue
76
- | { input: () => AttributeValue | undefined }
77
- | { output: () => AttributeValue | undefined }
78
- | undefined
79
- >,
80
- ): Attributes {
81
- if (!shouldRecord(telemetry)) {
82
- return {};
83
- }
84
-
85
- const result: Attributes = {};
86
-
87
- for (const [key, value] of Object.entries(attributes)) {
88
- if (value == null) continue;
89
-
90
- if (
91
- typeof value === 'object' &&
92
- 'input' in value &&
93
- typeof value.input === 'function'
94
- ) {
95
- if (telemetry?.recordInputs === false) continue;
96
- const resolved = value.input();
97
- if (resolved != null) result[key] = resolved;
98
- continue;
99
- }
100
-
101
- if (
102
- typeof value === 'object' &&
103
- 'output' in value &&
104
- typeof value.output === 'function'
105
- ) {
106
- if (telemetry?.recordOutputs === false) continue;
107
- const resolved = value.output();
108
- if (resolved != null) result[key] = resolved;
109
- continue;
110
- }
111
-
112
- result[key] = value as AttributeValue;
113
- }
114
-
115
- return result;
46
+ import { recordErrorOnSpan } from './record-span';
47
+ import { selectAttributes } from './select-attributes';
48
+ import {
49
+ getDetailedUsageAttributes,
50
+ getHeaderAttributes,
51
+ getRuntimeContextAttributes,
52
+ normalizeSupplementalAttributes,
53
+ selectSupplementalAttributes,
54
+ type OpenTelemetryOptions,
55
+ type SupplementalAttributeOptions,
56
+ } from './supplemental-attributes';
57
+
58
+ export type { OpenTelemetryOptions } from './supplemental-attributes';
59
+
60
+ interface OtelStepStartEvent extends GenerateTextStepStartEvent<ToolSet> {
61
+ readonly stepToolChoice?: unknown;
116
62
  }
117
63
 
118
64
  interface CallState {
@@ -130,19 +76,18 @@ interface CallState {
130
76
  settings: Record<string, unknown>;
131
77
  provider: string;
132
78
  modelId: string;
79
+ baseSupplementalAttributes: Attributes;
133
80
  }
134
81
 
135
82
  export class OpenTelemetry implements Telemetry {
136
83
  private readonly callStates = new Map<string, CallState>();
137
84
 
138
85
  private readonly tracer: Tracer;
86
+ private readonly supplementalAttributes: SupplementalAttributeOptions;
139
87
 
140
- constructor(
141
- options: {
142
- tracer?: Tracer;
143
- } = {},
144
- ) {
88
+ constructor(options: OpenTelemetryOptions = {}) {
145
89
  this.tracer = options.tracer ?? trace.getTracer('gen_ai');
90
+ this.supplementalAttributes = normalizeSupplementalAttributes(options);
146
91
  }
147
92
 
148
93
  private getCallState(callId: string): CallState | undefined {
@@ -229,6 +174,16 @@ export class OpenTelemetry implements Telemetry {
229
174
 
230
175
  const providerName = mapProviderName(event.provider);
231
176
  const operationName = mapOperationName(event.operationId);
177
+ const baseSupplementalAttributes = selectSupplementalAttributes(
178
+ telemetry,
179
+ this.supplementalAttributes,
180
+ {
181
+ runtimeContext: getRuntimeContextAttributes(
182
+ event.runtimeContext as Record<string, unknown> | undefined,
183
+ ),
184
+ headers: getHeaderAttributes(event.headers),
185
+ },
186
+ );
232
187
 
233
188
  const attributes = selectAttributes(telemetry, {
234
189
  'gen_ai.operation.name': operationName,
@@ -260,6 +215,7 @@ export class OpenTelemetry implements Telemetry {
260
215
  }),
261
216
  ),
262
217
  },
218
+ ...baseSupplementalAttributes,
263
219
  });
264
220
 
265
221
  const spanName = `${operationName} ${event.modelId}`;
@@ -284,6 +240,7 @@ export class OpenTelemetry implements Telemetry {
284
240
  settings,
285
241
  provider: event.provider,
286
242
  modelId: event.modelId,
243
+ baseSupplementalAttributes,
287
244
  });
288
245
  }
289
246
 
@@ -309,6 +266,13 @@ export class OpenTelemetry implements Telemetry {
309
266
 
310
267
  const providerName = mapProviderName(event.provider);
311
268
  const operationName = mapOperationName(event.operationId);
269
+ const baseSupplementalAttributes = selectSupplementalAttributes(
270
+ telemetry,
271
+ this.supplementalAttributes,
272
+ {
273
+ headers: getHeaderAttributes(event.headers),
274
+ },
275
+ );
312
276
 
313
277
  const attributes = selectAttributes(telemetry, {
314
278
  'gen_ai.operation.name': operationName,
@@ -340,6 +304,17 @@ export class OpenTelemetry implements Telemetry {
340
304
  }),
341
305
  ),
342
306
  },
307
+ ...baseSupplementalAttributes,
308
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
309
+ schema: {
310
+ 'ai.schema': event.schema
311
+ ? { input: () => JSON.stringify(event.schema) }
312
+ : undefined,
313
+ 'ai.schema.name': event.schemaName,
314
+ 'ai.schema.description': event.schemaDescription,
315
+ 'ai.settings.output': event.output,
316
+ },
317
+ }),
343
318
  });
344
319
 
345
320
  const spanName = `${operationName} ${event.modelId}`;
@@ -364,6 +339,7 @@ export class OpenTelemetry implements Telemetry {
364
339
  settings,
365
340
  provider: event.provider,
366
341
  modelId: event.modelId,
342
+ baseSupplementalAttributes,
367
343
  });
368
344
  }
369
345
 
@@ -400,6 +376,7 @@ export class OpenTelemetry implements Telemetry {
400
376
  ? JSON.stringify(formatInputMessages(event.promptMessages))
401
377
  : undefined,
402
378
  },
379
+ ...state.baseSupplementalAttributes,
403
380
  });
404
381
 
405
382
  const spanName = `chat ${event.modelId}`;
@@ -443,6 +420,18 @@ export class OpenTelemetry implements Telemetry {
443
420
  }
444
421
  },
445
422
  },
423
+ ...selectSupplementalAttributes(
424
+ telemetry,
425
+ this.supplementalAttributes,
426
+ {
427
+ providerMetadata: {
428
+ 'ai.response.providerMetadata': event.providerMetadata
429
+ ? JSON.stringify(event.providerMetadata)
430
+ : undefined,
431
+ },
432
+ usage: getDetailedUsageAttributes(event.usage),
433
+ },
434
+ ),
446
435
  }),
447
436
  );
448
437
 
@@ -460,16 +449,35 @@ export class OpenTelemetry implements Telemetry {
460
449
  functionId: event.functionId,
461
450
  };
462
451
 
463
- const settings: Record<string, unknown> = {
464
- maxRetries: event.maxRetries,
465
- };
466
-
467
452
  const providerName = mapProviderName(event.provider);
453
+ const baseSupplementalAttributes = selectSupplementalAttributes(
454
+ telemetry,
455
+ this.supplementalAttributes,
456
+ {
457
+ headers: getHeaderAttributes(event.headers),
458
+ },
459
+ );
460
+ const value = event.value;
461
+ const isMany = event.operationId === 'ai.embedMany';
468
462
 
469
463
  const attributes = selectAttributes(telemetry, {
470
464
  'gen_ai.operation.name': 'embeddings',
471
465
  'gen_ai.provider.name': providerName,
472
466
  'gen_ai.request.model': event.modelId,
467
+ ...baseSupplementalAttributes,
468
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
469
+ embedding: isMany
470
+ ? {
471
+ 'ai.values': {
472
+ input: () => (value as string[]).map(v => JSON.stringify(v)),
473
+ },
474
+ }
475
+ : {
476
+ 'ai.value': {
477
+ input: () => JSON.stringify(value),
478
+ },
479
+ },
480
+ }),
473
481
  });
474
482
 
475
483
  const spanName = `embeddings ${event.modelId}`;
@@ -491,19 +499,31 @@ export class OpenTelemetry implements Telemetry {
491
499
  embedSpans: new Map(),
492
500
  rerankSpan: undefined,
493
501
  toolSpans: new Map(),
494
- settings,
502
+ settings: { maxRetries: event.maxRetries },
495
503
  provider: event.provider,
496
504
  modelId: event.modelId,
505
+ baseSupplementalAttributes,
497
506
  });
498
507
  }
499
508
 
500
- onStepStart(event: GenerateTextStepStartEvent<ToolSet>): void {
509
+ onStepStart(event: OtelStepStartEvent): void {
501
510
  const state = this.getCallState(event.callId);
502
511
  if (!state?.rootSpan || !state.rootContext) return;
503
512
 
504
513
  const { telemetry } = state;
505
514
  const stepAttributes = selectAttributes(telemetry, {
506
515
  'gen_ai.operation.name': 'agent_step',
516
+ ...state.baseSupplementalAttributes,
517
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
518
+ toolChoice: {
519
+ 'ai.prompt.toolChoice': {
520
+ input: () =>
521
+ event.stepToolChoice != null
522
+ ? JSON.stringify(event.stepToolChoice)
523
+ : undefined,
524
+ },
525
+ },
526
+ }),
507
527
  });
508
528
 
509
529
  state.stepSpan = this.tracer.startSpan(
@@ -605,6 +625,13 @@ export class OpenTelemetry implements Telemetry {
605
625
  }),
606
626
  ),
607
627
  },
628
+ ...selectSupplementalAttributes(
629
+ telemetry,
630
+ this.supplementalAttributes,
631
+ {
632
+ usage: getDetailedUsageAttributes(event.usage),
633
+ },
634
+ ),
608
635
  }),
609
636
  );
610
637
 
@@ -668,7 +695,7 @@ export class OpenTelemetry implements Telemetry {
668
695
  // JSON.stringify might fail for non-serializable results
669
696
  }
670
697
  } else {
671
- recordSpanError(span, toolOutput.error);
698
+ recordErrorOnSpan(span, toolOutput.error);
672
699
  }
673
700
 
674
701
  span.end();
@@ -679,6 +706,19 @@ export class OpenTelemetry implements Telemetry {
679
706
  const state = this.getCallState(event.callId);
680
707
  if (!state?.stepSpan) return;
681
708
 
709
+ const { telemetry } = state;
710
+
711
+ state.stepSpan.setAttributes(
712
+ selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
713
+ providerMetadata: {
714
+ 'ai.response.providerMetadata': event.providerMetadata
715
+ ? JSON.stringify(event.providerMetadata)
716
+ : undefined,
717
+ },
718
+ usage: getDetailedUsageAttributes(event.usage),
719
+ }),
720
+ );
721
+
682
722
  state.stepSpan.end();
683
723
  state.stepSpan = undefined;
684
724
  state.stepContext = undefined;
@@ -746,6 +786,18 @@ export class OpenTelemetry implements Telemetry {
746
786
  }),
747
787
  ),
748
788
  },
789
+ ...selectSupplementalAttributes(
790
+ telemetry,
791
+ this.supplementalAttributes,
792
+ {
793
+ providerMetadata: {
794
+ 'ai.response.providerMetadata': event.providerMetadata
795
+ ? JSON.stringify(event.providerMetadata)
796
+ : undefined,
797
+ },
798
+ usage: getDetailedUsageAttributes(event.totalUsage),
799
+ },
800
+ ),
749
801
  }),
750
802
  );
751
803
 
@@ -778,6 +830,18 @@ export class OpenTelemetry implements Telemetry {
778
830
  )
779
831
  : undefined,
780
832
  },
833
+ ...selectSupplementalAttributes(
834
+ telemetry,
835
+ this.supplementalAttributes,
836
+ {
837
+ providerMetadata: {
838
+ 'ai.response.providerMetadata': event.providerMetadata
839
+ ? JSON.stringify(event.providerMetadata)
840
+ : undefined,
841
+ },
842
+ usage: getDetailedUsageAttributes(event.usage),
843
+ },
844
+ ),
781
845
  }),
782
846
  );
783
847
 
@@ -790,10 +854,31 @@ export class OpenTelemetry implements Telemetry {
790
854
  if (!state?.rootSpan) return;
791
855
 
792
856
  const { telemetry } = state;
857
+ const isMany = state.operationId === 'ai.embedMany';
793
858
 
794
859
  state.rootSpan.setAttributes(
795
860
  selectAttributes(telemetry, {
796
861
  'gen_ai.usage.input_tokens': event.usage.tokens,
862
+ ...selectSupplementalAttributes(
863
+ telemetry,
864
+ this.supplementalAttributes,
865
+ {
866
+ embedding: isMany
867
+ ? {
868
+ 'ai.embeddings': {
869
+ output: () =>
870
+ (event.embedding as number[][]).map(e =>
871
+ JSON.stringify(e),
872
+ ),
873
+ },
874
+ }
875
+ : {
876
+ 'ai.embedding': {
877
+ output: () => JSON.stringify(event.embedding),
878
+ },
879
+ },
880
+ },
881
+ ),
797
882
  }),
798
883
  );
799
884
 
@@ -812,6 +897,14 @@ export class OpenTelemetry implements Telemetry {
812
897
  'gen_ai.operation.name': 'embeddings',
813
898
  'gen_ai.provider.name': providerName,
814
899
  'gen_ai.request.model': state.modelId,
900
+ ...state.baseSupplementalAttributes,
901
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
902
+ embedding: {
903
+ 'ai.values': {
904
+ input: () => event.values.map(v => JSON.stringify(v)),
905
+ },
906
+ },
907
+ }),
815
908
  });
816
909
 
817
910
  const spanName = `embeddings ${state.modelId}`;
@@ -841,6 +934,18 @@ export class OpenTelemetry implements Telemetry {
841
934
  span.setAttributes(
842
935
  selectAttributes(telemetry, {
843
936
  'gen_ai.usage.input_tokens': event.usage.tokens,
937
+ ...selectSupplementalAttributes(
938
+ telemetry,
939
+ this.supplementalAttributes,
940
+ {
941
+ embedding: {
942
+ 'ai.embeddings': {
943
+ output: () =>
944
+ event.embeddings.map(embedding => JSON.stringify(embedding)),
945
+ },
946
+ },
947
+ },
948
+ ),
844
949
  }),
845
950
  );
846
951
 
@@ -857,16 +962,27 @@ export class OpenTelemetry implements Telemetry {
857
962
  functionId: event.functionId,
858
963
  };
859
964
 
860
- const settings: Record<string, unknown> = {
861
- maxRetries: event.maxRetries,
862
- };
863
-
864
965
  const providerName = mapProviderName(event.provider);
966
+ const baseSupplementalAttributes = selectSupplementalAttributes(
967
+ telemetry,
968
+ this.supplementalAttributes,
969
+ {
970
+ headers: getHeaderAttributes(event.headers),
971
+ },
972
+ );
865
973
 
866
974
  const attributes = selectAttributes(telemetry, {
867
975
  'gen_ai.operation.name': 'rerank',
868
976
  'gen_ai.provider.name': providerName,
869
977
  'gen_ai.request.model': event.modelId,
978
+ ...baseSupplementalAttributes,
979
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
980
+ reranking: {
981
+ 'ai.documents': {
982
+ input: () => event.documents.map(d => JSON.stringify(d)),
983
+ },
984
+ },
985
+ }),
870
986
  });
871
987
 
872
988
  const spanName = `rerank ${event.modelId}`;
@@ -888,9 +1004,10 @@ export class OpenTelemetry implements Telemetry {
888
1004
  embedSpans: new Map(),
889
1005
  rerankSpan: undefined,
890
1006
  toolSpans: new Map(),
891
- settings,
1007
+ settings: { maxRetries: event.maxRetries },
892
1008
  provider: event.provider,
893
1009
  modelId: event.modelId,
1010
+ baseSupplementalAttributes,
894
1011
  });
895
1012
  }
896
1013
 
@@ -913,6 +1030,14 @@ export class OpenTelemetry implements Telemetry {
913
1030
  'gen_ai.operation.name': 'rerank',
914
1031
  'gen_ai.provider.name': providerName,
915
1032
  'gen_ai.request.model': state.modelId,
1033
+ ...state.baseSupplementalAttributes,
1034
+ ...selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
1035
+ reranking: {
1036
+ 'ai.documents': {
1037
+ input: () => event.documents.map(d => JSON.stringify(d)),
1038
+ },
1039
+ },
1040
+ }),
916
1041
  });
917
1042
 
918
1043
  const spanName = `rerank ${state.modelId}`;
@@ -931,6 +1056,18 @@ export class OpenTelemetry implements Telemetry {
931
1056
  if (!state?.rerankSpan) return;
932
1057
 
933
1058
  const { span } = state.rerankSpan;
1059
+ const { telemetry } = state;
1060
+
1061
+ span.setAttributes(
1062
+ selectSupplementalAttributes(telemetry, this.supplementalAttributes, {
1063
+ reranking: {
1064
+ 'ai.ranking.type': event.documentsType,
1065
+ 'ai.ranking': {
1066
+ output: () => event.ranking.map(r => JSON.stringify(r)),
1067
+ },
1068
+ },
1069
+ }),
1070
+ );
934
1071
 
935
1072
  span.end();
936
1073
  state.rerankSpan = undefined;
@@ -950,38 +1087,38 @@ export class OpenTelemetry implements Telemetry {
950
1087
  const actualError = event.error ?? error;
951
1088
 
952
1089
  for (const { span: toolSpan } of state.toolSpans.values()) {
953
- recordSpanError(toolSpan, actualError);
1090
+ recordErrorOnSpan(toolSpan, actualError);
954
1091
  toolSpan.end();
955
1092
  }
956
1093
  state.toolSpans.clear();
957
1094
 
958
1095
  if (state.inferenceSpan) {
959
- recordSpanError(state.inferenceSpan, actualError);
1096
+ recordErrorOnSpan(state.inferenceSpan, actualError);
960
1097
  state.inferenceSpan.end();
961
1098
  state.inferenceSpan = undefined;
962
1099
  state.inferenceContext = undefined;
963
1100
  }
964
1101
 
965
1102
  if (state.stepSpan) {
966
- recordSpanError(state.stepSpan, actualError);
1103
+ recordErrorOnSpan(state.stepSpan, actualError);
967
1104
  state.stepSpan.end();
968
1105
  state.stepSpan = undefined;
969
1106
  state.stepContext = undefined;
970
1107
  }
971
1108
 
972
1109
  for (const { span: embedSpan } of state.embedSpans.values()) {
973
- recordSpanError(embedSpan, actualError);
1110
+ recordErrorOnSpan(embedSpan, actualError);
974
1111
  embedSpan.end();
975
1112
  }
976
1113
  state.embedSpans.clear();
977
1114
 
978
1115
  if (state.rerankSpan) {
979
- recordSpanError(state.rerankSpan.span, actualError);
1116
+ recordErrorOnSpan(state.rerankSpan.span, actualError);
980
1117
  state.rerankSpan.span.end();
981
1118
  state.rerankSpan = undefined;
982
1119
  }
983
1120
 
984
- recordSpanError(state.rootSpan, actualError);
1121
+ recordErrorOnSpan(state.rootSpan, actualError);
985
1122
 
986
1123
  state.rootSpan.end();
987
1124
  this.cleanupCallState(event.callId);
@@ -1,11 +1,10 @@
1
1
  import {
2
- Attributes,
3
- Span,
4
- Tracer,
5
2
  SpanStatusCode,
6
3
  context,
4
+ type Attributes,
5
+ type Span,
6
+ type Tracer,
7
7
  } from '@opentelemetry/api';
8
-
9
8
  export async function recordSpan<T>({
10
9
  name,
11
10
  tracer,
@@ -0,0 +1,57 @@
1
+ import type { Attributes, AttributeValue } from '@opentelemetry/api';
2
+ import type { TelemetryOptions } from 'ai';
3
+
4
+ export type AttributeSpec =
5
+ | AttributeValue
6
+ | { input: () => AttributeValue | undefined }
7
+ | { output: () => AttributeValue | undefined }
8
+ | undefined;
9
+
10
+ export type AttributeSpecMap = Record<string, AttributeSpec>;
11
+
12
+ function shouldRecord(
13
+ telemetry: TelemetryOptions | undefined,
14
+ ): telemetry is TelemetryOptions {
15
+ return telemetry?.isEnabled !== false;
16
+ }
17
+
18
+ export function selectAttributes(
19
+ telemetry: TelemetryOptions | undefined,
20
+ attributes: AttributeSpecMap,
21
+ ): Attributes {
22
+ if (!shouldRecord(telemetry)) {
23
+ return {};
24
+ }
25
+
26
+ const result: Attributes = {};
27
+
28
+ for (const [key, value] of Object.entries(attributes)) {
29
+ if (value == null) continue;
30
+
31
+ if (
32
+ typeof value === 'object' &&
33
+ 'input' in value &&
34
+ typeof value.input === 'function'
35
+ ) {
36
+ if (telemetry?.recordInputs === false) continue;
37
+ const resolved = value.input();
38
+ if (resolved != null) result[key] = resolved;
39
+ continue;
40
+ }
41
+
42
+ if (
43
+ typeof value === 'object' &&
44
+ 'output' in value &&
45
+ typeof value.output === 'function'
46
+ ) {
47
+ if (telemetry?.recordOutputs === false) continue;
48
+ const resolved = value.output();
49
+ if (resolved != null) result[key] = resolved;
50
+ continue;
51
+ }
52
+
53
+ result[key] = value as AttributeValue;
54
+ }
55
+
56
+ return result;
57
+ }
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  LanguageModelV4Message,
3
3
  LanguageModelV4Prompt,
4
4
  } from '@ai-sdk/provider';
@@ -21,13 +21,31 @@ export function stringifyForTelemetry(prompt: LanguageModelV4Prompt): string {
21
21
  part.type === 'file'
22
22
  ? {
23
23
  ...part,
24
- data:
25
- part.data instanceof Uint8Array
26
- ? convertDataContentToBase64String(part.data)
27
- : part.data,
24
+ data: serializeFileData(part.data),
28
25
  }
29
26
  : part,
30
27
  ),
31
28
  })),
32
29
  );
33
30
  }
31
+
32
+ function serializeFileData(
33
+ data:
34
+ | { type: 'data'; data: string | Uint8Array }
35
+ | { type: 'url'; url: URL }
36
+ | { type: 'reference'; reference: Record<string, string> }
37
+ | { type: 'text'; text: string },
38
+ ): unknown {
39
+ switch (data.type) {
40
+ case 'data':
41
+ return data.data instanceof Uint8Array
42
+ ? convertDataContentToBase64String(data.data)
43
+ : data.data;
44
+ case 'url':
45
+ return data.url.toString();
46
+ case 'reference':
47
+ return data.reference;
48
+ case 'text':
49
+ return data.text;
50
+ }
51
+ }