@kortyx/hooks 0.30.1 → 0.31.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/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.31.0](https://github.com/kortyx-io/kortyx/compare/hooks-v0.30.2...hooks-v0.31.0) (2026-10-08)
4
+
5
+
6
+ ### Features
7
+
8
+ * **telemetry:** preserve complete private error diagnostics ([#290](https://github.com/kortyx-io/kortyx/issues/290)) ([e1c5a88](https://github.com/kortyx-io/kortyx/commit/e1c5a88c494016fd84a1af40579412fcfde31271))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @kortyx/core bumped to 0.12.0
16
+ * @kortyx/providers bumped to 0.9.0
17
+ * @kortyx/telemetry-contracts bumped to 0.15.0
18
+
19
+ ## [0.30.2](https://github.com/kortyx-io/kortyx/compare/hooks-v0.30.1...hooks-v0.30.2) (2026-10-07)
20
+
21
+
22
+ ### Dependencies
23
+
24
+ * The following workspace dependencies were updated
25
+ * dependencies
26
+ * @kortyx/providers bumped to 0.8.4
27
+ * @kortyx/telemetry-contracts bumped to 0.14.0
28
+
3
29
  ## [0.30.1](https://github.com/kortyx-io/kortyx/compare/hooks-v0.30.0...hooks-v0.30.1) (2026-10-03)
4
30
 
5
31
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _kortyx_core from '@kortyx/core';
2
2
  import { WorkflowDefinition, TokenUsage, InterruptInput, GraphState, NodeConfig, InterruptResumeValue, InterruptResult } from '@kortyx/core';
3
- import { K as KortyxTelemetryConfig, R as ReasonTraceAdapter, a as KortyxTraceMetadata, b as ReportErrorOptions, c as KortyxTelemetryEventType } from './tracing-BsxYlgEw.js';
4
- export { E as EnsureWorkflowTopologyRequest, d as EnsureWorkflowTopologyResponse, e as KortyxErrorDetails, f as KortyxTelemetryContentCapture, g as KortyxTelemetryCorrelation, h as KortyxTelemetryEvent, i as KortyxTelemetryPrompt, j as KortyxTelemetryReporter, k as KortyxTelemetryService, l as KortyxTraceAdapter, m as KortyxTraceErrorProjection, n as ReasonTraceAttributes, o as ReasonTraceSpan, p as ReasonTraceSpanEndArgs, q as ReasonTraceSpanStartArgs } from './tracing-BsxYlgEw.js';
3
+ import { K as KortyxTelemetryConfig, R as ReasonTraceAdapter, a as KortyxTraceMetadata, b as ReportErrorOptions, c as KortyxTelemetryEventType } from './tracing-mXE0Euvx.js';
4
+ export { E as EnsureWorkflowTopologyRequest, d as EnsureWorkflowTopologyResponse, e as KortyxErrorDetails, f as KortyxTelemetryContentCapture, g as KortyxTelemetryCorrelation, h as KortyxTelemetryEvent, i as KortyxTelemetryPrompt, j as KortyxTelemetryReporter, k as KortyxTelemetryService, l as KortyxTraceAdapter, m as KortyxTraceErrorProjection, n as ReasonTraceAttributes, o as ReasonTraceSpan, p as ReasonTraceSpanEndArgs, q as ReasonTraceSpanStartArgs } from './tracing-mXE0Euvx.js';
5
5
  import { FailureDescriptor, KortyxError } from '@kortyx/core/errors';
6
6
  import * as _kortyx_providers from '@kortyx/providers';
7
7
  import { ToolObservation, ProviderModelRef, KortyxReasoningOptions, KortyxResponseFormat, KortyxExecutableTool, KortyxUsage, KortyxFinishReason, KortyxProviderMetadata, KortyxWarning, KortyxToolCall, KortyxToolResult } from '@kortyx/providers';
package/dist/index.js CHANGED
@@ -717,52 +717,257 @@ var import_core2 = require("@kortyx/core");
717
717
  var import_errors3 = require("@kortyx/core/errors");
718
718
 
719
719
  // src/error-diagnostics.ts
720
+ var import_telemetry_contracts2 = require("@kortyx/telemetry-contracts");
721
+
722
+ // src/diagnostic-capture.ts
720
723
  var import_errors2 = require("@kortyx/core/errors");
721
- var MAX_STACK_LENGTH = 32768;
722
- var MAX_CAUSE_DEPTH = 4;
723
- var projectDetails = (value, depth, seen) => {
724
- const objectValue = value && typeof value === "object" ? value : void 0;
725
- if (objectValue) {
726
- if (seen.has(objectValue))
727
- return { type: "Error", message: "Circular error cause." };
728
- seen.add(objectValue);
724
+ var import_telemetry_contracts = require("@kortyx/telemetry-contracts");
725
+ var encoder = new TextEncoder();
726
+ var nativeStackGetter = Object.getOwnPropertyDescriptor(
727
+ new Error(),
728
+ "stack"
729
+ )?.get;
730
+ function nativeStack(object, getter) {
731
+ if (!getter || getter !== nativeStackGetter)
732
+ throw new Error("Custom stack accessor");
733
+ for (const key of ["name", "message"]) {
734
+ let cursor = object;
735
+ while (cursor) {
736
+ const descriptor = Object.getOwnPropertyDescriptor(cursor, key);
737
+ if (descriptor) {
738
+ if (!("value" in descriptor) || typeof descriptor.value !== "string")
739
+ throw new Error("Custom error formatting");
740
+ break;
741
+ }
742
+ cursor = Object.getPrototypeOf(cursor);
743
+ }
744
+ }
745
+ const formatter = Object.getOwnPropertyDescriptor(Error, "prepareStackTrace");
746
+ if (formatter && (!("value" in formatter) || !formatter.writable))
747
+ throw new Error("Custom stack formatter");
748
+ Object.defineProperty(Error, "prepareStackTrace", {
749
+ ...formatter ?? { configurable: true, writable: true },
750
+ value: void 0
751
+ });
752
+ try {
753
+ return getter.call(object);
754
+ } finally {
755
+ if (formatter) Object.defineProperty(Error, "prepareStackTrace", formatter);
756
+ else Reflect.deleteProperty(Error, "prepareStackTrace");
729
757
  }
730
- const name = (0, import_errors2.errorProperty)(value, "name") ?? (value instanceof Error ? value.name : void 0);
731
- const message = (0, import_errors2.errorProperty)(value, "message");
732
- const stack = (0, import_errors2.errorProperty)(value, "stack") ?? (value instanceof Error ? (() => {
758
+ }
759
+ var pointer = (key) => key.replace(/~/g, "~0").replace(/\//g, "~1");
760
+ function captureDiagnosticContent(error, project) {
761
+ const omissions = [];
762
+ const redactions = [];
763
+ const seen = /* @__PURE__ */ new WeakMap();
764
+ let nodes = 0;
765
+ let remaining = import_telemetry_contracts.DIAGNOSTIC_LIMITS.bytes - 64 * 1024;
766
+ const omit = (path, reason, extra = {}) => {
767
+ if (omissions.length < 1e3)
768
+ omissions.push({ path: path.slice(0, 4096), reason, ...extra });
769
+ return { $omitted: reason };
770
+ };
771
+ const visit = (value, path, depth) => {
772
+ if (++nodes > import_telemetry_contracts.DIAGNOSTIC_LIMITS.nodes) return omit(path, "node_limit");
773
+ if (depth > import_telemetry_contracts.DIAGNOSTIC_LIMITS.depth) return omit(path, "depth_limit");
774
+ if (typeof value === "string") {
775
+ const clean = (0, import_telemetry_contracts.redactDiagnosticText)(value);
776
+ if (clean !== value && redactions.length < 1e3)
777
+ redactions.push({
778
+ path: path.slice(0, 4096),
779
+ reason: "credential_text"
780
+ });
781
+ const bytes = encoder.encode(JSON.stringify(clean)).length;
782
+ if (bytes > remaining)
783
+ return omit(path, "byte_limit", {
784
+ originalBytes: encoder.encode(value).length
785
+ });
786
+ remaining -= bytes;
787
+ return clean;
788
+ }
789
+ if (value === null || typeof value === "boolean") return value;
790
+ if (typeof value === "number")
791
+ return Number.isFinite(value) ? value : { $type: "number", value: String(value) };
792
+ if (typeof value === "bigint")
793
+ return { $type: "bigint", value: value.toString() };
794
+ if (typeof value === "undefined") return { $type: "undefined" };
795
+ if (typeof value === "function" || typeof value === "symbol")
796
+ return omit(path, `unsupported_${typeof value}`);
797
+ const object = value;
798
+ const reference = seen.get(object);
799
+ if (reference) return { $ref: reference };
800
+ seen.set(object, path);
801
+ remaining -= 32;
802
+ if (remaining < 0) return omit(path, "byte_limit");
733
803
  try {
734
- return value.stack;
804
+ if (value instanceof Date)
805
+ return { $type: "Date", value: Date.prototype.toISOString.call(value) };
806
+ if (value instanceof Map || value instanceof Set) {
807
+ const isMap = value instanceof Map;
808
+ const iterator = isMap ? Map.prototype.entries.call(value) : Set.prototype.values.call(value);
809
+ const count = Object.getOwnPropertyDescriptor(
810
+ isMap ? Map.prototype : Set.prototype,
811
+ "size"
812
+ )?.get?.call(value);
813
+ const entries = [];
814
+ for (let index = 0; index < Math.min(count, import_telemetry_contracts.DIAGNOSTIC_LIMITS.properties); index++)
815
+ entries.push(iterator.next().value);
816
+ if (count > entries.length)
817
+ omissions.push({
818
+ path,
819
+ reason: "collection_limit",
820
+ originalCount: count
821
+ });
822
+ return isMap ? {
823
+ $type: "Map",
824
+ entries: visit(entries, `${path}/entries`, depth + 1)
825
+ } : {
826
+ $type: "Set",
827
+ values: visit(entries, `${path}/values`, depth + 1)
828
+ };
829
+ }
830
+ if (Array.isArray(value)) {
831
+ const count = Math.min(value.length, import_telemetry_contracts.DIAGNOSTIC_LIMITS.properties);
832
+ const items = Array.from(
833
+ { length: count },
834
+ (_, index) => read(value, String(index), `${path}/${index}`, depth)
835
+ );
836
+ if (count < value.length)
837
+ items.push(
838
+ omit(path, "collection_limit", { originalCount: value.length })
839
+ );
840
+ return items;
841
+ }
842
+ const keys = Object.getOwnPropertyNames(object);
843
+ const result = /* @__PURE__ */ Object.create(null);
844
+ if (value instanceof Error) {
845
+ const ownName = Object.getOwnPropertyDescriptor(object, "name");
846
+ let type = ownName && "value" in ownName ? ownName.value : void 0;
847
+ if (typeof type !== "string") {
848
+ const name = Object.getOwnPropertyDescriptor(
849
+ Object.getPrototypeOf(value),
850
+ "name"
851
+ );
852
+ type = name && "value" in name ? name.value : "Error";
853
+ }
854
+ result.type = visit(
855
+ typeof type === "string" ? type : "Error",
856
+ `${path}/type`,
857
+ depth + 1
858
+ );
859
+ }
860
+ for (const key of keys.slice(0, import_telemetry_contracts.DIAGNOSTIC_LIMITS.properties)) {
861
+ if (["__proto__", "constructor", "prototype"].includes(key)) {
862
+ result[key] = omit(`${path}/${pointer(key)}`, "unsafe_key");
863
+ continue;
864
+ }
865
+ remaining -= encoder.encode(key).length + 4;
866
+ result[key] = read(object, key, `${path}/${pointer(key)}`, depth);
867
+ }
868
+ if (keys.length > import_telemetry_contracts.DIAGNOSTIC_LIMITS.properties)
869
+ result.$omittedProperties = omit(path, "property_limit", {
870
+ originalCount: keys.length
871
+ });
872
+ return result;
735
873
  } catch {
736
- return void 0;
874
+ return omit(path, "unreadable_object");
875
+ }
876
+ };
877
+ const read = (object, key, path, depth) => {
878
+ if ((0, import_telemetry_contracts.isPrivateDiagnosticField)(key)) {
879
+ if (redactions.length < 1e3)
880
+ redactions.push({
881
+ path: path.slice(0, 4096),
882
+ reason: "credential_field"
883
+ });
884
+ return "[REDACTED]";
885
+ }
886
+ try {
887
+ const property = Object.getOwnPropertyDescriptor(object, key);
888
+ if (property && "value" in property)
889
+ return visit(property.value, path, depth + 1);
890
+ if (key === "stack" && object instanceof Error && property?.get && property.get === nativeStackGetter)
891
+ return visit(nativeStack(object, property.get), path, depth + 1);
892
+ return omit(
893
+ path,
894
+ property ? "accessor_not_evaluated" : "unavailable_property"
895
+ );
896
+ } catch {
897
+ return omit(path, "unreadable_property");
737
898
  }
738
- })() : void 0);
739
- const cause = depth < MAX_CAUSE_DEPTH ? (0, import_errors2.errorProperty)(value, "cause") : null;
740
- return {
741
- type: typeof name === "string" ? name.slice(0, 256) : "Error",
742
- message: (typeof value === "string" ? value : typeof message === "string" ? message : "Execution reported a fault.").slice(0, 8192),
743
- ...typeof stack === "string" ? { stack: stack.slice(0, MAX_STACK_LENGTH) } : {},
744
- ...cause !== null && cause !== void 0 ? { cause: projectDetails(cause, depth + 1, seen) } : {}
745
899
  };
746
- };
747
- var sanitizeProjectedDetails = (details, depth = 0) => ({
748
- ...typeof details.type === "string" ? { type: details.type.slice(0, 256) } : {},
749
- message: typeof details.message === "string" ? details.message.slice(0, 8192) : "Execution reported a fault.",
750
- ...typeof details.stack === "string" ? { stack: details.stack.slice(0, MAX_STACK_LENGTH) } : {},
751
- ...depth < MAX_CAUSE_DEPTH && details.cause ? { cause: sanitizeProjectedDetails(details.cause, depth + 1) } : {}
752
- });
753
- function exceptionDiagnostics(error, project) {
754
900
  try {
755
- const details = project ? project(error) : projectDetails(error, 0, /* @__PURE__ */ new WeakSet());
756
- return details ? sanitizeProjectedDetails(details) : null;
901
+ const original = project ? project(error) : error;
902
+ if (project && original === null) return null;
903
+ const captured = visit(original, "#/data", 0);
904
+ const upstreamOmissions = (0, import_errors2.errorProperty)(original, "diagnosticOmissions");
905
+ if (Array.isArray(upstreamOmissions))
906
+ for (const note of upstreamOmissions.slice(0, 1e3)) {
907
+ const path = (0, import_errors2.errorProperty)(note, "path");
908
+ const reason = (0, import_errors2.errorProperty)(note, "reason");
909
+ if (typeof path === "string" && typeof reason === "string")
910
+ omissions.push({
911
+ path: (0, import_telemetry_contracts.redactDiagnosticText)(path).slice(0, 4096),
912
+ reason: (0, import_telemetry_contracts.redactDiagnosticText)(reason).slice(0, 128)
913
+ });
914
+ }
915
+ const data = captured && typeof captured === "object" && !Array.isArray(captured) ? captured : {
916
+ type: "Error",
917
+ message: typeof captured === "string" ? captured : "Execution reported a fault.",
918
+ value: captured
919
+ };
920
+ if (typeof data.message !== "string" && !("message" in data))
921
+ data.message = "Execution reported a fault.";
922
+ if (!("type" in data))
923
+ data.type = typeof data.name === "string" ? data.name : "Error";
924
+ const content = {
925
+ schemaVersion: 1,
926
+ data,
927
+ capture: {
928
+ status: omissions.length ? "partial" : "complete",
929
+ omissions,
930
+ redactions
931
+ }
932
+ };
933
+ if (encoder.encode(JSON.stringify(content)).length > import_telemetry_contracts.DIAGNOSTIC_LIMITS.bytes)
934
+ return {
935
+ schemaVersion: 1,
936
+ data: {
937
+ type: "Error",
938
+ message: "Diagnostic exceeded the capture budget."
939
+ },
940
+ capture: {
941
+ status: "partial",
942
+ redactions: [],
943
+ omissions: [{ path: "#/data", reason: "serialized_byte_limit" }]
944
+ }
945
+ };
946
+ return content;
757
947
  } catch {
758
- return null;
948
+ return {
949
+ schemaVersion: 1,
950
+ data: { type: "Error", message: "Diagnostic capture failed." },
951
+ capture: {
952
+ status: "failed",
953
+ redactions: [],
954
+ omissions: [{ path: "#/data", reason: "projection_or_capture_failed" }]
955
+ }
956
+ };
759
957
  }
760
958
  }
959
+
960
+ // src/error-diagnostics.ts
961
+ function exceptionDiagnostics(error, project) {
962
+ const content = captureDiagnosticContent(error, project);
963
+ return !content || content.capture.status === "failed" ? null : content.data;
964
+ }
761
965
  function errorDiagnostics(error, project) {
762
- const details = exceptionDiagnostics(error, project);
966
+ const captured = exceptionDiagnostics(error, project);
967
+ const details = captured ? (0, import_telemetry_contracts2.summarizeDiagnostic)(captured) : null;
763
968
  return details ? {
764
- errorMessage: details.message,
765
- ...details.type ? { errorType: details.type } : {}
969
+ errorMessage: details.message.slice(0, 8192),
970
+ ...details.type ? { errorType: details.type.slice(0, 256) } : {}
766
971
  } : {};
767
972
  }
768
973
 
@@ -854,10 +1059,40 @@ async function closeOwnedTools(owned) {
854
1059
  function captureToolError(observation, error, policy, returned = false) {
855
1060
  try {
856
1061
  const reported = returned && error && typeof error === "object" && "isError" in error && error.isError === true && "content" in error && typeof error.content === "string" ? { name: "ToolError", message: error.content } : error;
1062
+ let projected = false;
1063
+ let projection;
1064
+ let projectionFailure;
1065
+ let projectionFailed = false;
1066
+ const project = policy?.error ? (original) => {
1067
+ if (!projected) {
1068
+ projected = true;
1069
+ try {
1070
+ projection = policy.error?.(original);
1071
+ } catch (error2) {
1072
+ projectionFailure = error2;
1073
+ projectionFailed = true;
1074
+ }
1075
+ }
1076
+ if (projectionFailed) throw projectionFailure;
1077
+ return projection ?? null;
1078
+ } : void 0;
857
1079
  Object.assign(
858
1080
  observation,
859
- errorDiagnostics(policy?.error ? error : reported, policy?.error)
1081
+ errorDiagnostics(policy?.error ? error : reported, project)
1082
+ );
1083
+ const diagnosticId = getHookContext().reasonTrace?.reportError?.(
1084
+ policy?.error ? error : reported,
1085
+ {
1086
+ mechanism: "tool",
1087
+ ...project ? { error: project } : {},
1088
+ correlation: {
1089
+ toolCallId: observation.toolCallId,
1090
+ attemptId: observation.attemptId
1091
+ }
1092
+ }
860
1093
  );
1094
+ if (typeof diagnosticId === "string")
1095
+ observation.diagnosticId = diagnosticId;
861
1096
  } catch {
862
1097
  }
863
1098
  }