deepline 0.1.91 → 0.1.93

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.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -43,6 +53,7 @@ __export(src_exports, {
43
53
  PLAY_BOOTSTRAP_TEMPLATES: () => PLAY_BOOTSTRAP_TEMPLATES,
44
54
  PROD_URL: () => PROD_URL,
45
55
  RateLimitError: () => RateLimitError,
56
+ RunObserveTransportUnavailableError: () => RunObserveTransportUnavailableError,
46
57
  SDK_API_CONTRACT: () => SDK_API_CONTRACT,
47
58
  SDK_VERSION: () => SDK_VERSION,
48
59
  defineInput: () => defineInput,
@@ -246,10 +257,10 @@ var import_node_path2 = require("path");
246
257
 
247
258
  // src/release.ts
248
259
  var SDK_RELEASE = {
249
- version: "0.1.91",
260
+ version: "0.1.93",
250
261
  apiContract: "2026-06-dataset-column-cell-stale-hard-cutover",
251
262
  supportPolicy: {
252
- latest: "0.1.91",
263
+ latest: "0.1.93",
253
264
  minimumSupported: "0.1.53",
254
265
  deprecatedBelow: "0.1.53"
255
266
  }
@@ -604,6 +615,807 @@ function sleep(ms) {
604
615
  return new Promise((resolve2) => setTimeout(resolve2, ms));
605
616
  }
606
617
 
618
+ // src/stream-reconnect.ts
619
+ var STREAM_RECONNECT_BASE_DELAY_MS = 500;
620
+ var STREAM_RECONNECT_MAX_DELAY_MS = 15e3;
621
+ var STREAM_HEALTHY_CONNECTION_MS = 3e4;
622
+ function streamReconnectDelayMs(attempt) {
623
+ const cappedExponentialMs = Math.min(
624
+ STREAM_RECONNECT_MAX_DELAY_MS,
625
+ STREAM_RECONNECT_BASE_DELAY_MS * 2 ** Math.max(0, attempt)
626
+ );
627
+ return Math.max(1, Math.floor(Math.random() * (cappedExponentialMs + 1)));
628
+ }
629
+ function isTransientPlayStreamError(error) {
630
+ if (error instanceof DeeplineError && typeof error.statusCode === "number") {
631
+ return error.statusCode >= 500 && error.statusCode < 600;
632
+ }
633
+ const text = error instanceof Error ? error.message : String(error);
634
+ return /auth validation backend timed out|fetch failed|eaddrnotavail|econnreset|etimedout|eai_again|socket hang up/i.test(
635
+ text
636
+ );
637
+ }
638
+
639
+ // ../shared_libs/play-runtime/live-events.ts
640
+ function resolveTimingWindow(input) {
641
+ const startedAt = typeof input.startedAt === "number" && Number.isFinite(input.startedAt) ? input.startedAt : null;
642
+ const completedAt = typeof input.completedAt === "number" && Number.isFinite(input.completedAt) ? input.completedAt : null;
643
+ const updatedAt = typeof input.updatedAt === "number" && Number.isFinite(input.updatedAt) ? input.updatedAt : null;
644
+ return {
645
+ startedAt,
646
+ completedAt,
647
+ updatedAt
648
+ };
649
+ }
650
+
651
+ // ../shared_libs/play-runtime/run-ledger.ts
652
+ var LOG_TAIL_LIMIT = 100;
653
+ function isRecord(value) {
654
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
655
+ }
656
+ function finiteNumber(value) {
657
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
658
+ }
659
+ function optionalFiniteNumber(value) {
660
+ const normalized = finiteNumber(value);
661
+ return normalized === null ? void 0 : normalized;
662
+ }
663
+ function optionalString(value) {
664
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
665
+ }
666
+ function optionalNullableString(value) {
667
+ if (value === null) return null;
668
+ return optionalString(value);
669
+ }
670
+ function normalizePlayRunLedgerStatus(value) {
671
+ const normalized = String(value ?? "").trim().toLowerCase();
672
+ switch (normalized) {
673
+ case "queued":
674
+ case "pending":
675
+ return "queued";
676
+ case "running":
677
+ case "started":
678
+ return "running";
679
+ case "waiting":
680
+ return "waiting";
681
+ case "completed":
682
+ case "complete":
683
+ case "succeeded":
684
+ return "completed";
685
+ case "failed":
686
+ case "error":
687
+ return "failed";
688
+ case "cancelled":
689
+ case "canceled":
690
+ return "cancelled";
691
+ case "terminated":
692
+ return "terminated";
693
+ case "timed_out":
694
+ case "timeout":
695
+ return "timed_out";
696
+ default:
697
+ return "unknown";
698
+ }
699
+ }
700
+ function createEmptyPlayRunLedgerSnapshot(input) {
701
+ const status = normalizePlayRunLedgerStatus(input.status ?? "unknown");
702
+ const startedAt = finiteNumber(input.startedAt) ?? null;
703
+ const finishedAt = finiteNumber(input.finishedAt) ?? null;
704
+ return {
705
+ runId: input.runId,
706
+ playName: input.playName ?? null,
707
+ status,
708
+ error: optionalNullableString(input.error) ?? null,
709
+ createdAt: finiteNumber(input.createdAt) ?? null,
710
+ startedAt,
711
+ updatedAt: finiteNumber(input.updatedAt) ?? finishedAt ?? startedAt ?? finiteNumber(input.createdAt) ?? null,
712
+ finishedAt,
713
+ durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : null,
714
+ orderedStepIds: [],
715
+ stepsById: {},
716
+ logTail: [],
717
+ totalLogCount: 0,
718
+ logsTruncated: false,
719
+ activeStepId: null,
720
+ activeArtifactTableNamespace: null,
721
+ resultTableNamespace: null
722
+ };
723
+ }
724
+ function normalizePlayRunLedgerSnapshot(value, fallback) {
725
+ if (!isRecord(value)) {
726
+ return createEmptyPlayRunLedgerSnapshot(fallback);
727
+ }
728
+ const orderedStepIds = Array.isArray(value.orderedStepIds) ? value.orderedStepIds.filter(
729
+ (entry) => typeof entry === "string" && Boolean(entry.trim())
730
+ ) : [];
731
+ const rawSteps = isRecord(value.stepsById) ? value.stepsById : {};
732
+ const stepsById = {};
733
+ for (const [stepId, rawStep] of Object.entries(rawSteps)) {
734
+ if (!stepId.trim() || !isRecord(rawStep)) continue;
735
+ const rawStatus = normalizeStepStatus(rawStep.status);
736
+ if (!rawStatus) continue;
737
+ const completedAt = finiteNumber(rawStep.completedAt);
738
+ const status = rawStatus === "running" && completedAt !== null ? "completed" : rawStatus;
739
+ const rawProgress = isRecord(rawStep.progress) ? rawStep.progress : null;
740
+ stepsById[stepId] = {
741
+ stepId,
742
+ label: optionalString(rawStep.label),
743
+ kind: optionalString(rawStep.kind),
744
+ status,
745
+ artifactTableNamespace: optionalNullableString(
746
+ rawStep.artifactTableNamespace
747
+ ),
748
+ startedAt: finiteNumber(rawStep.startedAt),
749
+ completedAt,
750
+ updatedAt: finiteNumber(rawStep.updatedAt),
751
+ progress: rawProgress ? normalizeStepProgress(rawProgress) : null
752
+ };
753
+ }
754
+ const createdAt = finiteNumber(value.createdAt) ?? fallback.createdAt ?? null;
755
+ const startedAt = finiteNumber(value.startedAt) ?? fallback.startedAt ?? null;
756
+ const finishedAt = finiteNumber(value.finishedAt) ?? fallback.finishedAt ?? null;
757
+ const updatedAt = finiteNumber(value.updatedAt) ?? fallback.updatedAt ?? finishedAt ?? startedAt ?? createdAt ?? null;
758
+ const error = Object.prototype.hasOwnProperty.call(value, "error") ? optionalNullableString(value.error) ?? null : fallback.error ?? null;
759
+ const rawTail = Array.isArray(value.logTail) ? value.logTail : value.logs;
760
+ const logTail = Array.isArray(rawTail) ? rawTail.filter((line) => typeof line === "string") : [];
761
+ return {
762
+ runId: optionalString(value.runId) ?? fallback.runId,
763
+ playName: optionalNullableString(value.playName) ?? fallback.playName ?? null,
764
+ status: normalizePlayRunLedgerStatus(value.status ?? fallback.status),
765
+ error,
766
+ createdAt,
767
+ startedAt,
768
+ updatedAt,
769
+ finishedAt,
770
+ durationMs: startedAt !== null && finishedAt !== null ? Math.max(0, finishedAt - startedAt) : finiteNumber(value.durationMs),
771
+ orderedStepIds: orderedStepIds.filter((stepId) => stepsById[stepId]),
772
+ stepsById,
773
+ logTail: logTail.slice(-LOG_TAIL_LIMIT),
774
+ // Snapshots persisted before totalLogCount existed only know the retained
775
+ // tail, so the best lower bound for the cumulative count is the tail size.
776
+ totalLogCount: Math.max(
777
+ finiteNumber(value.totalLogCount) ?? logTail.length,
778
+ logTail.length
779
+ ),
780
+ logsTruncated: value.logsTruncated === true,
781
+ activeStepId: optionalNullableString(value.activeStepId),
782
+ activeArtifactTableNamespace: optionalNullableString(
783
+ value.activeArtifactTableNamespace
784
+ ),
785
+ resultTableNamespace: optionalNullableString(value.resultTableNamespace),
786
+ resultSummary: value.resultSummary,
787
+ result: value.result
788
+ };
789
+ }
790
+ function normalizeStepStatus(value) {
791
+ const normalized = String(value ?? "").trim().toLowerCase();
792
+ if (normalized === "running" || normalized === "completed" || normalized === "failed" || normalized === "skipped") {
793
+ return normalized;
794
+ }
795
+ return null;
796
+ }
797
+ function normalizeStepProgress(value) {
798
+ return {
799
+ ...optionalFiniteNumber(value.completed) !== void 0 ? { completed: optionalFiniteNumber(value.completed) } : {},
800
+ ...optionalFiniteNumber(value.total) !== void 0 ? { total: optionalFiniteNumber(value.total) } : {},
801
+ ...optionalFiniteNumber(value.failed) !== void 0 ? { failed: optionalFiniteNumber(value.failed) } : {},
802
+ ...optionalString(value.message) ? { message: optionalString(value.message) } : {},
803
+ ...optionalNullableString(value.artifactTableNamespace) !== void 0 ? {
804
+ artifactTableNamespace: optionalNullableString(
805
+ value.artifactTableNamespace
806
+ )
807
+ } : {},
808
+ ...finiteNumber(value.updatedAt) !== null ? { updatedAt: finiteNumber(value.updatedAt) } : {}
809
+ };
810
+ }
811
+
812
+ // ../shared_libs/play-runtime/run-snapshot-stream.ts
813
+ function normalizePlayRunLiveStatus(value) {
814
+ const normalized = String(value ?? "").trim().toLowerCase();
815
+ switch (normalized) {
816
+ case "queued":
817
+ case "pending":
818
+ return "running";
819
+ case "running":
820
+ case "started":
821
+ return "running";
822
+ case "completed":
823
+ case "complete":
824
+ case "succeeded":
825
+ return "completed";
826
+ case "failed":
827
+ case "error":
828
+ return "failed";
829
+ case "cancelled":
830
+ case "canceled":
831
+ return "cancelled";
832
+ case "terminated":
833
+ return "terminated";
834
+ case "timed_out":
835
+ case "timeout":
836
+ return "timed_out";
837
+ default:
838
+ return "unknown";
839
+ }
840
+ }
841
+ function isTerminalPlayRunLiveStatus(status) {
842
+ return status === "completed" || status === "failed" || status === "cancelled" || status === "terminated" || status === "timed_out";
843
+ }
844
+ function buildSnapshotFromLedger(snapshot) {
845
+ const nodeStates = snapshot.orderedStepIds.map((stepId) => snapshot.stepsById[stepId]).filter((step) => Boolean(step)).map((step) => ({
846
+ nodeId: step.stepId,
847
+ status: step.status,
848
+ artifactTableNamespace: step.artifactTableNamespace ?? null,
849
+ progress: step.progress ? {
850
+ completed: step.progress.completed,
851
+ total: step.progress.total,
852
+ failed: step.progress.failed,
853
+ message: step.progress.message,
854
+ artifactTableNamespace: step.progress.artifactTableNamespace ?? step.artifactTableNamespace ?? null,
855
+ startedAt: step.startedAt ?? null,
856
+ completedAt: step.completedAt ?? null,
857
+ updatedAt: step.progress.updatedAt ?? step.updatedAt ?? null
858
+ } : null,
859
+ startedAt: step.startedAt ?? null,
860
+ completedAt: step.completedAt ?? null,
861
+ updatedAt: step.updatedAt ?? null
862
+ }));
863
+ return {
864
+ runId: snapshot.runId,
865
+ status: normalizePlayRunLiveStatus(snapshot.status),
866
+ updatedAt: snapshot.updatedAt ?? snapshot.finishedAt ?? snapshot.startedAt ?? null,
867
+ logs: snapshot.logTail,
868
+ totalLogCount: snapshot.totalLogCount,
869
+ ...snapshot.logsTruncated ? { logsTruncated: true } : {},
870
+ activeArtifactTableNamespace: snapshot.activeArtifactTableNamespace ?? null,
871
+ resultTableNamespace: snapshot.resultTableNamespace ?? null,
872
+ nodeStates,
873
+ activeNodeId: snapshot.activeStepId ?? null
874
+ };
875
+ }
876
+ function buildPlayRunStatusSnapshot(input) {
877
+ const ledgerSnapshot = normalizePlayRunLedgerSnapshot(input.run.runSnapshot, {
878
+ runId: input.run.workflowId,
879
+ playName: input.run.name ?? null,
880
+ status: input.run.status,
881
+ createdAt: input.run.createdAt ?? null,
882
+ startedAt: input.run.startedAt ?? null,
883
+ updatedAt: input.run.updatedAt ?? null,
884
+ finishedAt: input.run.finishedAt ?? null
885
+ });
886
+ return buildSnapshotFromLedger(ledgerSnapshot);
887
+ }
888
+ function makeRunStreamEvent(input) {
889
+ return {
890
+ ...input,
891
+ scope: "play",
892
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString()
893
+ };
894
+ }
895
+ var EMPTY_PLAY_RUN_STREAM_DIFF_STATE = {
896
+ runSignature: "",
897
+ snapshotSignature: "",
898
+ stepStatusSignature: "",
899
+ stepProgressSignature: "",
900
+ lastLogSeq: 0
901
+ };
902
+ function getSnapshotCursor(snapshot) {
903
+ return String(snapshot.updatedAt ?? Date.now());
904
+ }
905
+ function getRunSignature(snapshot) {
906
+ return [snapshot.runId, snapshot.status, snapshot.updatedAt ?? 0].join(":");
907
+ }
908
+ function getStepStatusSignature(snapshot) {
909
+ return snapshot.nodeStates.map(
910
+ (state) => [
911
+ state.nodeId,
912
+ state.status,
913
+ state.artifactTableNamespace ?? "",
914
+ state.startedAt ?? "",
915
+ state.completedAt ?? "",
916
+ state.progress?.startedAt ?? "",
917
+ state.progress?.completedAt ?? "",
918
+ state.updatedAt ?? "",
919
+ state.progress?.updatedAt ?? ""
920
+ ].join(":")
921
+ ).join("|");
922
+ }
923
+ function getStepProgressSignature(snapshot) {
924
+ return snapshot.nodeStates.map(
925
+ (state) => [
926
+ state.nodeId,
927
+ state.progress?.completed ?? "",
928
+ state.progress?.total ?? "",
929
+ state.progress?.failed ?? "",
930
+ state.progress?.artifactTableNamespace ?? "",
931
+ state.progress?.startedAt ?? "",
932
+ state.progress?.completedAt ?? "",
933
+ state.progress?.updatedAt ?? "",
934
+ state.progress?.message ?? ""
935
+ ].join(":")
936
+ ).join("|");
937
+ }
938
+ function getSnapshotSignature(snapshot) {
939
+ return JSON.stringify(snapshot);
940
+ }
941
+ function resolvePlayRunLogGap(snapshot, lastLogSeq) {
942
+ if (snapshot.totalLogCount <= lastLogSeq) {
943
+ return null;
944
+ }
945
+ const tailFirstSeq = snapshot.totalLogCount - snapshot.logs.length + 1;
946
+ if (lastLogSeq + 1 >= tailFirstSeq) {
947
+ return null;
948
+ }
949
+ return {
950
+ missingCount: tailFirstSeq - 1 - lastLogSeq,
951
+ tailFirstSeq
952
+ };
953
+ }
954
+ function diffLogLines(input) {
955
+ const { logs, totalLogCount } = input.snapshot;
956
+ if (totalLogCount <= input.lastLogSeq) {
957
+ return { lines: [], lastLogSeq: input.lastLogSeq, firstSeq: null };
958
+ }
959
+ const tailFirstSeq = totalLogCount - logs.length + 1;
960
+ if (input.lastLogSeq + 1 >= tailFirstSeq) {
961
+ return {
962
+ lines: logs.slice(input.lastLogSeq + 1 - tailFirstSeq),
963
+ lastLogSeq: totalLogCount,
964
+ firstSeq: input.lastLogSeq + 1
965
+ };
966
+ }
967
+ const missingCount = tailFirstSeq - 1 - input.lastLogSeq;
968
+ return {
969
+ lines: [
970
+ `[stream] ${missingCount} log lines not retained in the live window; full logs via runs logs`,
971
+ ...logs
972
+ ],
973
+ lastLogSeq: totalLogCount,
974
+ firstSeq: null
975
+ };
976
+ }
977
+ function diffPlayRunStreamEvents(input) {
978
+ const { snapshot, streamId, previous } = input;
979
+ const cursor = getSnapshotCursor(snapshot);
980
+ const logDiff = diffLogLines({
981
+ snapshot,
982
+ lastLogSeq: previous.lastLogSeq
983
+ });
984
+ const next = {
985
+ runSignature: getRunSignature(snapshot),
986
+ stepStatusSignature: getStepStatusSignature(snapshot),
987
+ stepProgressSignature: getStepProgressSignature(snapshot),
988
+ snapshotSignature: getSnapshotSignature(snapshot),
989
+ lastLogSeq: logDiff.lastLogSeq
990
+ };
991
+ const events = [];
992
+ if (next.stepStatusSignature !== previous.stepStatusSignature) {
993
+ for (const state of snapshot.nodeStates) {
994
+ if (state.status === "idle") {
995
+ continue;
996
+ }
997
+ const persistedStartedAt = state.startedAt ?? state.progress?.startedAt ?? null;
998
+ const persistedCompletedAt = state.completedAt ?? state.progress?.completedAt ?? null;
999
+ events.push(
1000
+ makeRunStreamEvent({
1001
+ cursor,
1002
+ streamId,
1003
+ type: "play.step.status",
1004
+ payload: {
1005
+ runId: snapshot.runId,
1006
+ stepId: state.nodeId,
1007
+ status: state.status,
1008
+ artifactTableNamespace: state.artifactTableNamespace ?? null,
1009
+ ...resolveTimingWindow({
1010
+ startedAt: persistedStartedAt,
1011
+ completedAt: persistedCompletedAt,
1012
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1013
+ })
1014
+ }
1015
+ })
1016
+ );
1017
+ }
1018
+ }
1019
+ if (next.stepProgressSignature !== previous.stepProgressSignature) {
1020
+ for (const state of snapshot.nodeStates) {
1021
+ if (!state.progress) {
1022
+ continue;
1023
+ }
1024
+ events.push(
1025
+ makeRunStreamEvent({
1026
+ cursor: String(
1027
+ state.progress.updatedAt ?? snapshot.updatedAt ?? Date.now()
1028
+ ),
1029
+ streamId,
1030
+ type: "play.step.progress",
1031
+ payload: {
1032
+ runId: snapshot.runId,
1033
+ stepId: state.nodeId,
1034
+ completed: state.progress.completed,
1035
+ total: state.progress.total,
1036
+ failed: state.progress.failed,
1037
+ message: state.progress.message,
1038
+ artifactTableNamespace: state.progress.artifactTableNamespace ?? state.artifactTableNamespace ?? null,
1039
+ ...resolveTimingWindow({
1040
+ startedAt: state.startedAt ?? state.progress.startedAt ?? null,
1041
+ completedAt: state.completedAt ?? state.progress.completedAt ?? null,
1042
+ updatedAt: state.progress.updatedAt ?? snapshot.updatedAt ?? null
1043
+ })
1044
+ }
1045
+ })
1046
+ );
1047
+ }
1048
+ }
1049
+ if (logDiff.lines.length > 0) {
1050
+ events.push(
1051
+ makeRunStreamEvent({
1052
+ cursor,
1053
+ streamId,
1054
+ type: "play.run.log",
1055
+ payload: {
1056
+ runId: snapshot.runId,
1057
+ lines: logDiff.lines,
1058
+ source: "worker",
1059
+ ...logDiff.firstSeq !== null ? { firstSeq: logDiff.firstSeq } : {},
1060
+ totalLogCount: snapshot.totalLogCount
1061
+ }
1062
+ })
1063
+ );
1064
+ }
1065
+ if (next.snapshotSignature !== previous.snapshotSignature) {
1066
+ const enrichedNodeStates = snapshot.nodeStates.map((state) => {
1067
+ const timing = resolveTimingWindow({
1068
+ startedAt: state.startedAt ?? state.progress?.startedAt ?? null,
1069
+ completedAt: state.completedAt ?? state.progress?.completedAt ?? null,
1070
+ updatedAt: state.updatedAt ?? state.progress?.updatedAt ?? snapshot.updatedAt ?? null
1071
+ });
1072
+ return {
1073
+ ...state,
1074
+ ...timing,
1075
+ progress: state.progress ? {
1076
+ ...state.progress,
1077
+ ...resolveTimingWindow({
1078
+ startedAt: state.progress.startedAt ?? state.startedAt ?? null,
1079
+ completedAt: state.progress.completedAt ?? state.completedAt ?? null,
1080
+ updatedAt: state.progress.updatedAt ?? state.updatedAt ?? snapshot.updatedAt ?? null
1081
+ })
1082
+ } : state.progress
1083
+ };
1084
+ });
1085
+ events.push(
1086
+ makeRunStreamEvent({
1087
+ cursor,
1088
+ streamId,
1089
+ type: "play.run.snapshot",
1090
+ payload: { ...snapshot, nodeStates: enrichedNodeStates }
1091
+ })
1092
+ );
1093
+ }
1094
+ if (next.runSignature !== previous.runSignature) {
1095
+ events.push(
1096
+ makeRunStreamEvent({
1097
+ cursor,
1098
+ streamId,
1099
+ type: "play.run.status",
1100
+ payload: {
1101
+ runId: snapshot.runId,
1102
+ status: snapshot.status,
1103
+ updatedAt: snapshot.updatedAt
1104
+ }
1105
+ })
1106
+ );
1107
+ }
1108
+ return { events, next };
1109
+ }
1110
+
1111
+ // src/runs/observe-transport.ts
1112
+ var RunObserveTransportUnavailableError = class extends Error {
1113
+ constructor(message, reason) {
1114
+ super(message);
1115
+ this.reason = reason;
1116
+ this.name = "RunObserveTransportUnavailableError";
1117
+ }
1118
+ reason;
1119
+ };
1120
+ var OBSERVE_BOOTSTRAP_TIMEOUT_MS = 1e4;
1121
+ var OBSERVE_RECONNECT_NOTICE_MS = 1e4;
1122
+ var OBSERVE_STALE_WARNING_MS = 12e4;
1123
+ var OBSERVE_WATCHDOG_TICK_MS = 5e3;
1124
+ var GRANT_REFRESH_MARGIN_MS = 5 * 6e4;
1125
+ var BACKFILL_PAGE_LIMIT = 1e3;
1126
+ var BACKFILL_MAX_PAGES = 30;
1127
+ var OBSERVER_SNAPSHOT_QUERY = "runObservers:getPlayRunSnapshotForObserver";
1128
+ var OBSERVER_LOG_PAGE_QUERY = "runObservers:getRunLogPageForObserver";
1129
+ function errorText(error) {
1130
+ return error instanceof Error ? error.message : String(error);
1131
+ }
1132
+ async function mintRunObserveGrant(http, runId) {
1133
+ let response;
1134
+ try {
1135
+ response = await http.post(
1136
+ `/api/v2/runs/${encodeURIComponent(runId)}/observe-grant`,
1137
+ {}
1138
+ );
1139
+ } catch (error) {
1140
+ if (error instanceof DeeplineError) {
1141
+ if (error.statusCode === 401 || error.statusCode === 403) {
1142
+ throw error;
1143
+ }
1144
+ throw new RunObserveTransportUnavailableError(
1145
+ `observe-grant endpoint unavailable (${error.statusCode ?? "network"}): ${error.message}`,
1146
+ "grant_endpoint_unavailable"
1147
+ );
1148
+ }
1149
+ throw new RunObserveTransportUnavailableError(
1150
+ `observe-grant request failed: ${errorText(error)}`,
1151
+ "grant_request_failed"
1152
+ );
1153
+ }
1154
+ const grant = response;
1155
+ if (!grant || typeof grant.convexUrl !== "string" || !grant.convexUrl.trim() || typeof grant.token !== "string" || !grant.token.trim() || typeof grant.expiresAt !== "number") {
1156
+ throw new RunObserveTransportUnavailableError(
1157
+ "observe-grant endpoint returned an invalid grant payload.",
1158
+ "grant_payload_invalid"
1159
+ );
1160
+ }
1161
+ return grant;
1162
+ }
1163
+ async function backfillLogGap(input) {
1164
+ const lines = [];
1165
+ let cursor = input.lastLogSeq;
1166
+ for (let page = 0; page < BACKFILL_MAX_PAGES; page += 1) {
1167
+ if (cursor >= input.tailFirstSeq - 1) {
1168
+ break;
1169
+ }
1170
+ let logPage;
1171
+ try {
1172
+ logPage = await input.queryLogPage(
1173
+ cursor,
1174
+ Math.min(BACKFILL_PAGE_LIMIT, input.tailFirstSeq - 1 - cursor)
1175
+ );
1176
+ } catch {
1177
+ return null;
1178
+ }
1179
+ const entries = (logPage?.entries ?? []).filter(
1180
+ (entry) => entry.seq > cursor && entry.seq < input.tailFirstSeq
1181
+ );
1182
+ if (entries.length === 0) {
1183
+ break;
1184
+ }
1185
+ for (const entry of entries) {
1186
+ if (entry.seq !== cursor + 1) {
1187
+ return null;
1188
+ }
1189
+ lines.push(entry.line);
1190
+ cursor = entry.seq;
1191
+ }
1192
+ }
1193
+ if (cursor < input.tailFirstSeq - 1) {
1194
+ return null;
1195
+ }
1196
+ return lines;
1197
+ }
1198
+ async function* observeRunEvents(options) {
1199
+ const { http, runId } = options;
1200
+ let grant = await mintRunObserveGrant(http, runId);
1201
+ let convexBrowser;
1202
+ let convexServer;
1203
+ try {
1204
+ convexBrowser = await import("convex/browser");
1205
+ convexServer = await import("convex/server");
1206
+ } catch (error) {
1207
+ throw new RunObserveTransportUnavailableError(
1208
+ `convex client module unavailable: ${errorText(error)}`,
1209
+ "convex_module_unavailable"
1210
+ );
1211
+ }
1212
+ let webSocketConstructor;
1213
+ if (typeof WebSocket === "undefined") {
1214
+ try {
1215
+ const wsModuleName = "ws";
1216
+ const ws = await import(wsModuleName);
1217
+ webSocketConstructor = ws.default;
1218
+ } catch (error) {
1219
+ throw new RunObserveTransportUnavailableError(
1220
+ `no WebSocket implementation available: ${errorText(error)}`,
1221
+ "websocket_unavailable"
1222
+ );
1223
+ }
1224
+ }
1225
+ const snapshotQuery = convexServer.makeFunctionReference(
1226
+ OBSERVER_SNAPSHOT_QUERY
1227
+ );
1228
+ const logPageQuery = convexServer.makeFunctionReference(
1229
+ OBSERVER_LOG_PAGE_QUERY
1230
+ );
1231
+ const client = new convexBrowser.ConvexClient(grant.convexUrl, {
1232
+ ...webSocketConstructor ? { webSocketConstructor } : {},
1233
+ unsavedChangesWarning: false
1234
+ });
1235
+ const queue = [];
1236
+ let wake = null;
1237
+ const push = (item) => {
1238
+ queue.push(item);
1239
+ wake?.();
1240
+ wake = null;
1241
+ };
1242
+ let lastForcedRefreshAt = 0;
1243
+ client.setAuth(async ({ forceRefreshToken }) => {
1244
+ const now = Date.now();
1245
+ if (!forceRefreshToken && grant.expiresAt - now > GRANT_REFRESH_MARGIN_MS) {
1246
+ return grant.token;
1247
+ }
1248
+ if (forceRefreshToken && now - lastForcedRefreshAt < 5e3) {
1249
+ push({
1250
+ kind: "error",
1251
+ error: new DeeplineError(
1252
+ `Run observe grant for ${runId} was rejected after a re-mint. The server and Convex deployment disagree on the grant issuer/JWKS.`,
1253
+ 401,
1254
+ "RUN_OBSERVE_GRANT_REJECTED"
1255
+ )
1256
+ });
1257
+ return null;
1258
+ }
1259
+ if (forceRefreshToken) {
1260
+ lastForcedRefreshAt = now;
1261
+ }
1262
+ try {
1263
+ grant = await mintRunObserveGrant(http, runId);
1264
+ return grant.token;
1265
+ } catch (error) {
1266
+ push({ kind: "error", error });
1267
+ return null;
1268
+ }
1269
+ });
1270
+ const unsubscribe = client.onUpdate(
1271
+ snapshotQuery,
1272
+ { workflowId: runId },
1273
+ (run) => push({ kind: "run", run: run ?? null }),
1274
+ (error) => push({ kind: "error", error })
1275
+ );
1276
+ let lastUpdateAt = Date.now();
1277
+ let lastStatusTerminal = false;
1278
+ let disconnectedSince = null;
1279
+ let warnedReconnecting = false;
1280
+ let warnedStale = false;
1281
+ const watchdog = setInterval(() => {
1282
+ const now = Date.now();
1283
+ try {
1284
+ const connectionState = client.connectionState();
1285
+ if (connectionState.isWebSocketConnected) {
1286
+ disconnectedSince = null;
1287
+ warnedReconnecting = false;
1288
+ } else {
1289
+ disconnectedSince ??= now;
1290
+ if (!warnedReconnecting && now - disconnectedSince >= OBSERVE_RECONNECT_NOTICE_MS) {
1291
+ warnedReconnecting = true;
1292
+ options.onNotice?.(
1293
+ `[observe] connection lost; reconnecting to live run ${runId}\u2026`
1294
+ );
1295
+ }
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ if (!lastStatusTerminal && !warnedStale && now - lastUpdateAt >= OBSERVE_STALE_WARNING_MS) {
1300
+ warnedStale = true;
1301
+ options.onNotice?.(
1302
+ `[observe] no live updates for ${Math.round((now - lastUpdateAt) / 1e3)}s; run ${runId} may be stalled (status checks continue)`
1303
+ );
1304
+ }
1305
+ }, OBSERVE_WATCHDOG_TICK_MS);
1306
+ watchdog.unref?.();
1307
+ const abortListener = () => push({
1308
+ kind: "error",
1309
+ error: new DeeplineError(
1310
+ "Run observation aborted.",
1311
+ void 0,
1312
+ "ABORTED"
1313
+ )
1314
+ });
1315
+ options.signal?.addEventListener("abort", abortListener, { once: true });
1316
+ let diffState = EMPTY_PLAY_RUN_STREAM_DIFF_STATE;
1317
+ const streamId = ["observe", runId].join(":");
1318
+ let sawFirstSnapshot = false;
1319
+ try {
1320
+ for (; ; ) {
1321
+ if (queue.length === 0) {
1322
+ const waitForItem = new Promise((resolve2) => {
1323
+ wake = resolve2;
1324
+ });
1325
+ if (!sawFirstSnapshot) {
1326
+ const timedOut = await Promise.race([
1327
+ waitForItem.then(() => false),
1328
+ new Promise(
1329
+ (resolve2) => setTimeout(() => resolve2(true), OBSERVE_BOOTSTRAP_TIMEOUT_MS)
1330
+ )
1331
+ ]);
1332
+ if (timedOut && queue.length === 0) {
1333
+ throw new RunObserveTransportUnavailableError(
1334
+ `no snapshot from Convex at ${grant.convexUrl} within ${OBSERVE_BOOTSTRAP_TIMEOUT_MS}ms`,
1335
+ "convex_unreachable"
1336
+ );
1337
+ }
1338
+ } else {
1339
+ await waitForItem;
1340
+ }
1341
+ continue;
1342
+ }
1343
+ const item = queue.shift();
1344
+ if (item.kind === "error") {
1345
+ if (options.signal?.aborted) {
1346
+ return;
1347
+ }
1348
+ throw item.error;
1349
+ }
1350
+ sawFirstSnapshot = true;
1351
+ lastUpdateAt = Date.now();
1352
+ warnedStale = false;
1353
+ if (item.run === null) {
1354
+ throw new DeeplineError(
1355
+ `Run ${runId} was not found (or is not visible to this grant).`,
1356
+ 404,
1357
+ "RUN_NOT_FOUND"
1358
+ );
1359
+ }
1360
+ const snapshot = buildPlayRunStatusSnapshot({ run: item.run });
1361
+ lastStatusTerminal = isTerminalPlayRunLiveStatus(snapshot.status);
1362
+ const gap = resolvePlayRunLogGap(snapshot, diffState.lastLogSeq);
1363
+ if (gap && diffState.lastLogSeq > 0) {
1364
+ const backfilled = await backfillLogGap({
1365
+ queryLogPage: (afterSeq, limit) => client.query(logPageQuery, {
1366
+ workflowId: runId,
1367
+ afterSeq,
1368
+ limit
1369
+ }),
1370
+ lastLogSeq: diffState.lastLogSeq,
1371
+ tailFirstSeq: gap.tailFirstSeq
1372
+ });
1373
+ if (backfilled && backfilled.length > 0) {
1374
+ yield {
1375
+ cursor: String(snapshot.updatedAt ?? Date.now()),
1376
+ streamId,
1377
+ scope: "play",
1378
+ type: "play.run.log",
1379
+ at: (/* @__PURE__ */ new Date()).toISOString(),
1380
+ payload: {
1381
+ runId: snapshot.runId,
1382
+ lines: backfilled,
1383
+ source: "worker",
1384
+ firstSeq: diffState.lastLogSeq + 1,
1385
+ totalLogCount: snapshot.totalLogCount
1386
+ }
1387
+ };
1388
+ diffState = { ...diffState, lastLogSeq: gap.tailFirstSeq - 1 };
1389
+ }
1390
+ }
1391
+ const { events, next } = diffPlayRunStreamEvents({
1392
+ streamId,
1393
+ snapshot,
1394
+ previous: diffState
1395
+ });
1396
+ diffState = next;
1397
+ const ordered = [
1398
+ ...events.filter((event) => event.type === "play.run.snapshot"),
1399
+ ...events.filter((event) => event.type !== "play.run.snapshot")
1400
+ ];
1401
+ for (const event of ordered) {
1402
+ yield event;
1403
+ }
1404
+ if (lastStatusTerminal) {
1405
+ return;
1406
+ }
1407
+ }
1408
+ } finally {
1409
+ clearInterval(watchdog);
1410
+ options.signal?.removeEventListener("abort", abortListener);
1411
+ try {
1412
+ unsubscribe();
1413
+ } catch {
1414
+ }
1415
+ await client.close().catch(() => void 0);
1416
+ }
1417
+ }
1418
+
607
1419
  // src/client.ts
608
1420
  var TERMINAL_PLAY_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
609
1421
  var INCLUDE_TOOL_METADATA_HEADER = "x-deepline-include-tool-metadata";
@@ -622,7 +1434,8 @@ function isTransientCompileManifestError(error) {
622
1434
  message
623
1435
  );
624
1436
  }
625
- function isRecord(value) {
1437
+ var RUN_LOGS_PAGE_LIMIT = 1e3;
1438
+ function isRecord2(value) {
626
1439
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
627
1440
  }
628
1441
  function isPrebuiltPlayDescription(play) {
@@ -692,10 +1505,31 @@ function normalizeLiveStatus(value) {
692
1505
  }
693
1506
  return null;
694
1507
  }
1508
+ function appendPlayLiveLogLines(state, payload) {
1509
+ const lines = readStringArray(payload.lines);
1510
+ if (lines.length === 0) {
1511
+ return;
1512
+ }
1513
+ const firstSeq = typeof payload.firstSeq === "number" && Number.isFinite(payload.firstSeq) && payload.firstSeq >= 1 ? Math.trunc(payload.firstSeq) : null;
1514
+ if (firstSeq === null) {
1515
+ state.logs.push(...lines);
1516
+ const totalLogCount = typeof payload.totalLogCount === "number" && Number.isFinite(payload.totalLogCount) ? Math.trunc(payload.totalLogCount) : null;
1517
+ if (totalLogCount !== null) {
1518
+ state.lastLogSeq = Math.max(state.lastLogSeq, totalLogCount);
1519
+ }
1520
+ return;
1521
+ }
1522
+ const skip = Math.max(0, state.lastLogSeq + 1 - firstSeq);
1523
+ if (skip >= lines.length) {
1524
+ return;
1525
+ }
1526
+ state.logs.push(...lines.slice(skip));
1527
+ state.lastLogSeq = Math.max(state.lastLogSeq, firstSeq + lines.length - 1);
1528
+ }
695
1529
  function updatePlayLiveStatusState(state, event) {
696
1530
  const payload = getPlayLiveEventPayload(event);
697
1531
  if (event.type === "play.run.log") {
698
- state.logs.push(...readStringArray(payload.lines));
1532
+ appendPlayLiveLogLines(state, payload);
699
1533
  return null;
700
1534
  }
701
1535
  if (event.type !== "play.run.snapshot" && event.type !== "play.run.status" && event.type !== "play.run.final_status") {
@@ -703,12 +1537,14 @@ function updatePlayLiveStatusState(state, event) {
703
1537
  }
704
1538
  const runId = typeof payload.runId === "string" && payload.runId ? payload.runId : isPlayRunPackage(payload) ? payload.run.id : state.runId;
705
1539
  const status = normalizeLiveStatus(payload.status) ?? (isPlayRunPackage(payload) ? normalizeLiveStatus(payload.run.status) : null) ?? state.status;
706
- const progressPayload = isRecord(payload.progress) ? payload.progress : {};
707
- const payloadLogs = readStringArray(payload.logs);
708
- const progressLogs = readStringArray(progressPayload.logs);
709
- const logs = payloadLogs.length > 0 ? payloadLogs : progressLogs;
710
- if (logs.length > 0 || event.type === "play.run.snapshot" || event.type === "play.run.final_status" && !isPlayRunPackage(payload)) {
711
- state.logs = logs;
1540
+ const progressPayload = isRecord2(payload.progress) ? payload.progress : {};
1541
+ if (event.type === "play.run.final_status" && state.logs.length === 0 && state.lastLogSeq === 0) {
1542
+ const payloadLogs = readStringArray(payload.logs);
1543
+ const progressLogs = readStringArray(progressPayload.logs);
1544
+ const seedLogs = payloadLogs.length > 0 ? payloadLogs : progressLogs;
1545
+ if (seedLogs.length > 0) {
1546
+ state.logs = seedLogs;
1547
+ }
712
1548
  }
713
1549
  if ("result" in payload) {
714
1550
  state.result = payload.result;
@@ -802,9 +1638,9 @@ var DeeplineClient = class {
802
1638
  return fields.length > 0 ? { fields } : schema;
803
1639
  }
804
1640
  schemaMetadata(schema, key) {
805
- if (!isRecord(schema)) return null;
1641
+ if (!isRecord2(schema)) return null;
806
1642
  const value = schema[key];
807
- return isRecord(value) ? value : null;
1643
+ return isRecord2(value) ? value : null;
808
1644
  }
809
1645
  playRunCommand(play, options) {
810
1646
  const target = play.reference || play.name;
@@ -851,7 +1687,7 @@ var DeeplineClient = class {
851
1687
  aliases,
852
1688
  inputSchema: options?.compact ? this.compactSchema(play.inputSchema) : play.inputSchema ?? null,
853
1689
  outputSchema: options?.compact ? this.compactSchema(play.outputSchema) : play.outputSchema ?? null,
854
- staticPipeline: isRecord(play.staticPipeline) ? play.staticPipeline : isRecord(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
1690
+ staticPipeline: isRecord2(play.staticPipeline) ? play.staticPipeline : isRecord2(play.currentRevision?.staticPipeline) ? play.currentRevision.staticPipeline : isRecord2(play.liveRevision?.staticPipeline) ? play.liveRevision.staticPipeline : null,
855
1691
  ...csvInput ? { csvInput } : {},
856
1692
  ...rowOutputSchema ? { rowOutputSchema } : {},
857
1693
  runCommand,
@@ -1535,43 +2371,140 @@ var DeeplineClient = class {
1535
2371
  );
1536
2372
  return response.runs ?? [];
1537
2373
  }
1538
- /** Read the canonical run stream and return the latest run snapshot. */
1539
- async tailRun(runId, options) {
2374
+ /**
2375
+ * Observe one run's live events through the Convex Run Snapshot
2376
+ * subscription transport (ADR-0008). Yields the same `play.*` event
2377
+ * envelopes as {@link streamPlayRunEvents} and ends after the terminal
2378
+ * snapshot. Throws {@link RunObserveTransportUnavailableError} when this
2379
+ * server cannot serve the transport (older server, unconfigured grants, or
2380
+ * unreachable Convex) — callers fall back to the SSE stream with a notice.
2381
+ */
2382
+ observeRunEvents(runId, options) {
2383
+ return observeRunEvents({
2384
+ http: this.http,
2385
+ runId,
2386
+ signal: options?.signal,
2387
+ onNotice: options?.onNotice
2388
+ });
2389
+ }
2390
+ /**
2391
+ * Tail one run through the subscription transport until terminal, then
2392
+ * return one durable REST status read (the final Run Response Package).
2393
+ */
2394
+ async tailRunViaObserveTransport(runId, options) {
1540
2395
  const state = {
1541
2396
  runId,
1542
2397
  status: "running",
1543
2398
  logs: [],
2399
+ lastLogSeq: 0,
1544
2400
  latest: null
1545
2401
  };
1546
- let terminal = false;
1547
- for await (const event of this.streamPlayRunEvents(runId, {
1548
- mode: "cli",
1549
- signal: options?.signal
2402
+ for await (const event of this.observeRunEvents(runId, {
2403
+ signal: options?.signal,
2404
+ onNotice: options?.onNotice
1550
2405
  })) {
1551
2406
  const status = updatePlayLiveStatusState(state, event);
1552
- if (!status) {
2407
+ if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
1553
2408
  continue;
1554
2409
  }
1555
- terminal = TERMINAL_PLAY_STATUSES.has(status.status);
1556
- if (terminal) {
1557
- break;
1558
- }
1559
- }
1560
- if (terminal && state.latest) {
1561
- return await this.getRunStatus(state.latest.runId || runId).catch(
2410
+ return await this.getRunStatus(status.runId || runId).catch(
1562
2411
  () => state.latest ?? playRunStatusFromState(state)
1563
2412
  );
1564
2413
  }
1565
- if (state.latest) {
1566
- return state.latest;
2414
+ if (options?.signal?.aborted) {
2415
+ throw new DeeplineError("Run observation aborted.", void 0, "ABORTED");
2416
+ }
2417
+ const refreshed = await this.getRunStatus(runId);
2418
+ if (TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
2419
+ return refreshed;
1567
2420
  }
1568
2421
  throw new DeeplineError(
1569
- `Run stream for ${runId} ended before the initial snapshot.`,
2422
+ `Run observation for ${runId} ended before a terminal status.`,
1570
2423
  void 0,
1571
- "PLAY_RUN_STREAM_EMPTY",
1572
- { runId }
2424
+ "PLAY_LIVE_STREAM_ENDED"
1573
2425
  );
1574
2426
  }
2427
+ /**
2428
+ * Read the canonical run stream until a terminal run status is observed.
2429
+ *
2430
+ * Tries the Convex Run Snapshot subscription transport first (ADR-0008);
2431
+ * when the server cannot serve it (grant endpoint missing/unconfigured or
2432
+ * Convex unreachable) it falls back — with one `onNotice` message — to the
2433
+ * support-window SSE stream below.
2434
+ *
2435
+ * Server stream windows are finite: they end cleanly at the function
2436
+ * ceiling even while the run keeps executing. A window that ends (cleanly
2437
+ * or via transient network error) without a terminal event triggers one
2438
+ * durable-status re-check followed by a backed-off reconnect, so long runs
2439
+ * tail to completion. Abort via `options.signal` to stop waiting.
2440
+ */
2441
+ async tailRun(runId, options) {
2442
+ try {
2443
+ return await this.tailRunViaObserveTransport(runId, options);
2444
+ } catch (error) {
2445
+ if (!(error instanceof RunObserveTransportUnavailableError)) {
2446
+ throw error;
2447
+ }
2448
+ options?.onNotice?.(
2449
+ `[observe] live subscription unavailable (${error.reason}); falling back to SSE tail (support window, ADR-0008)`
2450
+ );
2451
+ }
2452
+ const state = {
2453
+ runId,
2454
+ status: "running",
2455
+ logs: [],
2456
+ lastLogSeq: 0,
2457
+ latest: null
2458
+ };
2459
+ let reconnectAttempt = 0;
2460
+ for (; ; ) {
2461
+ const connectedAt = Date.now();
2462
+ let sawEvent = false;
2463
+ let endedReason = "stream window ended before a terminal event";
2464
+ try {
2465
+ for await (const event of this.streamPlayRunEvents(runId, {
2466
+ mode: "cli",
2467
+ signal: options?.signal
2468
+ })) {
2469
+ sawEvent = true;
2470
+ const status = updatePlayLiveStatusState(state, event);
2471
+ if (!status || !TERMINAL_PLAY_STATUSES.has(status.status)) {
2472
+ continue;
2473
+ }
2474
+ return await this.getRunStatus(status.runId || runId).catch(
2475
+ () => state.latest ?? playRunStatusFromState(state)
2476
+ );
2477
+ }
2478
+ } catch (error) {
2479
+ if (options?.signal?.aborted || !isTransientPlayStreamError(error)) {
2480
+ throw error;
2481
+ }
2482
+ endedReason = error instanceof Error ? error.message : String(error);
2483
+ }
2484
+ let refreshed = null;
2485
+ try {
2486
+ refreshed = await this.getRunStatus(runId);
2487
+ } catch (error) {
2488
+ if (!isTransientPlayStreamError(error)) {
2489
+ throw error;
2490
+ }
2491
+ }
2492
+ if (refreshed && TERMINAL_PLAY_STATUSES.has(refreshed.status)) {
2493
+ return refreshed;
2494
+ }
2495
+ if (sawEvent || Date.now() - connectedAt >= STREAM_HEALTHY_CONNECTION_MS) {
2496
+ reconnectAttempt = 0;
2497
+ }
2498
+ const delayMs = streamReconnectDelayMs(reconnectAttempt);
2499
+ reconnectAttempt += 1;
2500
+ options?.onReconnect?.({
2501
+ attempt: reconnectAttempt,
2502
+ delayMs,
2503
+ reason: endedReason
2504
+ });
2505
+ await sleep2(delayMs);
2506
+ }
2507
+ }
1575
2508
  /**
1576
2509
  * Fetch persisted logs for a run using the public runs resource model.
1577
2510
  *
@@ -1582,19 +2515,40 @@ var DeeplineClient = class {
1582
2515
  * ```
1583
2516
  */
1584
2517
  async getRunLogs(runId, options) {
1585
- const status = await this.getRunStatus(runId, { full: true });
1586
- const logs = status.progress?.logs ?? [];
1587
- const limit = typeof options?.limit === "number" && Number.isFinite(options.limit) ? Math.max(0, Math.trunc(options.limit)) : 200;
1588
- const entries = logs.slice(Math.max(0, logs.length - limit));
2518
+ const limit = options?.all ? Number.MAX_SAFE_INTEGER : typeof options?.limit === "number" && Number.isFinite(options.limit) && options.limit > 0 ? Math.trunc(options.limit) : 200;
2519
+ const fetchPage = (afterSeq2, pageLimit) => this.http.get(
2520
+ `/api/v2/runs/${encodeURIComponent(runId)}/logs?afterSeq=${afterSeq2}&limit=${pageLimit}`
2521
+ );
2522
+ const probe = await fetchPage(0, 1);
2523
+ const lastStoredSeq = probe.lastStoredSeq;
2524
+ let afterSeq = options?.all ? 0 : Math.max(0, lastStoredSeq - limit);
2525
+ const entries = [];
2526
+ while (entries.length < limit) {
2527
+ const page = await fetchPage(
2528
+ afterSeq,
2529
+ Math.min(RUN_LOGS_PAGE_LIMIT, limit - entries.length)
2530
+ );
2531
+ if (page.entries.length === 0) {
2532
+ break;
2533
+ }
2534
+ entries.push(...page.entries);
2535
+ afterSeq = page.entries[page.entries.length - 1].seq;
2536
+ if (!page.hasMore) {
2537
+ break;
2538
+ }
2539
+ }
2540
+ const firstSequence = entries.length > 0 ? entries[0].seq : null;
2541
+ const lastSequence = entries.length > 0 ? entries[entries.length - 1].seq : null;
1589
2542
  return {
1590
- runId: status.runId,
1591
- totalCount: logs.length,
2543
+ runId: probe.runId,
2544
+ totalCount: probe.totalLogCount,
1592
2545
  returnedCount: entries.length,
1593
- firstSequence: logs.length === 0 ? null : logs.length - entries.length + 1,
1594
- lastSequence: logs.length === 0 ? null : logs.length,
1595
- truncated: logs.length > entries.length,
1596
- hasMore: logs.length > entries.length,
1597
- entries
2546
+ firstSequence,
2547
+ lastSequence,
2548
+ truncated: entries.length < probe.totalLogCount,
2549
+ hasMore: lastSequence !== null && lastSequence < lastStoredSeq,
2550
+ entries: entries.map((entry) => entry.line),
2551
+ ...probe.logsTruncated ? { logsTruncated: true } : {}
1598
2552
  };
1599
2553
  }
1600
2554
  /**
@@ -1885,6 +2839,7 @@ var DeeplineClient = class {
1885
2839
  runId: workflowId,
1886
2840
  status: "running",
1887
2841
  logs: [],
2842
+ lastLogSeq: 0,
1888
2843
  latest: null
1889
2844
  };
1890
2845
  if (options?.signal?.aborted) {
@@ -2255,7 +3210,7 @@ var TARGET_FALLBACK_KEYS = {
2255
3210
  status: [/^email_status$/i, /^status$/i],
2256
3211
  email_status: [/^email_status$/i, /^status$/i]
2257
3212
  };
2258
- function isRecord2(value) {
3213
+ function isRecord3(value) {
2259
3214
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2260
3215
  }
2261
3216
  function toV2RawToolOutputPath(path) {
@@ -2315,7 +3270,7 @@ function valuesAtSegments(current, segments, path = []) {
2315
3270
  if (!Array.isArray(current)) return [];
2316
3271
  return valuesAtSegments(current[segment], rest, [...path, segment]);
2317
3272
  }
2318
- if (!isRecord2(current)) return [];
3273
+ if (!isRecord3(current)) return [];
2319
3274
  const directMatches = valuesAtSegments(current[segment], rest, [
2320
3275
  ...path,
2321
3276
  segment
@@ -2345,7 +3300,7 @@ function getValuesAtPath(root, path) {
2345
3300
  return valuesAtSegments(root, parsePath(path)).map((entry) => entry.value);
2346
3301
  }
2347
3302
  function toResultEnvelope(value) {
2348
- if (isRecord2(value) && "data" in value) {
3303
+ if (isRecord3(value) && "data" in value) {
2349
3304
  const envelope = { data: value.data };
2350
3305
  if ("meta" in value) envelope.meta = value.meta;
2351
3306
  return envelope;
@@ -2398,7 +3353,7 @@ function getAtPath(root, path) {
2398
3353
  current = current[segment];
2399
3354
  continue;
2400
3355
  }
2401
- if (!isRecord2(current)) return void 0;
3356
+ if (!isRecord3(current)) return void 0;
2402
3357
  current = current[segment];
2403
3358
  }
2404
3359
  return current;
@@ -2415,7 +3370,7 @@ function normalizeString(value) {
2415
3370
  }
2416
3371
  function normalizeRows(value) {
2417
3372
  if (!Array.isArray(value)) return null;
2418
- return value.map((entry) => isRecord2(entry) ? entry : { value: entry });
3373
+ return value.map((entry) => isRecord3(entry) ? entry : { value: entry });
2419
3374
  }
2420
3375
  function findFirstTargetByPath(result, paths) {
2421
3376
  for (const path of paths ?? []) {
@@ -2474,7 +3429,7 @@ function findFirstTargetByKey(result, target, depth = 0, path = []) {
2474
3429
  }
2475
3430
  return null;
2476
3431
  }
2477
- if (!isRecord2(result)) return null;
3432
+ if (!isRecord3(result)) return null;
2478
3433
  const patterns = TARGET_FALLBACK_KEYS[target] ?? [
2479
3434
  new RegExp(`^${target}$`, "i")
2480
3435
  ];
@@ -2534,7 +3489,7 @@ function normalizeJobChangeStatus(value) {
2534
3489
  function firstExperienceDate(value) {
2535
3490
  if (!Array.isArray(value)) return null;
2536
3491
  for (const entry of value) {
2537
- if (!isRecord2(entry)) continue;
3492
+ if (!isRecord3(entry)) continue;
2538
3493
  const date = normalizeString(
2539
3494
  entry.start_date ?? entry.started_at ?? entry.startDate
2540
3495
  );
@@ -2543,10 +3498,10 @@ function firstExperienceDate(value) {
2543
3498
  return null;
2544
3499
  }
2545
3500
  function normalizeJobChange(value) {
2546
- const record = isRecord2(value) ? value : {};
2547
- const nested = isRecord2(record.job_change) ? record.job_change : record;
2548
- const output = isRecord2(nested.output) ? nested.output : nested;
2549
- const person = isRecord2(output.person) ? output.person : {};
3501
+ const record = isRecord3(value) ? value : {};
3502
+ const nested = isRecord3(record.job_change) ? record.job_change : record;
3503
+ const output = isRecord3(nested.output) ? nested.output : nested;
3504
+ const person = isRecord3(output.person) ? output.person : {};
2550
3505
  const status = normalizeJobChangeStatus(
2551
3506
  output.status ?? output.job_change_status ?? output.job_changed ?? output.changed
2552
3507
  );
@@ -3114,11 +4069,11 @@ var Deepline = class {
3114
4069
  return new DeeplineContext(options);
3115
4070
  }
3116
4071
  };
3117
- function isRecord3(value) {
4072
+ function isRecord4(value) {
3118
4073
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
3119
4074
  }
3120
4075
  function stringArrayRecord(value) {
3121
- if (!isRecord3(value)) return {};
4076
+ if (!isRecord4(value)) return {};
3122
4077
  return Object.fromEntries(
3123
4078
  Object.entries(value).map(([key, paths]) => [
3124
4079
  key,
@@ -3130,10 +4085,10 @@ function stringArray(value) {
3130
4085
  return Array.isArray(value) ? value.map(String) : [];
3131
4086
  }
3132
4087
  function extractorDescriptorRecord(value) {
3133
- if (!isRecord3(value)) return {};
4088
+ if (!isRecord4(value)) return {};
3134
4089
  return Object.fromEntries(
3135
4090
  Object.entries(value).flatMap(([key, descriptor]) => {
3136
- if (!isRecord3(descriptor)) return [];
4091
+ if (!isRecord4(descriptor)) return [];
3137
4092
  const paths = stringArray(descriptor.paths).map((path) => path.trim()).filter(Boolean);
3138
4093
  if (paths.length === 0) return [];
3139
4094
  const transforms = stringArray(descriptor.transforms).map((transform) => transform.trim()).filter(Boolean);
@@ -3154,14 +4109,14 @@ function extractorDescriptorRecord(value) {
3154
4109
  function toolExecutionEnvelopeToResult(fallbackToolId, response) {
3155
4110
  const raw = response.toolResponse?.raw ?? null;
3156
4111
  const meta = response.toolResponse?.meta;
3157
- const metadata = isRecord3(response._metadata) ? response._metadata.tool : null;
3158
- const toolMetadata = isRecord3(metadata) ? metadata : {};
4112
+ const metadata = isRecord4(response._metadata) ? response._metadata.tool : null;
4113
+ const toolMetadata = isRecord4(metadata) ? metadata : {};
3159
4114
  return createToolExecuteResult({
3160
4115
  status: typeof response.status === "string" ? response.status : "completed",
3161
4116
  jobId: typeof response.job_id === "string" ? response.job_id : void 0,
3162
4117
  result: {
3163
4118
  data: raw,
3164
- ...isRecord3(meta) ? { meta } : {}
4119
+ ...isRecord4(meta) ? { meta } : {}
3165
4120
  },
3166
4121
  metadata: {
3167
4122
  toolId: typeof toolMetadata.toolId === "string" ? toolMetadata.toolId : fallbackToolId,
@@ -3175,7 +4130,7 @@ function toolExecutionEnvelopeToResult(fallbackToolId, response) {
3175
4130
  cached: false,
3176
4131
  source: "live"
3177
4132
  },
3178
- meta: isRecord3(response.meta) ? response.meta : void 0
4133
+ meta: isRecord4(response.meta) ? response.meta : void 0
3179
4134
  });
3180
4135
  }
3181
4136
  function defineInput(schema) {
@@ -3477,6 +4432,7 @@ function extractSummaryFields(payload) {
3477
4432
  PLAY_BOOTSTRAP_TEMPLATES,
3478
4433
  PROD_URL,
3479
4434
  RateLimitError,
4435
+ RunObserveTransportUnavailableError,
3480
4436
  SDK_API_CONTRACT,
3481
4437
  SDK_VERSION,
3482
4438
  defineInput,