Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/o11y-run-ref-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@workflow/core": patch
"@workflow/web-shared": patch
"@workflow/web": patch
---

Add clickable Run reference rendering in observability UI
14 changes: 11 additions & 3 deletions packages/cli/src/lib/inspect/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import {
hydrateResourceIO as hydrateResourceIOGeneric,
isEncryptedData,
isExpiredStub,
isRunRef,
observabilityRevivers,
type Revivers,
serializedInstanceToRef,
} from '@workflow/core/serialization-format';
import { parseClassName } from '@workflow/utils/parse-name';
import chalk from 'chalk';
Expand Down Expand Up @@ -206,12 +208,18 @@ export function getCLIRevivers(): Revivers {
...observabilityRevivers,
// CLI-specific overrides for class instances with inspect.custom
Class: (value) => `<class:${extractClassName(value.classId)}>`,
Instance: (value) =>
new CLIClassInstanceRef(
Instance: (value) => {
// Run instances are rendered as RunRef for clickable rendering
const runRef = serializedInstanceToRef(value);
if (isRunRef(runRef)) {
return runRef;
}
return new CLIClassInstanceRef(
extractClassName(value.classId),
value.classId,
value.data
),
);
},
Set: (value) => new Set(value),
URL: (value) => new URL(value),
URLSearchParams: (value) => new URLSearchParams(value === '.' ? '' : value),
Expand Down
47 changes: 40 additions & 7 deletions packages/core/src/serialization-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,32 @@ export interface StreamRef {
streamId: string;
}

/** Marker for Run reference objects rendered as links in the UI */
export const RUN_REF_TYPE = '__workflow_run_ref__';

/** A Run reference for UI display */
export interface RunRef {
__type: typeof RUN_REF_TYPE;
runId: string;
}

/** Check if a value is a RunRef object */
export const isRunRef = (value: unknown): value is RunRef => {
return (
value !== null &&
typeof value === 'object' &&
'__type' in value &&
value.__type === RUN_REF_TYPE &&
'runId' in value &&
typeof value.runId === 'string'
);
};

/** Convert a serialized Run value to a RunRef for display */
export const serializedRunToRunRef = (value: { runId: string }): RunRef => {
return { __type: RUN_REF_TYPE, runId: value.runId };
};

/** Marker for custom class instance references */
export const CLASS_INSTANCE_REF_TYPE = '__workflow_class_instance_ref__';

Expand Down Expand Up @@ -347,16 +373,23 @@ export const extractClassName = (classId: string): string => {
return parts[parts.length - 1] || classId;
};

/** Convert a serialized class instance to a ClassInstanceRef for display */
/** Convert a serialized class instance to a ClassInstanceRef for display.
* Run instances are special-cased to a RunRef for clickable rendering. */
export const serializedInstanceToRef = (value: {
classId: string;
data: unknown;
}): ClassInstanceRef => {
return new ClassInstanceRef(
extractClassName(value.classId),
value.classId,
value.data
);
}): ClassInstanceRef | RunRef => {
const className = extractClassName(value.classId);
if (
className === 'Run' &&
value.data !== null &&
typeof value.data === 'object' &&
'runId' in value.data &&
typeof (value.data as { runId: unknown }).runId === 'string'
) {
return serializedRunToRunRef(value.data as { runId: string });
}
return new ClassInstanceRef(className, value.classId, value.data);
};

/** Convert a serialized class reference to a display string */
Expand Down
3 changes: 3 additions & 0 deletions packages/web-shared/src/components/run-trace-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface RunTraceViewProps {
) => Promise<void>;
onCancelRun?: (runId: string) => Promise<void>;
onStreamClick?: (streamId: string) => void;
onRunClick?: (runId: string) => void;
onSpanSelect?: (info: SpanSelectionInfo) => void;
onLoadMoreSpans?: () => void | Promise<void>;
hasMoreSpans?: boolean;
Expand All @@ -42,6 +43,7 @@ export function RunTraceView({
onResolveHook,
onCancelRun,
onStreamClick,
onRunClick,
onSpanSelect,
onLoadMoreSpans,
hasMoreSpans,
Expand Down Expand Up @@ -71,6 +73,7 @@ export function RunTraceView({
onResolveHook={onResolveHook}
onCancelRun={onCancelRun}
onStreamClick={onStreamClick}
onRunClick={onRunClick}
onSpanSelect={onSpanSelect}
onLoadMoreSpans={onLoadMoreSpans}
hasMoreSpans={hasMoreSpans}
Expand Down
Loading
Loading