@oneuptime/common 11.2.3 → 11.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.
@@ -524,17 +524,108 @@ export default class Telemetry {
524
524
  }): void {
525
525
  const { span, exception } = data;
526
526
 
527
+ const exceptionAttributes: Attributes =
528
+ this.getExceptionAttributes(exception);
529
+
527
530
  // log the exception as well
528
531
  logger.error(exception);
529
532
 
533
+ /*
534
+ * Span *events* (from recordException) are not reliably surfaced when the
535
+ * span is read back, and setStatus on its own only records the error CODE,
536
+ * not the message. So we also attach the exception details as queryable
537
+ * span attributes — including DB driver fields like the failing constraint
538
+ * and table — so the actual cause is visible in the trace UI instead of an
539
+ * empty "Error" status.
540
+ */
541
+ span.setAttributes(exceptionAttributes);
530
542
  span.recordException(exception as SpanException);
531
543
  span.setStatus({
532
544
  code: SpanStatusCode.ERROR,
545
+ message:
546
+ (exceptionAttributes["exception.message"] as string | undefined) ||
547
+ "Error",
533
548
  });
534
549
 
535
550
  this.endSpan(span);
536
551
  }
537
552
 
553
+ /*
554
+ * Pulls every useful field off an unknown thrown value into OpenTelemetry
555
+ * span attributes. Error message/stack are non-enumerable, so they are read
556
+ * explicitly. For database failures (TypeORM QueryFailedError / pg errors),
557
+ * the Postgres fields (SQLSTATE code, detail, constraint, table, column) live
558
+ * either on the error itself or on `driverError`; these are what tell us which
559
+ * constraint failed during e.g. a cascade delete.
560
+ */
561
+ private static getExceptionAttributes(exception: unknown): Attributes {
562
+ const attributes: Attributes = {};
563
+
564
+ if (exception === null || exception === undefined) {
565
+ attributes["exception.message"] =
566
+ "Unknown error: null or undefined was thrown";
567
+ return attributes;
568
+ }
569
+
570
+ if (exception instanceof Error) {
571
+ attributes["exception.type"] =
572
+ exception.name || exception.constructor?.name || "Error";
573
+ attributes["exception.message"] = exception.message || "";
574
+ if (exception.stack) {
575
+ attributes["exception.stacktrace"] = exception.stack.substring(0, 8000);
576
+ }
577
+ } else if (typeof exception === "string") {
578
+ attributes["exception.message"] = exception;
579
+ } else {
580
+ attributes["exception.message"] = this.safeStringify(exception);
581
+ }
582
+
583
+ type PotentialDatabaseError = {
584
+ code?: unknown;
585
+ detail?: unknown;
586
+ constraint?: unknown;
587
+ table?: unknown;
588
+ column?: unknown;
589
+ schema?: unknown;
590
+ query?: unknown;
591
+ driverError?: PotentialDatabaseError;
592
+ };
593
+
594
+ const error: PotentialDatabaseError = exception as PotentialDatabaseError;
595
+ const databaseError: PotentialDatabaseError = error.driverError || error;
596
+
597
+ const setStringAttribute: (key: string, value: unknown) => void = (
598
+ key: string,
599
+ value: unknown,
600
+ ): void => {
601
+ if (value !== undefined && value !== null && value !== "") {
602
+ attributes[key] = String(value);
603
+ }
604
+ };
605
+
606
+ // SQLSTATE (e.g. "23503" = foreign key violation) or a Node error code.
607
+ setStringAttribute("exception.code", error.code ?? databaseError.code);
608
+ setStringAttribute("db.error.detail", databaseError.detail);
609
+ setStringAttribute("db.error.constraint", databaseError.constraint);
610
+ setStringAttribute("db.error.table", databaseError.table);
611
+ setStringAttribute("db.error.column", databaseError.column);
612
+ setStringAttribute("db.error.schema", databaseError.schema);
613
+
614
+ if (typeof error.query === "string" && error.query.length > 0) {
615
+ attributes["db.statement"] = error.query.substring(0, 2000);
616
+ }
617
+
618
+ return attributes;
619
+ }
620
+
621
+ private static safeStringify(value: unknown): string {
622
+ try {
623
+ return JSON.stringify(value) || String(value);
624
+ } catch {
625
+ return String(value);
626
+ }
627
+ }
628
+
538
629
  public static endSpan(span: Span): void {
539
630
  span.end();
540
631
  }
@@ -0,0 +1,164 @@
1
+ import LlmSpanUtil, {
2
+ LlmSpanFields,
3
+ } from "../../../../Server/Utils/Telemetry/LlmSpan";
4
+ import { AttributeType } from "../../../../Server/Utils/Telemetry/Telemetry";
5
+ import Dictionary from "../../../../Types/Dictionary";
6
+ import { describe, expect, test } from "@jest/globals";
7
+
8
+ type Attrs = Dictionary<AttributeType | Array<AttributeType>>;
9
+
10
+ describe("LlmSpanUtil.extract", () => {
11
+ test("non-LLM span returns empty/default fields", () => {
12
+ const attrs: Attrs = {
13
+ "http.method": "GET",
14
+ "http.route": "/api/users",
15
+ "db.system": "postgresql",
16
+ };
17
+
18
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
19
+
20
+ expect(fields.isLlmSpan).toBe(false);
21
+ expect(fields.llmSystem).toBe("");
22
+ expect(fields.llmTotalTokens).toBe(0);
23
+ expect(fields.llmCost).toBe(0);
24
+ });
25
+
26
+ test("empty attributes returns defaults", () => {
27
+ const fields: LlmSpanFields = LlmSpanUtil.extract({});
28
+ expect(fields).toEqual(LlmSpanUtil.empty());
29
+ });
30
+
31
+ test("OTel GenAI conventions (chat completion)", () => {
32
+ const attrs: Attrs = {
33
+ "gen_ai.system": "openai",
34
+ "gen_ai.operation.name": "chat",
35
+ "gen_ai.request.model": "gpt-4o",
36
+ "gen_ai.response.model": "gpt-4o-2024-08-06",
37
+ "gen_ai.usage.input_tokens": 1200,
38
+ "gen_ai.usage.output_tokens": 350,
39
+ "gen_ai.usage.cost": 0.0185,
40
+ };
41
+
42
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
43
+
44
+ expect(fields.isLlmSpan).toBe(true);
45
+ expect(fields.llmSystem).toBe("openai");
46
+ expect(fields.llmOperation).toBe("chat");
47
+ expect(fields.llmRequestModel).toBe("gpt-4o");
48
+ expect(fields.llmResponseModel).toBe("gpt-4o-2024-08-06");
49
+ expect(fields.llmInputTokens).toBe(1200);
50
+ expect(fields.llmOutputTokens).toBe(350);
51
+ // total derived from input + output when not reported.
52
+ expect(fields.llmTotalTokens).toBe(1550);
53
+ expect(fields.llmCost).toBe(0.0185);
54
+ });
55
+
56
+ test("explicit total_tokens is preferred over derived sum", () => {
57
+ const attrs: Attrs = {
58
+ "gen_ai.system": "anthropic",
59
+ "gen_ai.request.model": "claude-opus-4-8",
60
+ "gen_ai.usage.input_tokens": 100,
61
+ "gen_ai.usage.output_tokens": 50,
62
+ "gen_ai.usage.total_tokens": 999,
63
+ };
64
+
65
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
66
+ expect(fields.llmTotalTokens).toBe(999);
67
+ });
68
+
69
+ test("OpenLLMetry legacy prompt/completion token aliases", () => {
70
+ const attrs: Attrs = {
71
+ "gen_ai.system": "anthropic",
72
+ "gen_ai.request.model": "claude-sonnet-4-6",
73
+ "gen_ai.usage.prompt_tokens": 80,
74
+ "gen_ai.usage.completion_tokens": 20,
75
+ };
76
+
77
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
78
+ expect(fields.isLlmSpan).toBe(true);
79
+ expect(fields.llmInputTokens).toBe(80);
80
+ expect(fields.llmOutputTokens).toBe(20);
81
+ expect(fields.llmTotalTokens).toBe(100);
82
+ });
83
+
84
+ test("OpenInference (llm.* + llm.token_count.*) conventions", () => {
85
+ const attrs: Attrs = {
86
+ "openinference.span.kind": "LLM",
87
+ "llm.system": "openai",
88
+ "llm.model_name": "gpt-4o-mini",
89
+ "llm.token_count.prompt": 500,
90
+ "llm.token_count.completion": 120,
91
+ "llm.token_count.total": 620,
92
+ };
93
+
94
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
95
+ expect(fields.isLlmSpan).toBe(true);
96
+ expect(fields.llmSystem).toBe("openai");
97
+ expect(fields.llmOperation).toBe("LLM");
98
+ expect(fields.llmRequestModel).toBe("gpt-4o-mini");
99
+ expect(fields.llmInputTokens).toBe(500);
100
+ expect(fields.llmOutputTokens).toBe(120);
101
+ expect(fields.llmTotalTokens).toBe(620);
102
+ });
103
+
104
+ test("numeric token values reported as strings are coerced", () => {
105
+ const attrs: Attrs = {
106
+ "gen_ai.system": "openai",
107
+ "gen_ai.usage.input_tokens": "42",
108
+ "gen_ai.usage.output_tokens": "8",
109
+ };
110
+
111
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
112
+ expect(fields.llmInputTokens).toBe(42);
113
+ expect(fields.llmOutputTokens).toBe(8);
114
+ expect(fields.llmTotalTokens).toBe(50);
115
+ });
116
+
117
+ test("agent + tool spans are detected", () => {
118
+ const agentAttrs: Attrs = {
119
+ "gen_ai.operation.name": "invoke_agent",
120
+ "gen_ai.agent.name": "research-agent",
121
+ };
122
+ const agentFields: LlmSpanFields = LlmSpanUtil.extract(agentAttrs);
123
+ expect(agentFields.isLlmSpan).toBe(true);
124
+ expect(agentFields.llmAgentName).toBe("research-agent");
125
+ expect(agentFields.llmOperation).toBe("invoke_agent");
126
+
127
+ const toolAttrs: Attrs = {
128
+ "gen_ai.operation.name": "execute_tool",
129
+ "gen_ai.tool.name": "web_search",
130
+ };
131
+ const toolFields: LlmSpanFields = LlmSpanUtil.extract(toolAttrs);
132
+ expect(toolFields.isLlmSpan).toBe(true);
133
+ expect(toolFields.llmToolName).toBe("web_search");
134
+ });
135
+
136
+ test("response model is used as request model fallback", () => {
137
+ const attrs: Attrs = {
138
+ "gen_ai.system": "openai",
139
+ "gen_ai.response.model": "gpt-4o-2024-08-06",
140
+ };
141
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
142
+ expect(fields.llmRequestModel).toBe("gpt-4o-2024-08-06");
143
+ expect(fields.llmResponseModel).toBe("gpt-4o-2024-08-06");
144
+ });
145
+
146
+ test("bare gen_ai-namespaced attribute still flags as LLM span", () => {
147
+ const attrs: Attrs = {
148
+ "gen_ai.request.temperature": 0.7,
149
+ };
150
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
151
+ expect(fields.isLlmSpan).toBe(true);
152
+ });
153
+
154
+ test("array-valued attributes do not break string/number extraction", () => {
155
+ const attrs: Attrs = {
156
+ "gen_ai.system": "openai",
157
+ "gen_ai.response.finish_reasons": ["stop"],
158
+ "gen_ai.usage.input_tokens": 10,
159
+ };
160
+ const fields: LlmSpanFields = LlmSpanUtil.extract(attrs);
161
+ expect(fields.isLlmSpan).toBe(true);
162
+ expect(fields.llmInputTokens).toBe(10);
163
+ });
164
+ });
@@ -16,10 +16,15 @@ export default interface DashboardTraceChartComponent extends BaseComponent {
16
16
  // Substring filter on span name (e.g. "/Shipment/ShipShipment").
17
17
  spanNameContains?: string | undefined;
18
18
  /*
19
- * Attribute equality filters, ANDed "key=value; key2=value2"
20
- * (e.g. "url.host=torginol.starship.online").
19
+ * Attribute equality filters, ANDed. The structured editor stores a
20
+ * key/value record (e.g. { "url.host": "torginol.starship.online" }).
21
+ * A legacy "key=value; key2=value2" string is still read for widgets
22
+ * saved before the structured editor existed.
21
23
  */
22
- attributeFilters?: string | undefined;
24
+ attributeFilters?:
25
+ | string
26
+ | Record<string, string | number | boolean>
27
+ | undefined;
23
28
  /*
24
29
  * Optional split dimension: a span attribute key (e.g. url.host,
25
30
  * resource.service.instance.id) or a top-level column (name,
@@ -4,6 +4,7 @@ import React, {
4
4
  useState,
5
5
  useEffect,
6
6
  } from "react";
7
+ import { createPortal } from "react-dom";
7
8
  import Route from "../../../Types/API/Route";
8
9
  import URL from "../../../Types/API/URL";
9
10
  import IconProp from "../../../Types/Icon/IconProp";
@@ -368,9 +369,18 @@ const Navbar: FunctionComponent<ComponentProps> = (
368
369
  </div>
369
370
  )}
370
371
 
372
+ {/*
373
+ * Render the full-screen products modal in a portal on document.body so it
374
+ * is not a flex child of this `justify-between` nav. Otherwise it counts as
375
+ * a third flex item and shifts the right-side user/settings menu toward the
376
+ * middle whenever the modal opens. The modal is `fixed inset-0`, so its
377
+ * visual position is identical when portaled.
378
+ */}
371
379
  {isMoreMenuVisible &&
372
380
  props.moreMenuItems &&
373
- props.moreMenuItems.length > 0 && (
381
+ props.moreMenuItems.length > 0 &&
382
+ typeof document !== "undefined" &&
383
+ createPortal(
374
384
  <NavBarMenuModal
375
385
  items={props.moreMenuItems}
376
386
  footer={props.moreMenuFooter}
@@ -379,7 +389,8 @@ const Navbar: FunctionComponent<ComponentProps> = (
379
389
  keyboardHint={props.moreMenuKeyboardHint}
380
390
  recentLabel={props.moreMenuRecentLabel}
381
391
  onClose={closeMoreMenu}
382
- />
392
+ />,
393
+ document.body,
383
394
  )}
384
395
  </nav>
385
396
  );
@@ -15,11 +15,12 @@ const DisplaySection: ComponentArgumentSection = {
15
15
  order: 1,
16
16
  };
17
17
 
18
- const QuerySection: ComponentArgumentSection = {
19
- name: "Query",
20
- description: "Which spans to aggregate and how to split them",
21
- order: 2,
22
- };
18
+ /*
19
+ * The "Query" section (span-name filter, attribute filters, split-by, max
20
+ * series, include-child-spans) is rendered by a bespoke, structured editor —
21
+ * TraceChartQueryEditor — rather than these declarative free-text fields, so
22
+ * those arguments are deliberately not listed here. See ArgumentsForm.tsx.
23
+ */
23
24
 
24
25
  export default class DashboardTraceChartComponentUtil extends DashboardBaseComponentUtil {
25
26
  public static override getDefaultComponent(): DashboardTraceChartComponent {
@@ -76,59 +77,6 @@ export default class DashboardTraceChartComponentUtil extends DashboardBaseCompo
76
77
  ],
77
78
  });
78
79
 
79
- componentArguments.push({
80
- name: "Span Name Contains",
81
- description:
82
- "Only include spans whose name contains this text (e.g. /Shipment/ShipShipment)",
83
- required: false,
84
- type: ComponentInputType.Text,
85
- id: "spanNameContains",
86
- placeholder: "/Shipment/ShipShipment",
87
- section: QuerySection,
88
- });
89
-
90
- componentArguments.push({
91
- name: "Attribute Filters",
92
- description:
93
- "Attribute equality filters, ANDed — key=value pairs separated by semicolons (e.g. url.host=torginol.starship.online; http.method=POST)",
94
- required: false,
95
- type: ComponentInputType.Text,
96
- id: "attributeFilters",
97
- placeholder: "url.host=torginol.starship.online",
98
- section: QuerySection,
99
- });
100
-
101
- componentArguments.push({
102
- name: "Split By",
103
- description:
104
- "Optional dimension — a span attribute key (e.g. url.host, resource.service.instance.id) or one of: name, primaryEntityId, statusCode, kind. One series per value.",
105
- required: false,
106
- type: ComponentInputType.Text,
107
- id: "groupByAttribute",
108
- placeholder: "url.host",
109
- section: QuerySection,
110
- });
111
-
112
- componentArguments.push({
113
- name: "Top Series",
114
- description: "Cap on the number of series when split (default 10)",
115
- required: false,
116
- type: ComponentInputType.Number,
117
- id: "topLimit",
118
- placeholder: "10",
119
- section: QuerySection,
120
- });
121
-
122
- componentArguments.push({
123
- name: "Include Child Spans",
124
- description:
125
- "Count every span instead of root spans only (the traces explorer defaults to root spans)",
126
- required: false,
127
- type: ComponentInputType.Boolean,
128
- id: "includeChildSpans",
129
- section: QuerySection,
130
- });
131
-
132
80
  return componentArguments;
133
81
  }
134
82
  }
@@ -840,6 +840,160 @@ let Span = class Span extends AnalyticsBaseModel {
840
840
  update: [],
841
841
  },
842
842
  });
843
+ /*
844
+ * First-class LLM / GenAI / AI-agent columns. Denormalized at ingest from
845
+ * gen_ai.* (and OpenLLMetry/OpenInference) span attributes so LLM calls can
846
+ * be listed, filtered, and aggregated (tokens/cost/latency) cheaply without
847
+ * scanning the attributes map. See Common/Server/Utils/Telemetry/LlmSpan.ts.
848
+ */
849
+ const llmColumnAccessControl = {
850
+ read: [
851
+ Permission.ProjectOwner,
852
+ Permission.ProjectAdmin,
853
+ Permission.ProjectMember,
854
+ Permission.Viewer,
855
+ Permission.TelemetryAdmin,
856
+ Permission.TelemetryMember,
857
+ Permission.TelemetryViewer,
858
+ Permission.ReadTelemetryServiceTraces,
859
+ ],
860
+ create: [
861
+ Permission.ProjectOwner,
862
+ Permission.ProjectAdmin,
863
+ Permission.ProjectMember,
864
+ Permission.TelemetryAdmin,
865
+ Permission.TelemetryMember,
866
+ Permission.CreateTelemetryServiceTraces,
867
+ ],
868
+ update: [],
869
+ };
870
+ const isLlmSpanColumn = new AnalyticsTableColumn({
871
+ key: "isLlmSpan",
872
+ title: "Is LLM Span",
873
+ description: "Whether this span is an LLM / GenAI / AI-agent operation, populated at ingest time for fast filtering of AI calls",
874
+ required: true,
875
+ defaultValue: false,
876
+ type: TableColumnType.Boolean,
877
+ skipIndex: {
878
+ name: "idx_is_llm_span",
879
+ type: SkipIndexType.Set,
880
+ params: [2],
881
+ granularity: 4,
882
+ },
883
+ accessControl: llmColumnAccessControl,
884
+ });
885
+ const llmSystemColumn = new AnalyticsTableColumn({
886
+ key: "llmSystem",
887
+ isLowCardinality: true,
888
+ title: "LLM System",
889
+ description: "GenAI provider / system, e.g. openai, anthropic, aws.bedrock (gen_ai.system)",
890
+ required: false,
891
+ type: TableColumnType.Text,
892
+ skipIndex: {
893
+ name: "idx_llm_system",
894
+ type: SkipIndexType.Set,
895
+ params: [100],
896
+ granularity: 4,
897
+ },
898
+ accessControl: llmColumnAccessControl,
899
+ });
900
+ const llmOperationColumn = new AnalyticsTableColumn({
901
+ key: "llmOperation",
902
+ isLowCardinality: true,
903
+ title: "LLM Operation",
904
+ description: "GenAI operation, e.g. chat, embeddings, execute_tool, invoke_agent (gen_ai.operation.name)",
905
+ required: false,
906
+ type: TableColumnType.Text,
907
+ skipIndex: {
908
+ name: "idx_llm_operation",
909
+ type: SkipIndexType.Set,
910
+ params: [100],
911
+ granularity: 4,
912
+ },
913
+ accessControl: llmColumnAccessControl,
914
+ });
915
+ const llmRequestModelColumn = new AnalyticsTableColumn({
916
+ key: "llmRequestModel",
917
+ isLowCardinality: true,
918
+ title: "LLM Request Model",
919
+ description: "Model requested by the caller (gen_ai.request.model)",
920
+ required: false,
921
+ type: TableColumnType.Text,
922
+ skipIndex: {
923
+ name: "idx_llm_request_model",
924
+ type: SkipIndexType.Set,
925
+ params: [1000],
926
+ granularity: 4,
927
+ },
928
+ accessControl: llmColumnAccessControl,
929
+ });
930
+ const llmResponseModelColumn = new AnalyticsTableColumn({
931
+ key: "llmResponseModel",
932
+ isLowCardinality: true,
933
+ title: "LLM Response Model",
934
+ description: "Model the provider actually served (gen_ai.response.model)",
935
+ required: false,
936
+ type: TableColumnType.Text,
937
+ accessControl: llmColumnAccessControl,
938
+ });
939
+ const llmAgentNameColumn = new AnalyticsTableColumn({
940
+ key: "llmAgentName",
941
+ codec: { codec: "ZSTD", level: 1 },
942
+ title: "LLM Agent Name",
943
+ description: "Name of the agent for agent-framework spans (gen_ai.agent.name)",
944
+ required: false,
945
+ type: TableColumnType.Text,
946
+ accessControl: llmColumnAccessControl,
947
+ });
948
+ const llmToolNameColumn = new AnalyticsTableColumn({
949
+ key: "llmToolName",
950
+ codec: { codec: "ZSTD", level: 1 },
951
+ title: "LLM Tool Name",
952
+ description: "Name of the tool invoked for tool-call spans (gen_ai.tool.name)",
953
+ required: false,
954
+ type: TableColumnType.Text,
955
+ accessControl: llmColumnAccessControl,
956
+ });
957
+ const llmInputTokensColumn = new AnalyticsTableColumn({
958
+ key: "llmInputTokens",
959
+ title: "LLM Input Tokens",
960
+ description: "Input/prompt token count (gen_ai.usage.input_tokens)",
961
+ required: true,
962
+ defaultValue: 0,
963
+ type: TableColumnType.Number,
964
+ accessControl: llmColumnAccessControl,
965
+ });
966
+ const llmOutputTokensColumn = new AnalyticsTableColumn({
967
+ key: "llmOutputTokens",
968
+ title: "LLM Output Tokens",
969
+ description: "Output/completion token count (gen_ai.usage.output_tokens)",
970
+ required: true,
971
+ defaultValue: 0,
972
+ type: TableColumnType.Number,
973
+ accessControl: llmColumnAccessControl,
974
+ });
975
+ const llmTotalTokensColumn = new AnalyticsTableColumn({
976
+ key: "llmTotalTokens",
977
+ title: "LLM Total Tokens",
978
+ description: "Total token count (gen_ai.usage.total_tokens, or input + output)",
979
+ required: true,
980
+ defaultValue: 0,
981
+ type: TableColumnType.Number,
982
+ accessControl: llmColumnAccessControl,
983
+ });
984
+ const llmCostColumn = new AnalyticsTableColumn({
985
+ key: "llmCost",
986
+ title: "LLM Cost (USD)",
987
+ description: "Cost of this LLM call in USD. Only populated when the instrumentation reports it (gen_ai.usage.cost); OneUptime does not compute pricing.",
988
+ required: true,
989
+ defaultValue: 0,
990
+ /*
991
+ * Decimal (ClickHouse Double) — cost is fractional; an Int column would
992
+ * reject the fractional JSONEachRow value and poison the whole batch.
993
+ */
994
+ type: TableColumnType.Decimal,
995
+ accessControl: llmColumnAccessControl,
996
+ });
843
997
  const retentionDateColumn = new AnalyticsTableColumn({
844
998
  key: "retentionDate",
845
999
  codec: [{ codec: "DoubleDelta" }, { codec: "ZSTD", level: 1 }],
@@ -918,6 +1072,17 @@ let Span = class Span extends AnalyticsBaseModel {
918
1072
  kindColumn,
919
1073
  hasExceptionColumn,
920
1074
  isRootSpanColumn,
1075
+ isLlmSpanColumn,
1076
+ llmSystemColumn,
1077
+ llmOperationColumn,
1078
+ llmRequestModelColumn,
1079
+ llmResponseModelColumn,
1080
+ llmAgentNameColumn,
1081
+ llmToolNameColumn,
1082
+ llmInputTokensColumn,
1083
+ llmOutputTokensColumn,
1084
+ llmTotalTokensColumn,
1085
+ llmCostColumn,
921
1086
  retentionDateColumn,
922
1087
  ],
923
1088
  projections: [
@@ -1091,6 +1256,72 @@ let Span = class Span extends AnalyticsBaseModel {
1091
1256
  set retentionDate(v) {
1092
1257
  this.setColumnValue("retentionDate", v);
1093
1258
  }
1259
+ get isLlmSpan() {
1260
+ return this.getColumnValue("isLlmSpan");
1261
+ }
1262
+ set isLlmSpan(v) {
1263
+ this.setColumnValue("isLlmSpan", v);
1264
+ }
1265
+ get llmSystem() {
1266
+ return this.getColumnValue("llmSystem");
1267
+ }
1268
+ set llmSystem(v) {
1269
+ this.setColumnValue("llmSystem", v);
1270
+ }
1271
+ get llmOperation() {
1272
+ return this.getColumnValue("llmOperation");
1273
+ }
1274
+ set llmOperation(v) {
1275
+ this.setColumnValue("llmOperation", v);
1276
+ }
1277
+ get llmRequestModel() {
1278
+ return this.getColumnValue("llmRequestModel");
1279
+ }
1280
+ set llmRequestModel(v) {
1281
+ this.setColumnValue("llmRequestModel", v);
1282
+ }
1283
+ get llmResponseModel() {
1284
+ return this.getColumnValue("llmResponseModel");
1285
+ }
1286
+ set llmResponseModel(v) {
1287
+ this.setColumnValue("llmResponseModel", v);
1288
+ }
1289
+ get llmAgentName() {
1290
+ return this.getColumnValue("llmAgentName");
1291
+ }
1292
+ set llmAgentName(v) {
1293
+ this.setColumnValue("llmAgentName", v);
1294
+ }
1295
+ get llmToolName() {
1296
+ return this.getColumnValue("llmToolName");
1297
+ }
1298
+ set llmToolName(v) {
1299
+ this.setColumnValue("llmToolName", v);
1300
+ }
1301
+ get llmInputTokens() {
1302
+ return this.getColumnValue("llmInputTokens");
1303
+ }
1304
+ set llmInputTokens(v) {
1305
+ this.setColumnValue("llmInputTokens", v);
1306
+ }
1307
+ get llmOutputTokens() {
1308
+ return this.getColumnValue("llmOutputTokens");
1309
+ }
1310
+ set llmOutputTokens(v) {
1311
+ this.setColumnValue("llmOutputTokens", v);
1312
+ }
1313
+ get llmTotalTokens() {
1314
+ return this.getColumnValue("llmTotalTokens");
1315
+ }
1316
+ set llmTotalTokens(v) {
1317
+ this.setColumnValue("llmTotalTokens", v);
1318
+ }
1319
+ get llmCost() {
1320
+ return this.getColumnValue("llmCost");
1321
+ }
1322
+ set llmCost(v) {
1323
+ this.setColumnValue("llmCost", v);
1324
+ }
1094
1325
  };
1095
1326
  Span = __decorate([
1096
1327
  OperationalResource(),