@workflow/web-shared 5.0.0-beta.1 → 5.0.0-beta.3
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/README.md +1 -1
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/event-list-view.d.ts +3 -1
- package/dist/components/event-list-view.d.ts.map +1 -1
- package/dist/components/event-list-view.js +53 -53
- package/dist/components/event-list-view.js.map +1 -1
- package/dist/components/hook-actions.js.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/new-trace-viewer/components/copy-button.d.ts +9 -0
- package/dist/components/new-trace-viewer/components/copy-button.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/copy-button.js +27 -0
- package/dist/components/new-trace-viewer/components/copy-button.js.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts +8 -0
- package/dist/components/new-trace-viewer/components/event-list.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/event-list.js +35 -0
- package/dist/components/new-trace-viewer/components/event-list.js.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts +14 -0
- package/dist/components/new-trace-viewer/components/split-pane.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/split-pane.js +100 -0
- package/dist/components/new-trace-viewer/components/split-pane.js.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts +32 -0
- package/dist/components/new-trace-viewer/components/timeline.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/components/timeline.js +134 -0
- package/dist/components/new-trace-viewer/components/timeline.js.map +1 -0
- package/dist/components/new-trace-viewer/context.d.ts +15 -0
- package/dist/components/new-trace-viewer/context.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/context.js +49 -0
- package/dist/components/new-trace-viewer/context.js.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts +10 -0
- package/dist/components/new-trace-viewer/detail-panel.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/detail-panel.js +11 -0
- package/dist/components/new-trace-viewer/detail-panel.js.map +1 -0
- package/dist/components/new-trace-viewer/icons.d.ts +6 -0
- package/dist/components/new-trace-viewer/icons.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/icons.js +15 -0
- package/dist/components/new-trace-viewer/icons.js.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts +8 -0
- package/dist/components/new-trace-viewer/trace-viewer.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/trace-viewer.js +329 -0
- package/dist/components/new-trace-viewer/trace-viewer.js.map +1 -0
- package/dist/components/new-trace-viewer/types.d.ts +1 -0
- package/dist/components/new-trace-viewer/types.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/types.js +2 -0
- package/dist/components/new-trace-viewer/types.js.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts +1 -0
- package/dist/components/new-trace-viewer/util/constants.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/util/constants.js +2 -0
- package/dist/components/new-trace-viewer/util/constants.js.map +1 -0
- package/dist/components/new-trace-viewer/utils.d.ts +47 -0
- package/dist/components/new-trace-viewer/utils.d.ts.map +1 -0
- package/dist/components/new-trace-viewer/utils.js +358 -0
- package/dist/components/new-trace-viewer/utils.js.map +1 -0
- package/dist/components/run-trace-view.d.ts +2 -1
- package/dist/components/run-trace-view.d.ts.map +1 -1
- package/dist/components/run-trace-view.js +2 -2
- package/dist/components/run-trace-view.js.map +1 -1
- package/dist/components/sidebar/attribute-panel.d.ts +7 -1
- package/dist/components/sidebar/attribute-panel.d.ts.map +1 -1
- package/dist/components/sidebar/attribute-panel.js +76 -47
- package/dist/components/sidebar/attribute-panel.js.map +1 -1
- package/dist/components/sidebar/conversation-view.js.map +1 -1
- package/dist/components/sidebar/copyable-data-block.js +1 -1
- package/dist/components/sidebar/copyable-data-block.js.map +1 -1
- package/dist/components/sidebar/detail-card.d.ts.map +1 -1
- package/dist/components/sidebar/detail-card.js +1 -1
- package/dist/components/sidebar/detail-card.js.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts +3 -1
- package/dist/components/sidebar/entity-detail-panel.d.ts.map +1 -1
- package/dist/components/sidebar/entity-detail-panel.js +16 -36
- package/dist/components/sidebar/entity-detail-panel.js.map +1 -1
- package/dist/components/sidebar/events-list.d.ts.map +1 -1
- package/dist/components/sidebar/events-list.js +2 -2
- package/dist/components/sidebar/events-list.js.map +1 -1
- package/dist/components/sidebar/resolve-hook-modal.js.map +1 -1
- package/dist/components/sidebar/sidebar-data-context.d.ts +29 -0
- package/dist/components/sidebar/sidebar-data-context.d.ts.map +1 -0
- package/dist/components/sidebar/sidebar-data-context.js +19 -0
- package/dist/components/sidebar/sidebar-data-context.js.map +1 -0
- package/dist/components/stream-viewer.d.ts +1 -1
- package/dist/components/stream-viewer.d.ts.map +1 -1
- package/dist/components/stream-viewer.js +2 -26
- package/dist/components/stream-viewer.js.map +1 -1
- package/dist/components/trace-viewer/components/map.js.map +1 -1
- package/dist/components/trace-viewer/components/markers.js.map +1 -1
- package/dist/components/trace-viewer/components/node.js.map +1 -1
- package/dist/components/trace-viewer/components/search-input.js.map +1 -1
- package/dist/components/trace-viewer/components/search.js.map +1 -1
- package/dist/components/trace-viewer/components/span-content.js.map +1 -1
- package/dist/components/trace-viewer/components/span-detail-panel.js.map +1 -1
- package/dist/components/trace-viewer/components/span-segments.js.map +1 -1
- package/dist/components/trace-viewer/components/span-strategies.js.map +1 -1
- package/dist/components/trace-viewer/components/ui.js.map +1 -1
- package/dist/components/trace-viewer/components/zoom-button.js.map +1 -1
- package/dist/components/trace-viewer/components/zoom-icons.js.map +1 -1
- package/dist/components/trace-viewer/context.js.map +1 -1
- package/dist/components/trace-viewer/index.js.map +1 -1
- package/dist/components/trace-viewer/trace-viewer.js.map +1 -1
- package/dist/components/trace-viewer/types.js.map +1 -1
- package/dist/components/trace-viewer/util/constants.js.map +1 -1
- package/dist/components/trace-viewer/util/scrollbar-width.js.map +1 -1
- package/dist/components/trace-viewer/util/timing.js.map +1 -1
- package/dist/components/trace-viewer/util/tree.js.map +1 -1
- package/dist/components/trace-viewer/util/use-immediate-style.js.map +1 -1
- package/dist/components/trace-viewer/util/use-streaming-spans.js.map +1 -1
- package/dist/components/trace-viewer/util/use-trackpad-zoom.js.map +1 -1
- package/dist/components/trace-viewer/worker.js.map +1 -1
- package/dist/components/trace-viewer-new.d.ts +10 -0
- package/dist/components/trace-viewer-new.d.ts.map +1 -0
- package/dist/components/trace-viewer-new.js +23 -0
- package/dist/components/trace-viewer-new.js.map +1 -0
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/data-inspector.d.ts +38 -1
- package/dist/components/ui/data-inspector.d.ts.map +1 -1
- package/dist/components/ui/data-inspector.js +211 -21
- package/dist/components/ui/data-inspector.js.map +1 -1
- package/dist/components/ui/decrypt-button.js.map +1 -1
- package/dist/components/ui/error-card.js.map +1 -1
- package/dist/components/ui/error-stack-block.js.map +1 -1
- package/dist/components/ui/inspector-theme.d.ts +9 -10
- package/dist/components/ui/inspector-theme.d.ts.map +1 -1
- package/dist/components/ui/inspector-theme.js +38 -71
- package/dist/components/ui/inspector-theme.js.map +1 -1
- package/dist/components/ui/load-more-button.js.map +1 -1
- package/dist/components/ui/menu-dropdown.js.map +1 -1
- package/dist/components/ui/skeleton.js.map +1 -1
- package/dist/components/ui/spinner.js.map +1 -1
- package/dist/components/ui/timestamp-tooltip.js.map +1 -1
- package/dist/components/workflow-trace-view.d.ts +3 -1
- package/dist/components/workflow-trace-view.d.ts.map +1 -1
- package/dist/components/workflow-trace-view.js +9 -4
- package/dist/components/workflow-trace-view.js.map +1 -1
- package/dist/components/workflow-traces/event-colors.js.map +1 -1
- package/dist/components/workflow-traces/trace-colors.js.map +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js +1 -1
- package/dist/components/workflow-traces/trace-span-construction.js.map +1 -1
- package/dist/components/workflow-traces/trace-time-utils.js.map +1 -1
- package/dist/hooks/use-dark-mode.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/event-analysis.js.map +1 -1
- package/dist/lib/event-materialization.js.map +1 -1
- package/dist/lib/hydration.d.ts +8 -1
- package/dist/lib/hydration.d.ts.map +1 -1
- package/dist/lib/hydration.js +32 -2
- package/dist/lib/hydration.js.map +1 -1
- package/dist/lib/stream-display.d.ts +18 -0
- package/dist/lib/stream-display.d.ts.map +1 -0
- package/dist/lib/stream-display.js +30 -0
- package/dist/lib/stream-display.js.map +1 -0
- package/dist/lib/toast.js.map +1 -1
- package/dist/lib/trace-builder.js.map +1 -1
- package/dist/lib/utils.d.ts +7 -9
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +17 -22
- package/dist/lib/utils.js.map +1 -1
- package/dist/styles.css +470 -1
- package/package.json +4 -3
- package/src/components/event-list-view.tsx +181 -173
- package/src/components/index.ts +11 -1
- package/src/components/new-trace-viewer/components/copy-button.tsx +68 -0
- package/src/components/new-trace-viewer/components/event-list.tsx +109 -0
- package/src/components/new-trace-viewer/components/split-pane.tsx +186 -0
- package/src/components/new-trace-viewer/components/timeline.tsx +378 -0
- package/src/components/new-trace-viewer/context.tsx +94 -0
- package/src/components/new-trace-viewer/detail-panel.tsx +57 -0
- package/src/components/new-trace-viewer/icons.tsx +79 -0
- package/src/components/new-trace-viewer/trace-viewer.tsx +585 -0
- package/src/components/new-trace-viewer/types.ts +0 -0
- package/src/components/new-trace-viewer/util/constants.ts +0 -0
- package/src/components/new-trace-viewer/utils.ts +524 -0
- package/src/components/run-trace-view.tsx +3 -0
- package/src/components/sidebar/attribute-panel.tsx +200 -138
- package/src/components/sidebar/copyable-data-block.tsx +1 -1
- package/src/components/sidebar/detail-card.tsx +1 -19
- package/src/components/sidebar/entity-detail-panel.tsx +91 -147
- package/src/components/sidebar/events-list.tsx +9 -6
- package/src/components/sidebar/sidebar-data-context.tsx +62 -0
- package/src/components/stream-viewer.tsx +18 -40
- package/src/components/trace-viewer-new.tsx +49 -0
- package/src/components/ui/data-inspector.tsx +436 -31
- package/src/components/ui/inspector-theme.ts +39 -81
- package/src/components/workflow-trace-view.tsx +12 -2
- package/src/components/workflow-traces/trace-span-construction.ts +1 -1
- package/src/index.ts +2 -0
- package/src/lib/hydration.ts +37 -0
- package/src/lib/stream-display.ts +48 -0
- package/src/lib/utils.ts +18 -23
- package/src/styles.css +470 -1
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { Lock } from 'lucide-react';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
createContext,
|
|
14
|
+
useContext,
|
|
15
|
+
useEffect,
|
|
16
|
+
useMemo,
|
|
17
|
+
useRef,
|
|
18
|
+
useState,
|
|
19
|
+
} from 'react';
|
|
13
20
|
import {
|
|
14
21
|
ObjectInspector,
|
|
15
22
|
ObjectLabel,
|
|
@@ -19,6 +26,11 @@ import {
|
|
|
19
26
|
} from 'react-inspector';
|
|
20
27
|
import { useDarkMode } from '../../hooks/use-dark-mode';
|
|
21
28
|
import { ENCRYPTED_DISPLAY_NAME } from '../../lib/hydration';
|
|
29
|
+
import {
|
|
30
|
+
type DecodedStreamChunkSource,
|
|
31
|
+
type FormattedStreamChunkDisplay,
|
|
32
|
+
formatArrayBufferViewForDisplay,
|
|
33
|
+
} from '../../lib/stream-display';
|
|
22
34
|
import {
|
|
23
35
|
type InspectorThemeExtended,
|
|
24
36
|
inspectorThemeDark,
|
|
@@ -26,6 +38,7 @@ import {
|
|
|
26
38
|
inspectorThemeExtendedLight,
|
|
27
39
|
inspectorThemeLight,
|
|
28
40
|
} from './inspector-theme';
|
|
41
|
+
import { Spinner } from './spinner';
|
|
29
42
|
|
|
30
43
|
// ---------------------------------------------------------------------------
|
|
31
44
|
// StreamRef / ClassInstanceRef type detection
|
|
@@ -34,19 +47,62 @@ import {
|
|
|
34
47
|
|
|
35
48
|
const STREAM_REF_TYPE = '__workflow_stream_ref__';
|
|
36
49
|
const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';
|
|
50
|
+
const RUN_REF_TYPE = '__workflow_run_ref__';
|
|
51
|
+
const BYTES_DISPLAY_TYPE = '__workflow_bytes_display__';
|
|
37
52
|
|
|
38
53
|
interface StreamRef {
|
|
39
54
|
__type: typeof STREAM_REF_TYPE;
|
|
40
55
|
streamId: string;
|
|
41
56
|
}
|
|
42
57
|
|
|
58
|
+
interface RunRef {
|
|
59
|
+
__type: typeof RUN_REF_TYPE;
|
|
60
|
+
runId: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface BytesDisplay {
|
|
64
|
+
__type: typeof BYTES_DISPLAY_TYPE;
|
|
65
|
+
text: string;
|
|
66
|
+
decodedFrom?: DecodedStreamChunkSource;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deserializeChunkText(text: string): string {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(text);
|
|
72
|
+
if (typeof parsed === 'string') {
|
|
73
|
+
return parsed;
|
|
74
|
+
}
|
|
75
|
+
return JSON.stringify(parsed, null, 2);
|
|
76
|
+
} catch {
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseChunkData(text: string): unknown {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text);
|
|
84
|
+
} catch {
|
|
85
|
+
return text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
43
89
|
function isStreamRef(value: unknown): value is StreamRef {
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
if (value === null || typeof value !== 'object') return false;
|
|
91
|
+
// Check both enumerable and non-enumerable __type (opaque refs use non-enumerable)
|
|
92
|
+
const desc = Object.getOwnPropertyDescriptor(value, '__type');
|
|
93
|
+
return desc?.value === STREAM_REF_TYPE;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isRunRef(value: unknown): value is RunRef {
|
|
97
|
+
if (value === null || typeof value !== 'object') return false;
|
|
98
|
+
const desc = Object.getOwnPropertyDescriptor(value, '__type');
|
|
99
|
+
return desc?.value === RUN_REF_TYPE;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isBytesDisplay(value: unknown): value is BytesDisplay {
|
|
103
|
+
if (value === null || typeof value !== 'object') return false;
|
|
104
|
+
const desc = Object.getOwnPropertyDescriptor(value, '__type');
|
|
105
|
+
return desc?.value === BYTES_DISPLAY_TYPE;
|
|
50
106
|
}
|
|
51
107
|
|
|
52
108
|
function isClassInstanceRef(value: unknown): value is {
|
|
@@ -75,18 +131,89 @@ export const StreamClickContext = createContext<
|
|
|
75
131
|
((streamId: string) => void) | undefined
|
|
76
132
|
>(undefined);
|
|
77
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Context for passing a decrypt handler down to DataInspector instances.
|
|
136
|
+
* When provided, encrypted markers become clickable buttons that trigger decryption.
|
|
137
|
+
*/
|
|
138
|
+
export type DecryptClickContextValue = {
|
|
139
|
+
onDecrypt: () => void;
|
|
140
|
+
isDecrypting: boolean;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const DecryptClickContext = createContext<
|
|
144
|
+
DecryptClickContextValue | undefined
|
|
145
|
+
>(undefined);
|
|
146
|
+
|
|
147
|
+
export const RunClickContext = createContext<
|
|
148
|
+
((runId: string) => void) | undefined
|
|
149
|
+
>(undefined);
|
|
150
|
+
|
|
151
|
+
function EncryptedInlineLabel() {
|
|
152
|
+
const ctx = useContext(DecryptClickContext);
|
|
153
|
+
if (ctx) {
|
|
154
|
+
return (
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] cursor-pointer"
|
|
158
|
+
style={{
|
|
159
|
+
backgroundColor: 'var(--ds-gray-100)',
|
|
160
|
+
color: 'var(--ds-gray-700)',
|
|
161
|
+
border: '1px solid var(--ds-gray-400)',
|
|
162
|
+
fontStyle: 'italic',
|
|
163
|
+
opacity: ctx.isDecrypting ? 0.6 : 1,
|
|
164
|
+
}}
|
|
165
|
+
disabled={ctx.isDecrypting}
|
|
166
|
+
onClick={(e) => {
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
ctx.onDecrypt();
|
|
169
|
+
}}
|
|
170
|
+
title="Click to decrypt"
|
|
171
|
+
>
|
|
172
|
+
{ctx.isDecrypting ? (
|
|
173
|
+
<Spinner size={12} />
|
|
174
|
+
) : (
|
|
175
|
+
<Lock
|
|
176
|
+
className="h-3 w-3"
|
|
177
|
+
style={{ display: 'inline', flexShrink: 0 }}
|
|
178
|
+
/>
|
|
179
|
+
)}
|
|
180
|
+
<span>{ctx.isDecrypting ? 'Decrypting…' : 'Decrypt'}</span>
|
|
181
|
+
</button>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return (
|
|
185
|
+
<span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
|
|
186
|
+
<Lock
|
|
187
|
+
className="h-3 w-3"
|
|
188
|
+
style={{
|
|
189
|
+
display: 'inline',
|
|
190
|
+
verticalAlign: 'middle',
|
|
191
|
+
marginRight: '3px',
|
|
192
|
+
marginTop: '-1px',
|
|
193
|
+
}}
|
|
194
|
+
/>
|
|
195
|
+
Encrypted
|
|
196
|
+
</span>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
78
199
|
function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
|
|
79
200
|
const onStreamClick = useContext(StreamClickContext);
|
|
201
|
+
const [hovered, setHovered] = useState(false);
|
|
80
202
|
return (
|
|
81
203
|
<button
|
|
82
204
|
type="button"
|
|
83
|
-
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer"
|
|
205
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer underline decoration-transparent transition-colors"
|
|
84
206
|
style={{
|
|
85
|
-
backgroundColor: 'var(--ds-blue-100)',
|
|
86
|
-
color: 'var(--ds-blue-
|
|
207
|
+
backgroundColor: hovered ? 'var(--ds-blue-200)' : 'var(--ds-blue-100)',
|
|
208
|
+
color: 'var(--ds-blue-900)',
|
|
87
209
|
border: '1px solid var(--ds-blue-300)',
|
|
88
210
|
}}
|
|
89
|
-
|
|
211
|
+
onMouseEnter={() => setHovered(true)}
|
|
212
|
+
onMouseLeave={() => setHovered(false)}
|
|
213
|
+
onClick={(e) => {
|
|
214
|
+
e.stopPropagation();
|
|
215
|
+
onStreamClick?.(streamRef.streamId);
|
|
216
|
+
}}
|
|
90
217
|
title={`View stream: ${streamRef.streamId}`}
|
|
91
218
|
>
|
|
92
219
|
<span>📡</span>
|
|
@@ -95,6 +222,33 @@ function StreamRefInline({ streamRef }: { streamRef: StreamRef }) {
|
|
|
95
222
|
);
|
|
96
223
|
}
|
|
97
224
|
|
|
225
|
+
function RunRefInline({ runRef }: { runRef: RunRef }) {
|
|
226
|
+
const onRunClick = useContext(RunClickContext);
|
|
227
|
+
const [hovered, setHovered] = useState(false);
|
|
228
|
+
return (
|
|
229
|
+
<button
|
|
230
|
+
type="button"
|
|
231
|
+
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-mono cursor-pointer underline decoration-transparent transition-colors"
|
|
232
|
+
style={{
|
|
233
|
+
backgroundColor: hovered
|
|
234
|
+
? 'var(--ds-purple-200)'
|
|
235
|
+
: 'var(--ds-purple-100)',
|
|
236
|
+
color: 'var(--ds-purple-900)',
|
|
237
|
+
border: '1px solid var(--ds-purple-300)',
|
|
238
|
+
}}
|
|
239
|
+
onMouseEnter={() => setHovered(true)}
|
|
240
|
+
onMouseLeave={() => setHovered(false)}
|
|
241
|
+
onClick={(e) => {
|
|
242
|
+
e.stopPropagation();
|
|
243
|
+
onRunClick?.(runRef.runId);
|
|
244
|
+
}}
|
|
245
|
+
title={`View run: ${runRef.runId}`}
|
|
246
|
+
>
|
|
247
|
+
<span>{runRef.runId}</span>
|
|
248
|
+
</button>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
98
252
|
// ---------------------------------------------------------------------------
|
|
99
253
|
// Extended theme context (for colors react-inspector doesn't support natively)
|
|
100
254
|
// ---------------------------------------------------------------------------
|
|
@@ -103,6 +257,147 @@ const ExtendedThemeContext = createContext<InspectorThemeExtended>(
|
|
|
103
257
|
inspectorThemeExtendedLight
|
|
104
258
|
);
|
|
105
259
|
|
|
260
|
+
function DecodedBytesChunk({
|
|
261
|
+
decodedText,
|
|
262
|
+
source,
|
|
263
|
+
}: {
|
|
264
|
+
decodedText: string;
|
|
265
|
+
source: DecodedStreamChunkSource;
|
|
266
|
+
}) {
|
|
267
|
+
const [selectedView, setSelectedView] = useState<'decoded' | 'bytes'>(
|
|
268
|
+
'decoded'
|
|
269
|
+
);
|
|
270
|
+
const parsed = parseChunkData(decodedText);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<div className="min-w-0">
|
|
274
|
+
{selectedView === 'decoded' ? (
|
|
275
|
+
<div className="min-w-0">
|
|
276
|
+
{typeof parsed === 'string' ? (
|
|
277
|
+
<span
|
|
278
|
+
className="whitespace-pre-wrap break-words"
|
|
279
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
280
|
+
>
|
|
281
|
+
{deserializeChunkText(parsed)}
|
|
282
|
+
</span>
|
|
283
|
+
) : (
|
|
284
|
+
<DataInspector data={parsed} expandLevel={1} />
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
) : (
|
|
288
|
+
<DecodedBytesInspector decodedText={decodedText} source={source} />
|
|
289
|
+
)}
|
|
290
|
+
<div className="mt-2 flex">
|
|
291
|
+
<div
|
|
292
|
+
className="inline-flex overflow-hidden rounded border"
|
|
293
|
+
style={{ borderColor: 'var(--ds-gray-400)' }}
|
|
294
|
+
title={`${source.type} decoded as ${source.encoding.toUpperCase()} text. Switch to Bytes to inspect the summarized raw value.`}
|
|
295
|
+
>
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
className="h-5 px-1.5 text-[10px] font-medium"
|
|
299
|
+
style={{
|
|
300
|
+
backgroundColor:
|
|
301
|
+
selectedView === 'decoded'
|
|
302
|
+
? 'var(--ds-gray-200)'
|
|
303
|
+
: 'var(--ds-gray-100)',
|
|
304
|
+
color: 'var(--ds-gray-900)',
|
|
305
|
+
}}
|
|
306
|
+
onClick={() => setSelectedView('decoded')}
|
|
307
|
+
aria-pressed={selectedView === 'decoded'}
|
|
308
|
+
aria-label="Show decoded text"
|
|
309
|
+
>
|
|
310
|
+
Decoded
|
|
311
|
+
</button>
|
|
312
|
+
<button
|
|
313
|
+
type="button"
|
|
314
|
+
className="h-5 border-l px-1.5 text-[10px] font-medium"
|
|
315
|
+
style={{
|
|
316
|
+
borderColor: 'var(--ds-gray-400)',
|
|
317
|
+
backgroundColor:
|
|
318
|
+
selectedView === 'bytes'
|
|
319
|
+
? 'var(--ds-gray-200)'
|
|
320
|
+
: 'var(--ds-gray-100)',
|
|
321
|
+
color: 'var(--ds-gray-900)',
|
|
322
|
+
}}
|
|
323
|
+
onClick={() => setSelectedView('bytes')}
|
|
324
|
+
aria-pressed={selectedView === 'bytes'}
|
|
325
|
+
aria-label="Show raw bytes summary"
|
|
326
|
+
>
|
|
327
|
+
Bytes
|
|
328
|
+
</button>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function DecodedBytesInspector({
|
|
336
|
+
decodedText,
|
|
337
|
+
source,
|
|
338
|
+
}: {
|
|
339
|
+
decodedText: string;
|
|
340
|
+
source: DecodedStreamChunkSource;
|
|
341
|
+
}) {
|
|
342
|
+
const [expanded, setExpanded] = useState(false);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className="font-mono">
|
|
346
|
+
<button
|
|
347
|
+
type="button"
|
|
348
|
+
className="flex max-w-full items-start gap-1 text-left"
|
|
349
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
350
|
+
onClick={() => setExpanded((value) => !value)}
|
|
351
|
+
title={`${source.type} decoded as ${source.encoding.toUpperCase()} text`}
|
|
352
|
+
>
|
|
353
|
+
<span className="select-none" style={{ color: 'var(--ds-gray-700)' }}>
|
|
354
|
+
{expanded ? '▼' : '▶'}
|
|
355
|
+
</span>
|
|
356
|
+
<span className="min-w-0 break-words">{source.rawSummary}</span>
|
|
357
|
+
</button>
|
|
358
|
+
{expanded && (
|
|
359
|
+
<div className="mt-1 pl-5">
|
|
360
|
+
<span style={{ color: 'var(--ds-gray-700)' }}>decoded: </span>
|
|
361
|
+
<span
|
|
362
|
+
className="whitespace-pre-wrap break-words"
|
|
363
|
+
style={{ color: 'var(--ds-green-900)' }}
|
|
364
|
+
>
|
|
365
|
+
{JSON.stringify(decodedText)}
|
|
366
|
+
</span>
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function BytesDisplayLabel({
|
|
374
|
+
name,
|
|
375
|
+
display,
|
|
376
|
+
}: {
|
|
377
|
+
name?: string;
|
|
378
|
+
display: BytesDisplay;
|
|
379
|
+
}) {
|
|
380
|
+
return (
|
|
381
|
+
<div className="inline-block min-w-0 align-top">
|
|
382
|
+
{name != null && <ObjectName name={name} />}
|
|
383
|
+
{name != null && <span>: </span>}
|
|
384
|
+
{display.decodedFrom ? (
|
|
385
|
+
<DecodedBytesChunk
|
|
386
|
+
decodedText={display.text}
|
|
387
|
+
source={display.decodedFrom}
|
|
388
|
+
/>
|
|
389
|
+
) : (
|
|
390
|
+
<span
|
|
391
|
+
className="whitespace-pre-wrap break-words"
|
|
392
|
+
style={{ color: 'var(--ds-gray-1000)' }}
|
|
393
|
+
>
|
|
394
|
+
{display.text}
|
|
395
|
+
</span>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
106
401
|
// ---------------------------------------------------------------------------
|
|
107
402
|
// Custom nodeRenderer
|
|
108
403
|
// ---------------------------------------------------------------------------
|
|
@@ -132,26 +427,17 @@ function NodeRenderer({
|
|
|
132
427
|
}) {
|
|
133
428
|
const extendedTheme = useContext(ExtendedThemeContext);
|
|
134
429
|
|
|
135
|
-
|
|
430
|
+
if (isBytesDisplay(data)) {
|
|
431
|
+
return <BytesDisplayLabel name={name} display={data} />;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Encrypted marker → flat label with Lock icon, clickable when onDecrypt is available
|
|
136
435
|
if (
|
|
137
436
|
data !== null &&
|
|
138
437
|
typeof data === 'object' &&
|
|
139
438
|
data.constructor?.name === ENCRYPTED_DISPLAY_NAME
|
|
140
439
|
) {
|
|
141
|
-
const label =
|
|
142
|
-
<span style={{ color: 'var(--ds-gray-600)', fontStyle: 'italic' }}>
|
|
143
|
-
<Lock
|
|
144
|
-
className="h-3 w-3"
|
|
145
|
-
style={{
|
|
146
|
-
display: 'inline',
|
|
147
|
-
verticalAlign: 'middle',
|
|
148
|
-
marginRight: '3px',
|
|
149
|
-
marginTop: '-1px',
|
|
150
|
-
}}
|
|
151
|
-
/>
|
|
152
|
-
Encrypted
|
|
153
|
-
</span>
|
|
154
|
-
);
|
|
440
|
+
const label = <EncryptedInlineLabel />;
|
|
155
441
|
if (depth === 0) {
|
|
156
442
|
return label;
|
|
157
443
|
}
|
|
@@ -175,6 +461,17 @@ function NodeRenderer({
|
|
|
175
461
|
);
|
|
176
462
|
}
|
|
177
463
|
|
|
464
|
+
// RunRef → inline clickable badge linking to the target run
|
|
465
|
+
if (isRunRef(data)) {
|
|
466
|
+
return (
|
|
467
|
+
<span>
|
|
468
|
+
{name != null && <ObjectName name={name} />}
|
|
469
|
+
{name != null && <span>: </span>}
|
|
470
|
+
<RunRefInline runRef={data} />
|
|
471
|
+
</span>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
178
475
|
// ClassInstanceRef → show className as type, data as the inspectable value
|
|
179
476
|
if (isClassInstanceRef(data)) {
|
|
180
477
|
if (depth === 0) {
|
|
@@ -225,6 +522,76 @@ function NodeRenderer({
|
|
|
225
522
|
// Public component
|
|
226
523
|
// ---------------------------------------------------------------------------
|
|
227
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Create a non-expandable wrapper that carries ref data as non-enumerable
|
|
527
|
+
* properties. ObjectInspector won't render children for objects with no
|
|
528
|
+
* enumerable keys, but our NodeRenderer can still detect them.
|
|
529
|
+
*/
|
|
530
|
+
function makeOpaqueRef(ref: Record<string, unknown>): unknown {
|
|
531
|
+
const opaque = Object.create(null);
|
|
532
|
+
for (const [key, value] of Object.entries(ref)) {
|
|
533
|
+
Object.defineProperty(opaque, key, { value, enumerable: false });
|
|
534
|
+
}
|
|
535
|
+
return opaque;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function makeBytesDisplay(display: FormattedStreamChunkDisplay): unknown {
|
|
539
|
+
const opaque = Object.create(null);
|
|
540
|
+
Object.defineProperty(opaque, '__type', {
|
|
541
|
+
value: BYTES_DISPLAY_TYPE,
|
|
542
|
+
enumerable: false,
|
|
543
|
+
});
|
|
544
|
+
Object.defineProperty(opaque, 'text', {
|
|
545
|
+
value: display.text,
|
|
546
|
+
enumerable: false,
|
|
547
|
+
});
|
|
548
|
+
Object.defineProperty(opaque, 'decodedFrom', {
|
|
549
|
+
value: display.decodedFrom,
|
|
550
|
+
enumerable: false,
|
|
551
|
+
});
|
|
552
|
+
return opaque;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Recursively walk data and replace RunRef/StreamRef/typed array objects with
|
|
557
|
+
* non-expandable versions so ObjectInspector doesn't show their internals.
|
|
558
|
+
* Only recurses into plain objects and arrays to avoid stripping class
|
|
559
|
+
* instances (Date, Error, URL, Headers, etc.) that have their own rendering in
|
|
560
|
+
* NodeRenderer. Map and Set containers are preserved while their contents are
|
|
561
|
+
* prepared for display.
|
|
562
|
+
*
|
|
563
|
+
* Exported for testing the typed-array detection path used by hydrated
|
|
564
|
+
* AI agent stream chunks (e.g. `{ delta: new Uint8Array(...) }`).
|
|
565
|
+
*/
|
|
566
|
+
export function collapseRefs(data: unknown): unknown {
|
|
567
|
+
if (data === null || typeof data !== 'object') return data;
|
|
568
|
+
if (ArrayBuffer.isView(data) && !(data instanceof DataView)) {
|
|
569
|
+
return makeBytesDisplay(formatArrayBufferViewForDisplay(data));
|
|
570
|
+
}
|
|
571
|
+
if (isRunRef(data) || isStreamRef(data))
|
|
572
|
+
return makeOpaqueRef(data as unknown as Record<string, unknown>);
|
|
573
|
+
if (Array.isArray(data)) return data.map(collapseRefs);
|
|
574
|
+
if (data instanceof Map) {
|
|
575
|
+
return new Map(
|
|
576
|
+
Array.from(data.entries(), ([key, value]) => [
|
|
577
|
+
collapseRefs(key),
|
|
578
|
+
collapseRefs(value),
|
|
579
|
+
])
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
if (data instanceof Set) {
|
|
583
|
+
return new Set(Array.from(data.values(), collapseRefs));
|
|
584
|
+
}
|
|
585
|
+
// Only recurse into plain objects — leave class instances untouched
|
|
586
|
+
const proto = Object.getPrototypeOf(data);
|
|
587
|
+
if (proto !== Object.prototype && proto !== null) return data;
|
|
588
|
+
const result: Record<string, unknown> = {};
|
|
589
|
+
for (const [key, value] of Object.entries(data)) {
|
|
590
|
+
result[key] = collapseRefs(value);
|
|
591
|
+
}
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
|
|
228
595
|
export interface DataInspectorProps {
|
|
229
596
|
/** The data to inspect */
|
|
230
597
|
data: unknown;
|
|
@@ -234,6 +601,12 @@ export interface DataInspectorProps {
|
|
|
234
601
|
name?: string;
|
|
235
602
|
/** Callback when a stream reference is clicked */
|
|
236
603
|
onStreamClick?: (streamId: string) => void;
|
|
604
|
+
/** Callback when a run reference is clicked */
|
|
605
|
+
onRunClick?: (runId: string) => void;
|
|
606
|
+
/** Callback when an encrypted marker is clicked (triggers decryption) */
|
|
607
|
+
onDecrypt?: () => void;
|
|
608
|
+
/** Whether decryption is currently in progress */
|
|
609
|
+
isDecrypting?: boolean;
|
|
237
610
|
}
|
|
238
611
|
|
|
239
612
|
export function DataInspector({
|
|
@@ -241,8 +614,12 @@ export function DataInspector({
|
|
|
241
614
|
expandLevel = 2,
|
|
242
615
|
name,
|
|
243
616
|
onStreamClick,
|
|
617
|
+
onRunClick,
|
|
618
|
+
onDecrypt,
|
|
619
|
+
isDecrypting = false,
|
|
244
620
|
}: DataInspectorProps) {
|
|
245
|
-
const
|
|
621
|
+
const collapsedData = useMemo(() => collapseRefs(data), [data]);
|
|
622
|
+
const stableData = useStableInspectorData(collapsedData);
|
|
246
623
|
const [initialExpandLevel, setInitialExpandLevel] = useState(expandLevel);
|
|
247
624
|
const isDark = useDarkMode();
|
|
248
625
|
const extendedTheme = isDark
|
|
@@ -269,16 +646,31 @@ export function DataInspector({
|
|
|
269
646
|
</ExtendedThemeContext.Provider>
|
|
270
647
|
);
|
|
271
648
|
|
|
272
|
-
|
|
649
|
+
let wrapped = content;
|
|
650
|
+
|
|
273
651
|
if (onStreamClick) {
|
|
274
|
-
|
|
652
|
+
wrapped = (
|
|
275
653
|
<StreamClickContext.Provider value={onStreamClick}>
|
|
276
|
-
{
|
|
654
|
+
{wrapped}
|
|
277
655
|
</StreamClickContext.Provider>
|
|
278
656
|
);
|
|
279
657
|
}
|
|
658
|
+
if (onRunClick) {
|
|
659
|
+
wrapped = (
|
|
660
|
+
<RunClickContext.Provider value={onRunClick}>
|
|
661
|
+
{wrapped}
|
|
662
|
+
</RunClickContext.Provider>
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
if (onDecrypt) {
|
|
666
|
+
wrapped = (
|
|
667
|
+
<DecryptClickContext.Provider value={{ onDecrypt, isDecrypting }}>
|
|
668
|
+
{wrapped}
|
|
669
|
+
</DecryptClickContext.Provider>
|
|
670
|
+
);
|
|
671
|
+
}
|
|
280
672
|
|
|
281
|
-
return
|
|
673
|
+
return wrapped;
|
|
282
674
|
}
|
|
283
675
|
|
|
284
676
|
function useStableInspectorData<T>(next: T): T {
|
|
@@ -293,9 +685,22 @@ function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
|
293
685
|
return typeof value === 'object' && value !== null;
|
|
294
686
|
}
|
|
295
687
|
|
|
688
|
+
function isSameBytesDisplay(a: BytesDisplay, b: BytesDisplay): boolean {
|
|
689
|
+
return (
|
|
690
|
+
a.text === b.text &&
|
|
691
|
+
a.decodedFrom?.type === b.decodedFrom?.type &&
|
|
692
|
+
a.decodedFrom?.encoding === b.decodedFrom?.encoding &&
|
|
693
|
+
a.decodedFrom?.rawSummary === b.decodedFrom?.rawSummary
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
|
|
296
697
|
function isDeepEqual(a: unknown, b: unknown, seen = new WeakMap()): boolean {
|
|
297
698
|
if (Object.is(a, b)) return true;
|
|
298
699
|
|
|
700
|
+
if (isBytesDisplay(a) || isBytesDisplay(b)) {
|
|
701
|
+
return isBytesDisplay(a) && isBytesDisplay(b) && isSameBytesDisplay(a, b);
|
|
702
|
+
}
|
|
703
|
+
|
|
299
704
|
if (a instanceof Date && b instanceof Date) {
|
|
300
705
|
return a.getTime() === b.getTime();
|
|
301
706
|
}
|