@orka-js/devtools 1.1.0 → 1.2.1
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/dist/index.cjs +362 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -1
- package/dist/index.d.ts +129 -1
- package/dist/index.js +357 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -31,12 +31,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var src_exports = {};
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
DevToolsServer: () => DevToolsServer,
|
|
34
|
+
OpenTelemetryExporter: () => OpenTelemetryExporter,
|
|
35
|
+
ReplayDebugger: () => ReplayDebugger,
|
|
34
36
|
Trace: () => Trace,
|
|
35
37
|
TraceCollector: () => TraceCollector,
|
|
36
38
|
createDevToolsHook: () => createDevToolsHook,
|
|
39
|
+
createOTLPExporter: () => createOTLPExporter,
|
|
40
|
+
createReplayDebugger: () => createReplayDebugger,
|
|
37
41
|
createTracerWithDevTools: () => createTracerWithDevTools,
|
|
38
42
|
devtools: () => devtools,
|
|
39
43
|
getCollector: () => getCollector,
|
|
44
|
+
getReplayDebugger: () => getReplayDebugger,
|
|
40
45
|
resetCollector: () => resetCollector,
|
|
41
46
|
trace: () => trace,
|
|
42
47
|
withTrace: () => withTrace
|
|
@@ -754,6 +759,358 @@ function createTracerWithDevTools(options = {}) {
|
|
|
754
759
|
};
|
|
755
760
|
}
|
|
756
761
|
|
|
762
|
+
// src/opentelemetry.ts
|
|
763
|
+
var OpenTelemetryExporter = class {
|
|
764
|
+
config;
|
|
765
|
+
spanBuffer = [];
|
|
766
|
+
flushTimer;
|
|
767
|
+
unsubscribe;
|
|
768
|
+
constructor(config) {
|
|
769
|
+
this.config = {
|
|
770
|
+
endpoint: config.endpoint,
|
|
771
|
+
serviceName: config.serviceName ?? "orkajs-app",
|
|
772
|
+
serviceVersion: config.serviceVersion ?? "1.0.0",
|
|
773
|
+
headers: config.headers ?? {},
|
|
774
|
+
batchSize: config.batchSize ?? 100,
|
|
775
|
+
flushIntervalMs: config.flushIntervalMs ?? 5e3,
|
|
776
|
+
enabled: config.enabled ?? true
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Start the exporter - subscribes to trace events
|
|
781
|
+
*/
|
|
782
|
+
start() {
|
|
783
|
+
if (!this.config.enabled) return;
|
|
784
|
+
const collector = getCollector();
|
|
785
|
+
this.unsubscribe = collector.subscribe((event) => {
|
|
786
|
+
if (event.type === "run:end" && event.run) {
|
|
787
|
+
this.addSpan(event.run, event.sessionId);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
this.flushTimer = setInterval(() => {
|
|
791
|
+
this.flush();
|
|
792
|
+
}, this.config.flushIntervalMs);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Stop the exporter
|
|
796
|
+
*/
|
|
797
|
+
async stop() {
|
|
798
|
+
if (this.flushTimer) {
|
|
799
|
+
clearInterval(this.flushTimer);
|
|
800
|
+
this.flushTimer = void 0;
|
|
801
|
+
}
|
|
802
|
+
if (this.unsubscribe) {
|
|
803
|
+
this.unsubscribe();
|
|
804
|
+
this.unsubscribe = void 0;
|
|
805
|
+
}
|
|
806
|
+
await this.flush();
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Convert a TraceRun to OTLP span
|
|
810
|
+
*/
|
|
811
|
+
addSpan(run, traceId) {
|
|
812
|
+
const span = this.runToSpan(run, traceId);
|
|
813
|
+
this.spanBuffer.push(span);
|
|
814
|
+
for (const child of run.children) {
|
|
815
|
+
this.addSpan(child, traceId);
|
|
816
|
+
}
|
|
817
|
+
if (this.spanBuffer.length >= this.config.batchSize) {
|
|
818
|
+
this.flush();
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Convert TraceRun to OTLP Span format
|
|
823
|
+
*/
|
|
824
|
+
runToSpan(run, traceId) {
|
|
825
|
+
const attributes = [
|
|
826
|
+
{ key: "orka.run.type", value: { stringValue: run.type } },
|
|
827
|
+
{ key: "orka.run.name", value: { stringValue: run.name } },
|
|
828
|
+
{ key: "orka.run.status", value: { stringValue: run.status } }
|
|
829
|
+
];
|
|
830
|
+
if (run.metadata) {
|
|
831
|
+
if (run.metadata.model) {
|
|
832
|
+
attributes.push({ key: "llm.model", value: { stringValue: run.metadata.model } });
|
|
833
|
+
}
|
|
834
|
+
if (run.metadata.provider) {
|
|
835
|
+
attributes.push({ key: "llm.provider", value: { stringValue: run.metadata.provider } });
|
|
836
|
+
}
|
|
837
|
+
if (run.metadata.totalTokens !== void 0) {
|
|
838
|
+
attributes.push({ key: "llm.tokens.total", value: { intValue: String(run.metadata.totalTokens) } });
|
|
839
|
+
}
|
|
840
|
+
if (run.metadata.promptTokens !== void 0) {
|
|
841
|
+
attributes.push({ key: "llm.tokens.prompt", value: { intValue: String(run.metadata.promptTokens) } });
|
|
842
|
+
}
|
|
843
|
+
if (run.metadata.completionTokens !== void 0) {
|
|
844
|
+
attributes.push({ key: "llm.tokens.completion", value: { intValue: String(run.metadata.completionTokens) } });
|
|
845
|
+
}
|
|
846
|
+
if (run.metadata.cost !== void 0) {
|
|
847
|
+
attributes.push({ key: "llm.cost", value: { doubleValue: run.metadata.cost } });
|
|
848
|
+
}
|
|
849
|
+
if (run.metadata.toolName) {
|
|
850
|
+
attributes.push({ key: "tool.name", value: { stringValue: run.metadata.toolName } });
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (run.latencyMs !== void 0) {
|
|
854
|
+
attributes.push({ key: "orka.latency_ms", value: { intValue: String(run.latencyMs) } });
|
|
855
|
+
}
|
|
856
|
+
if (run.error) {
|
|
857
|
+
attributes.push({ key: "error.message", value: { stringValue: run.error } });
|
|
858
|
+
}
|
|
859
|
+
return {
|
|
860
|
+
traceId: this.toHex(traceId, 32),
|
|
861
|
+
spanId: this.toHex(run.id, 16),
|
|
862
|
+
parentSpanId: run.parentId ? this.toHex(run.parentId, 16) : void 0,
|
|
863
|
+
name: `${run.type}/${run.name}`,
|
|
864
|
+
kind: this.getSpanKind(run.type),
|
|
865
|
+
startTimeUnixNano: String(run.startTime * 1e6),
|
|
866
|
+
endTimeUnixNano: String((run.endTime ?? run.startTime) * 1e6),
|
|
867
|
+
attributes,
|
|
868
|
+
status: {
|
|
869
|
+
code: run.status === "error" ? 2 : 1,
|
|
870
|
+
message: run.error
|
|
871
|
+
}
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Get OTLP span kind based on run type
|
|
876
|
+
*/
|
|
877
|
+
getSpanKind(type) {
|
|
878
|
+
switch (type) {
|
|
879
|
+
case "llm":
|
|
880
|
+
case "embedding":
|
|
881
|
+
return 3;
|
|
882
|
+
// CLIENT
|
|
883
|
+
case "tool":
|
|
884
|
+
return 3;
|
|
885
|
+
// CLIENT
|
|
886
|
+
case "agent":
|
|
887
|
+
case "chain":
|
|
888
|
+
case "workflow":
|
|
889
|
+
case "graph":
|
|
890
|
+
return 0;
|
|
891
|
+
// INTERNAL
|
|
892
|
+
default:
|
|
893
|
+
return 0;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Convert string ID to hex format
|
|
898
|
+
*/
|
|
899
|
+
toHex(id, length) {
|
|
900
|
+
let hash = 0;
|
|
901
|
+
for (let i = 0; i < id.length; i++) {
|
|
902
|
+
const char = id.charCodeAt(i);
|
|
903
|
+
hash = (hash << 5) - hash + char;
|
|
904
|
+
hash = hash & hash;
|
|
905
|
+
}
|
|
906
|
+
const hex = Math.abs(hash).toString(16).padStart(length, "0");
|
|
907
|
+
return hex.slice(0, length);
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Flush spans to OTLP endpoint
|
|
911
|
+
*/
|
|
912
|
+
async flush() {
|
|
913
|
+
if (this.spanBuffer.length === 0) return;
|
|
914
|
+
const spans = [...this.spanBuffer];
|
|
915
|
+
this.spanBuffer = [];
|
|
916
|
+
const payload = {
|
|
917
|
+
resourceSpans: [{
|
|
918
|
+
resource: {
|
|
919
|
+
attributes: [
|
|
920
|
+
{ key: "service.name", value: { stringValue: this.config.serviceName } },
|
|
921
|
+
{ key: "service.version", value: { stringValue: this.config.serviceVersion } },
|
|
922
|
+
{ key: "telemetry.sdk.name", value: { stringValue: "@orka-js/devtools" } },
|
|
923
|
+
{ key: "telemetry.sdk.version", value: { stringValue: "1.1.0" } }
|
|
924
|
+
]
|
|
925
|
+
},
|
|
926
|
+
scopeSpans: [{
|
|
927
|
+
scope: {
|
|
928
|
+
name: "@orka-js/devtools",
|
|
929
|
+
version: "1.1.0"
|
|
930
|
+
},
|
|
931
|
+
spans
|
|
932
|
+
}]
|
|
933
|
+
}]
|
|
934
|
+
};
|
|
935
|
+
try {
|
|
936
|
+
const response = await fetch(`${this.config.endpoint}/v1/traces`, {
|
|
937
|
+
method: "POST",
|
|
938
|
+
headers: {
|
|
939
|
+
"Content-Type": "application/json",
|
|
940
|
+
...this.config.headers
|
|
941
|
+
},
|
|
942
|
+
body: JSON.stringify(payload)
|
|
943
|
+
});
|
|
944
|
+
if (!response.ok) {
|
|
945
|
+
console.error(`[DevTools] OTLP export failed: ${response.status} ${response.statusText}`);
|
|
946
|
+
this.spanBuffer.unshift(...spans);
|
|
947
|
+
}
|
|
948
|
+
} catch (error) {
|
|
949
|
+
console.error("[DevTools] OTLP export error:", error);
|
|
950
|
+
this.spanBuffer.unshift(...spans);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
function createOTLPExporter(config) {
|
|
955
|
+
const exporter = new OpenTelemetryExporter(config);
|
|
956
|
+
exporter.start();
|
|
957
|
+
return exporter;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/replay.ts
|
|
961
|
+
var ReplayDebugger = class {
|
|
962
|
+
/**
|
|
963
|
+
* Replay a trace run with optionally modified input
|
|
964
|
+
*/
|
|
965
|
+
async replay(options) {
|
|
966
|
+
const collector = getCollector();
|
|
967
|
+
const originalRun = collector.findRun(options.runId, options.sessionId);
|
|
968
|
+
if (!originalRun) {
|
|
969
|
+
throw new Error(`Run not found: ${options.runId}`);
|
|
970
|
+
}
|
|
971
|
+
const modifiedInput = options.modifyInput ? options.modifyInput(originalRun.input) : originalRun.input;
|
|
972
|
+
const replaySessionId = collector.startSession(`Replay: ${originalRun.name}`);
|
|
973
|
+
const replayRunId = collector.startRun(
|
|
974
|
+
originalRun.type,
|
|
975
|
+
`replay:${originalRun.name}`,
|
|
976
|
+
modifiedInput,
|
|
977
|
+
{
|
|
978
|
+
...originalRun.metadata,
|
|
979
|
+
replayOf: originalRun.id,
|
|
980
|
+
originalSessionId: options.sessionId
|
|
981
|
+
}
|
|
982
|
+
);
|
|
983
|
+
const replayedOutput = originalRun.output;
|
|
984
|
+
collector.endRun(replayRunId, replayedOutput, originalRun.metadata);
|
|
985
|
+
collector.endSession(replaySessionId);
|
|
986
|
+
const replayedRun = collector.findRun(replayRunId);
|
|
987
|
+
if (!replayedRun) {
|
|
988
|
+
throw new Error("Failed to create replayed run");
|
|
989
|
+
}
|
|
990
|
+
const inputChanged = JSON.stringify(originalRun.input) !== JSON.stringify(modifiedInput);
|
|
991
|
+
const outputChanged = JSON.stringify(originalRun.output) !== JSON.stringify(replayedRun.output);
|
|
992
|
+
const latencyDiff = (replayedRun.latencyMs ?? 0) - (originalRun.latencyMs ?? 0);
|
|
993
|
+
return {
|
|
994
|
+
originalRun,
|
|
995
|
+
replayedRun,
|
|
996
|
+
diff: {
|
|
997
|
+
inputChanged,
|
|
998
|
+
outputChanged,
|
|
999
|
+
latencyDiff
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Fork a trace to create a new branch for experimentation
|
|
1005
|
+
*/
|
|
1006
|
+
fork(runId, sessionId) {
|
|
1007
|
+
const collector = getCollector();
|
|
1008
|
+
const originalRun = collector.findRun(runId, sessionId);
|
|
1009
|
+
if (!originalRun) {
|
|
1010
|
+
throw new Error(`Run not found: ${runId}`);
|
|
1011
|
+
}
|
|
1012
|
+
const forkSessionId = collector.startSession(`Fork: ${originalRun.name}`);
|
|
1013
|
+
this.cloneRunTree(originalRun, forkSessionId);
|
|
1014
|
+
collector.endSession(forkSessionId);
|
|
1015
|
+
return forkSessionId;
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Clone a run and its children
|
|
1019
|
+
*/
|
|
1020
|
+
cloneRunTree(run, _sessionId, parentId) {
|
|
1021
|
+
const collector = getCollector();
|
|
1022
|
+
const newRunId = collector.startRun(
|
|
1023
|
+
run.type,
|
|
1024
|
+
`fork:${run.name}`,
|
|
1025
|
+
run.input,
|
|
1026
|
+
{
|
|
1027
|
+
...run.metadata,
|
|
1028
|
+
forkedFrom: run.id,
|
|
1029
|
+
originalParentId: parentId
|
|
1030
|
+
}
|
|
1031
|
+
);
|
|
1032
|
+
for (const child of run.children) {
|
|
1033
|
+
this.cloneRunTree(child, _sessionId, newRunId);
|
|
1034
|
+
}
|
|
1035
|
+
if (run.status === "error") {
|
|
1036
|
+
collector.errorRun(newRunId, run.error ?? "Unknown error");
|
|
1037
|
+
} else {
|
|
1038
|
+
collector.endRun(newRunId, run.output, run.metadata);
|
|
1039
|
+
}
|
|
1040
|
+
return newRunId;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Compare two runs and return detailed diff
|
|
1044
|
+
*/
|
|
1045
|
+
compare(runId1, runId2, sessionId) {
|
|
1046
|
+
const collector = getCollector();
|
|
1047
|
+
const run1 = collector.findRun(runId1, sessionId);
|
|
1048
|
+
const run2 = collector.findRun(runId2, sessionId);
|
|
1049
|
+
if (!run1 || !run2) {
|
|
1050
|
+
throw new Error("One or both runs not found");
|
|
1051
|
+
}
|
|
1052
|
+
return {
|
|
1053
|
+
run1: {
|
|
1054
|
+
id: run1.id,
|
|
1055
|
+
name: run1.name,
|
|
1056
|
+
type: run1.type,
|
|
1057
|
+
latencyMs: run1.latencyMs,
|
|
1058
|
+
status: run1.status,
|
|
1059
|
+
tokenCount: run1.metadata?.totalTokens
|
|
1060
|
+
},
|
|
1061
|
+
run2: {
|
|
1062
|
+
id: run2.id,
|
|
1063
|
+
name: run2.name,
|
|
1064
|
+
type: run2.type,
|
|
1065
|
+
latencyMs: run2.latencyMs,
|
|
1066
|
+
status: run2.status,
|
|
1067
|
+
tokenCount: run2.metadata?.totalTokens
|
|
1068
|
+
},
|
|
1069
|
+
diff: {
|
|
1070
|
+
latencyDiff: (run2.latencyMs ?? 0) - (run1.latencyMs ?? 0),
|
|
1071
|
+
latencyDiffPercent: run1.latencyMs ? ((run2.latencyMs ?? 0) - run1.latencyMs) / run1.latencyMs * 100 : 0,
|
|
1072
|
+
tokenDiff: (run2.metadata?.totalTokens ?? 0) - (run1.metadata?.totalTokens ?? 0),
|
|
1073
|
+
statusChanged: run1.status !== run2.status,
|
|
1074
|
+
inputChanged: JSON.stringify(run1.input) !== JSON.stringify(run2.input),
|
|
1075
|
+
outputChanged: JSON.stringify(run1.output) !== JSON.stringify(run2.output)
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Export a run as a reproducible test case
|
|
1081
|
+
*/
|
|
1082
|
+
exportTestCase(runId, sessionId) {
|
|
1083
|
+
const collector = getCollector();
|
|
1084
|
+
const run = collector.findRun(runId, sessionId);
|
|
1085
|
+
if (!run) {
|
|
1086
|
+
throw new Error(`Run not found: ${runId}`);
|
|
1087
|
+
}
|
|
1088
|
+
return {
|
|
1089
|
+
name: `test_${run.name}_${run.id.slice(0, 8)}`,
|
|
1090
|
+
description: `Exported from DevTools trace ${run.id}`,
|
|
1091
|
+
type: run.type,
|
|
1092
|
+
input: run.input,
|
|
1093
|
+
expectedOutput: run.output,
|
|
1094
|
+
metadata: run.metadata,
|
|
1095
|
+
assertions: [
|
|
1096
|
+
{ type: "status", expected: run.status },
|
|
1097
|
+
{ type: "latency_max", expected: (run.latencyMs ?? 0) * 1.5 }
|
|
1098
|
+
],
|
|
1099
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
function createReplayDebugger() {
|
|
1104
|
+
return new ReplayDebugger();
|
|
1105
|
+
}
|
|
1106
|
+
var replayDebugger;
|
|
1107
|
+
function getReplayDebugger() {
|
|
1108
|
+
if (!replayDebugger) {
|
|
1109
|
+
replayDebugger = new ReplayDebugger();
|
|
1110
|
+
}
|
|
1111
|
+
return replayDebugger;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
757
1114
|
// src/index.ts
|
|
758
1115
|
async function devtools(config = {}) {
|
|
759
1116
|
const collector = getCollector(config);
|
|
@@ -844,12 +1201,17 @@ var trace = {
|
|
|
844
1201
|
// Annotate the CommonJS export names for ESM import in node:
|
|
845
1202
|
0 && (module.exports = {
|
|
846
1203
|
DevToolsServer,
|
|
1204
|
+
OpenTelemetryExporter,
|
|
1205
|
+
ReplayDebugger,
|
|
847
1206
|
Trace,
|
|
848
1207
|
TraceCollector,
|
|
849
1208
|
createDevToolsHook,
|
|
1209
|
+
createOTLPExporter,
|
|
1210
|
+
createReplayDebugger,
|
|
850
1211
|
createTracerWithDevTools,
|
|
851
1212
|
devtools,
|
|
852
1213
|
getCollector,
|
|
1214
|
+
getReplayDebugger,
|
|
853
1215
|
resetCollector,
|
|
854
1216
|
trace,
|
|
855
1217
|
withTrace
|