Skip to content

[refactor] Annotation and trace helpers move into @agenta/entities - #5954

Open
ardaerzin wants to merge 1 commit into
fix/package-query-clientfrom
obs/wp0-entities-prep
Open

[refactor] Annotation and trace helpers move into @agenta/entities#5954
ardaerzin wants to merge 1 commit into
fix/package-query-clientfrom
obs/wp0-entities-prep

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

Observability is being extracted from web/oss into packages so that web/mobile can render the same data without a second implementation. Before any of that can move, the annotation and trace helpers it depends on have to stop living in oss/src/lib/hooks.

This is the first of five stacked PRs. It moves only the shared leaf helpers, so nothing above it has to reach back into the app layer.

Changes

useAnnotations' transformer, types and helpers move to @agenta/entities/annotation/dto. The hook keeps its place in the app and now imports the moved pieces. observability_helpers.ts and the annotations API follow their imports.

The design that governs the whole stack lands here too, in docs/design/observability-packages/: the plan, and a kickoff describing how the lanes were split.

Tests / notes

  • @agenta/entities builds, lints and passes its unit tests.
  • The lanes in this stack were split out of one working tree by decision, so an intermediate lane may not build standalone. Only the tip of the stack is verified green. A red obs/wp1 or obs/wp2 in isolation is expected, not a regression.

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Blocked Blocked Aug 12, 2026 12:17am

Request Review

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. refactoring A code change that neither fixes a bug nor adds a feature typescript Pull requests that update typescript code labels Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added shared annotation and evaluator data handling for querying, transformation, aggregation, and trace attachment.
    • Added workspace-member state support for author attribution.
    • Added reusable utilities for trace-node lookup, attachment extraction, CSV/text downloads, and value formatting.
    • Added comprehensive unit coverage for annotation helpers and nested trace searches.
  • Documentation

    • Added kickoff guidance and an implementation plan for the observability package extraction.

Walkthrough

Changes

Observability extraction foundation

Layer / File(s) Summary
Extraction architecture and work-package plan
docs/design/observability-packages/plan.md
Defines the target packages, WP0–WP8 sequence, parity requirements, validation gates, risks, measurements, and future table migration.
Extraction kickoff and validation workflow
docs/design/observability-packages/KICKOFF.md
Defines extraction rules, unresolved decisions, compatibility requirements, validation commands, Git workflow, implementation traps, and completion criteria.
Shared annotation and trace entity contracts
web/packages/agenta-entities/..., web/oss/src/lib/hooks/useAnnotations/index.ts, web/oss/src/lib/traces/observability_helpers.ts, web/oss/src/services/annotations/api/index.ts, web/packages/agenta-entities/tests/unit/*
Adds shared annotation DTOs, query mapping, transformations, typed aggregation and attachment helpers, workspace-member atoms, and generic span-tree lookup. OSS consumers use the shared contracts.
Shared payload and download utilities
web/packages/agenta-shared/src/utils/*
Adds recursive attachment sanitization, browser-safe CSV and text downloads, and JSON-or-string formatting exports.

Estimated code review effort: 4 (Complex) | ~60 minutes

Mergeability Score: 🟡 Moderate · up to 307b6

This refactor moves annotation and trace behavior into shared packages, but the current implementation can mishandle missing annotations, query traces through the wrong integration contract, or select the wrong trace node when metadata contains a matching ID. These issues should be resolved before merging.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: moving annotation and trace helpers into @agenta/entities.
Description check ✅ Passed The description accurately explains the observability extraction context, implementation changes, documentation, and validation status.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch obs/wp0-entities-prep

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
web/packages/agenta-shared/src/utils/attachments.ts (1)

1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reduce these comments to one short line.

  • web/packages/agenta-shared/src/utils/attachments.ts#L1-L6: Replace the five-line block with one short summary line.
  • web/packages/agenta-shared/src/utils/typeNarrowing.ts#L30-L33: Replace the four-line block with one short summary line.

As per coding guidelines, “Keep in-code comments to at most one short line; use longer comments only for genuinely surprising constraints such as bugs, races, or ordering requirements.”

Source: Coding guidelines

web/packages/agenta-entities/tests/unit/annotation-dto-helpers.test.ts (1)

3-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use package entry points in this test.

These direct src imports bypass the package export contract. Import annotation DTO symbols from @agenta/entities/annotation/dto. Import getNodeById from @agenta/entities/trace. If an export is missing, add it to the corresponding public entry point.

As per coding guidelines, use exported subpath imports for tree-shaking.

Sources: Coding guidelines, Learnings

web/packages/agenta-entities/src/annotation/dto/helpers.ts (1)

179-209: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Return recursively annotated child traces.

Define a recursive annotated trace type, constrain T to AnnotatableTrace, and return that type. This exposes annotation fields on child traces and removes the cast in annotation-dto-helpers.test.ts.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 54b180a5-c719-4fee-ace2-bbac6eb4737d

📥 Commits

Reviewing files that changed from the base of the PR and between 079bc20 and 307b66d.

📒 Files selected for processing (24)
  • docs/design/observability-packages/KICKOFF.md
  • docs/design/observability-packages/plan.md
  • web/oss/src/lib/helpers/utils.ts
  • web/oss/src/lib/hooks/useAnnotations/assets/transformer.ts
  • web/oss/src/lib/hooks/useAnnotations/index.ts
  • web/oss/src/lib/hooks/useAnnotations/types/index.ts
  • web/oss/src/lib/traces/observability_helpers.ts
  • web/oss/src/services/annotations/api/index.ts
  • web/packages/agenta-entities/package.json
  • web/packages/agenta-entities/src/annotation/dto/api.ts
  • web/packages/agenta-entities/src/annotation/dto/helpers.ts
  • web/packages/agenta-entities/src/annotation/dto/index.ts
  • web/packages/agenta-entities/src/annotation/dto/transformer.ts
  • web/packages/agenta-entities/src/annotation/dto/types.ts
  • web/packages/agenta-entities/src/organization/index.ts
  • web/packages/agenta-entities/src/organization/state.ts
  • web/packages/agenta-entities/src/trace/index.ts
  • web/packages/agenta-entities/src/trace/utils/index.ts
  • web/packages/agenta-entities/src/trace/utils/nodeTree.ts
  • web/packages/agenta-entities/tests/unit/annotation-dto-helpers.test.ts
  • web/packages/agenta-shared/src/utils/attachments.ts
  • web/packages/agenta-shared/src/utils/download.ts
  • web/packages/agenta-shared/src/utils/index.ts
  • web/packages/agenta-shared/src/utils/typeNarrowing.ts
💤 Files with no reviewable changes (4)
  • web/oss/src/lib/hooks/useAnnotations/assets/transformer.ts
  • web/oss/src/lib/helpers/utils.ts
  • web/oss/src/lib/hooks/useAnnotations/types/index.ts
  • web/oss/src/lib/traces/observability_helpers.ts

Comment on lines +6 to +10
> **Not yet decided:** the observability session row (WP5) and the `useEvaluatorReference`
> subtree (WP3) — both flagged inline, both need an answer before their WP starts.
> **To execute this plan, start from `KICKOFF.md` in this directory.**
> **WP0–WP3 are done** (empty states deferred within WP3 — see below) (§9 re-verified on `079bc20be4`; all numbers reproduced). Corrections it
> found are folded in below and listed in the table at the end of §9.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Publish one authoritative D1 decision.

The plan header and kickoff gate say D1 is open, while the WP3 audit says D1 is resolved by injecting the evaluator label.

  • docs/design/observability-packages/plan.md#L6-L10: update the status summary.
  • docs/design/observability-packages/plan.md#L333-L335: retain the resolved displayName contract if it is final.
  • docs/design/observability-packages/KICKOFF.md#L46-L60: remove the stale D1 blocker and option list.
📍 Affects 2 files
  • docs/design/observability-packages/plan.md#L6-L10 (this comment)
  • docs/design/observability-packages/plan.md#L333-L335
  • docs/design/observability-packages/KICKOFF.md#L46-L60

Comment on lines +158 to +163
> **Corrected — there are no re-export shims.** `web/eslint.config.mjs` bans
> `export … from "@agenta/*"` in `oss/src/**` and `ee/src/**` (`no-restricted-syntax`, for
> tree-shaking). Every WP in this stack must therefore **rewrite the call sites** to import
> from the package and delete the old OSS module, rather than leaving a shim behind. WP0
> rewrote 30 import sites across 25 files. Budget for this in every later WP — the plan's
> "nothing else in OSS moves in this PR" framing does not hold.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Align the extraction and compatibility rules across both documents.

The documents currently describe incompatible WP0/WP1 migrations and shim policies.

  • docs/design/observability-packages/plan.md#L158-L163: keep the direct-import and deletion rule.
  • docs/design/observability-packages/plan.md#L199-L203: remove the value re-export-shim instruction.
  • docs/design/observability-packages/KICKOFF.md#L68-L75: use the corrected DTO/API and getNodeById scope.
  • docs/design/observability-packages/KICKOFF.md#L142-L147: remove the requirement for old-path value shims.
📍 Affects 2 files
  • docs/design/observability-packages/plan.md#L158-L163 (this comment)
  • docs/design/observability-packages/plan.md#L199-L203
  • docs/design/observability-packages/KICKOFF.md#L68-L75
  • docs/design/observability-packages/KICKOFF.md#L142-L147

Comment on lines +269 to +274
> **Split `assets/constants.ts` (770 LOC) — it holds two unrelated exports.** `FILTER_COLUMNS`
> (lines 37–705) is filter field metadata and belongs to this WP; `spanTypeStyles` (line 706+)
> is the span-category icon/colour map consumed by `AvatarTreeContent` and `NodeNameCell` and
> belongs to **WP3**. Moving the file whole into either WP creates a backwards dependency
> between them. Split it at the extraction, don't carry it. (Confirmed exactly: line 37 and
> line 706.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use one WP owner for spanTypeStyles.

Lines 269-274 assign spanTypeStyles to WP3. Line 330 calls it a WP2 split. Change the dependency audit to say WP3. Otherwise the sequence can move this symbol before its intended package boundary.

Also applies to: 330-330

Comment on lines +642 to +654
cd web/oss/src/components/pages/observability
for f in components/*.tsx components/SessionsTable/components/Cells/*.tsx; do
d=$(grep -h '@/oss/' "$f" | sed 's/.*"\(@\/oss[^"]*\)".*/\1/' | tr '\n' ' ')
[ -n "$d" ] && echo "$f → $d"
done

# §8 the table
D=web/packages/agenta-ui/src/InfiniteVirtualTable
find $D -type f | xargs wc -l | tail -1 # 13295 total
grep -rl 'from "antd"' $D | wc -l # 12 runtime importers
grep -rl 'ColumnsType\|ColumnType\|antd/es/table' $D | wc -l # 22 type-coupled
grep -rln '@agenta/ui/table' web/oss/src web/ee/src web/packages/*/src | wc -l # 82 consumers
grep -rln '\.ant-table' web/oss/src web/ee/src web/packages/*/src | wc -l # 18 DOM-coupled

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the validation commands use consistent working directories.

The plan changes directory and does not restore it. The kickoff block runs from web/ but uses a repository-root path.

  • docs/design/observability-packages/plan.md#L642-L654: use a subshell or restore the repository root before setting D.
  • docs/design/observability-packages/KICKOFF.md#L83-L92: use packages/agenta-observability-ui/src from web/, or run the block from the repository root.
📍 Affects 2 files
  • docs/design/observability-packages/plan.md#L642-L654 (this comment)
  • docs/design/observability-packages/KICKOFF.md#L83-L92

Comment on lines +42 to +47
const response = await axios.post(
`${getAgentaApiUrl()}/simple/traces/query`,
mapAnnotationQueryToTrace(queries),
{params: projectId ? {project_id: projectId} : undefined},
)
return mapTraceResponseToAnnotation(response.data ?? {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline web/packages/agenta-sdk/src/resources.ts --items all
rg -n -C 3 'simple/traces|queryAllAnnotations|safeParseWithLogging' web/packages web/oss

Repository: Agenta-AI/agenta

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- annotation DTO API ---'
cat -n web/packages/agenta-entities/src/annotation/dto/api.ts

printf '%s\n' '--- annotation DTO schemas and mappers ---'
rg -n -C 5 'annotationSchema|AnnotationDto|mapAnnotationQueryToTrace|mapTraceResponseToAnnotation|trace' \
  web/packages/agenta-entities/src/annotation

printf '%s\n' '--- Fern resource accessor ---'
cat -n web/packages/agenta-sdk/src/resources.ts

printf '%s\n' '--- generated query method ---'
sed -n '1340,1425p' web/packages/agenta-api-client/src/generated/api/resources/traces/client/Client.ts
rg -n -C 8 'query.*trac|SimpleTrace.*Query|Query.*SimpleTrace|simple_traces_query|SimpleTracesQuery' \
  web/packages/agenta-api-client/src/generated/api/resources/traces web/packages/agenta-api-client/src/generated/api

Repository: Agenta-AI/agenta

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- exact DTO API imports and callers ---'
sed -n '1,120p' web/packages/agenta-entities/src/annotation/dto/api.ts
sed -n '1,100p' web/oss/src/services/annotations/api/index.ts
rg -n -C 4 'queryAllAnnotations\(' web --glob '*.{ts,tsx}'

printf '%s\n' '--- generated request/response type definitions ---'
rg -n -C 12 'QueryTraces|queryTraces|QuerySimple|SimpleTrace' \
  web/packages/agenta-api-client/src/generated/api/resources/traces \
  web/packages/agenta-api-client/src/generated/api

printf '%s\n' '--- validation helper contract and annotation schemas ---'
rg -n -C 10 'function safeParseWithLogging|const safeParseWithLogging|export .*safeParseWithLogging' \
  web/packages/agenta-entities/src web/packages/agenta-entities
rg -n -C 8 'annotationSchema|annotationsResponseSchema|AnnotationsResponse|Trace' \
  web/packages/agenta-entities/src/annotation web/packages/agenta-entities/src --glob '*schema*'

Repository: Agenta-AI/agenta

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- generated traces exports and method signatures ---'
rg -n 'export|query|Query|Trace' web/packages/agenta-api-client/src/generated/api/resources/traces \
  --glob '*.ts' | head -160

printf '%s\n' '--- all SDK resource accessors and imports ---'
cat -n web/packages/agenta-sdk/src/resources.ts

printf '%s\n' '--- DTO API and nearby schemas ---'
cat -n web/packages/agenta-entities/src/annotation/dto/api.ts
find web/packages/agenta-entities/src/annotation -maxdepth 3 -type f -print

Repository: Agenta-AI/agenta

Length of output: 32757


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- simple trace query method ---'
sed -n '1360,1425p' web/packages/agenta-api-client/src/generated/api/resources/traces/client/Client.ts

printf '%s\n' '--- generated type names and definitions ---'
rg -n 'Simple.*Query|Query.*Simple|SimpleTrace.*Response|TracesResponse|type .*Request|interface .*Request' \
  web/packages/agenta-api-client/src/generated/api \
  --glob '*.ts' | grep -E 'Simple|TracesResponse|Query.*Request|Request.*Query' | head -200

printf '%s\n' '--- generated traces resource entrypoint ---'
cat -n web/packages/agenta-api-client/src/generated/api/resources/traces/client/index.ts
cat -n web/packages/agenta-api-client/src/generated/api/resources/traces/index.ts

Repository: Agenta-AI/agenta

Length of output: 35534


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- method around /simple/traces/query ---'
awk 'NR>=1365 && NR<=1422 {printf "%6d\t%s\n", NR, $0}' \
  web/packages/agenta-api-client/src/generated/api/resources/traces/client/Client.ts

printf '%s\n' '--- generated declarations containing simple query types ---'
rg -n -C 6 'simple/traces/query|SimpleTrace.*Query|Query.*SimpleTrace|SimpleTraceResponse|TracesResponse' \
  web/packages/agenta-api-client/src/generated --glob '*.ts'

printf '%s\n' '--- DTO types ---'
cat -n web/packages/agenta-entities/src/annotation/dto/types.ts

Repository: Agenta-AI/agenta

Length of output: 49655


Use the existing Fern traces accessor and validate the response envelope.

Call getTracesClient().querySimpleTraces() with {queryParams: {project_id: projectId}}. Preserve raw trace items, but validate the {count, traces} envelope with safeParseWithLogging before mapping it. Do not add a new accessor; getTracesClient() already exists.

Source: Coding guidelines

Comment on lines +96 to +99
export interface AnnotationsResponseDto {
count: number
annotation: AnnotationDto
annotations: AnnotationDto[]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make AnnotationsResponseDto.annotation nullable.

Both response mappers return null when the API omits trace. The DTO declares annotation as AnnotationDto, and mapTraceResponseToAnnotation hides the mismatch with a cast. Callers can then dereference a missing annotation.

  • web/packages/agenta-entities/src/annotation/dto/types.ts#L96-L99: change annotation to AnnotationDto | null.
  • web/packages/agenta-entities/src/annotation/dto/api.ts#L24-L32: remove the unsafe as AnnotationDto cast and preserve the nullable value.
  • web/oss/src/services/annotations/api/index.ts#L28-L32: retain the nullable value under the corrected DTO contract and handle the union in consumers.
📍 Affects 3 files
  • web/packages/agenta-entities/src/annotation/dto/types.ts#L96-L99 (this comment)
  • web/packages/agenta-entities/src/annotation/dto/api.ts#L24-L32
  • web/oss/src/services/annotations/api/index.ts#L28-L32

Comment on lines +21 to +43
if (nodes) {
for (const value of Object.values(nodes) as (T | T[])[]) {
if (Array.isArray(value)) {
for (const node of value) {
if (node.span_id === id) {
return node
}

if (node.children) {
const foundNode = getNodeById(node.children as T[], id)
if (foundNode) return foundNode
}
}
} else {
if (value?.span_id === id) {
return value
}

if (value?.children) {
const foundNode = getNodeById(value.children as T[], id)
if (foundNode) return foundNode
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Traverse only children nodes.

Object.values(nodes) scans all node properties. If metadata such as a span link contains the requested span_id, this function can return that metadata object instead of the matching tree node.

Normalize the root input to an array. Then recurse only through node.children. Add a regression test with a non-child nested object that has the same span_id as a real child.

Proposed fix
-    if (nodes) {
-        for (const value of Object.values(nodes) as (T | T[])[]) {
-            if (Array.isArray(value)) {
-                for (const node of value) {
-                    if (node.span_id === id) {
-                        return node
-                    }
-
-                    if (node.children) {
-                        const foundNode = getNodeById(node.children as T[], id)
-                        if (foundNode) return foundNode
-                    }
-                }
-            } else {
-                if (value?.span_id === id) {
-                    return value
-                }
-
-                if (value?.children) {
-                    const foundNode = getNodeById(value.children as T[], id)
-                    if (foundNode) return foundNode
-                }
-            }
-        }
+    const roots = !nodes ? [] : Array.isArray(nodes) ? nodes : [nodes]
+    for (const node of roots) {
+        if (node.span_id === id) return node
+
+        if (Array.isArray(node.children)) {
+            const foundNode = getNodeById(node.children as T[], id)
+            if (foundNode) return foundNode
+        }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (nodes) {
for (const value of Object.values(nodes) as (T | T[])[]) {
if (Array.isArray(value)) {
for (const node of value) {
if (node.span_id === id) {
return node
}
if (node.children) {
const foundNode = getNodeById(node.children as T[], id)
if (foundNode) return foundNode
}
}
} else {
if (value?.span_id === id) {
return value
}
if (value?.children) {
const foundNode = getNodeById(value.children as T[], id)
if (foundNode) return foundNode
}
}
const roots = !nodes ? [] : Array.isArray(nodes) ? nodes : [nodes]
for (const node of roots) {
if (node.span_id === id) return node
if (Array.isArray(node.children)) {
const foundNode = getNodeById(node.children as T[], id)
if (foundNode) return foundNode
}
}

Comment on lines +34 to +35
export function getStringOrJson(value: unknown): string {
return typeof value === "string" ? value : JSON.stringify(value, null, 2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -HI -t f 'tsconfig*.json' . -x sh -c '
  echo "=== $1 ==="
  rg -n "\"strict\"|\"strictNullChecks\"" "$1" || true
' sh {}

Repository: Agenta-AI/agenta

Length of output: 2108


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="web/packages/agenta-shared/src/utils/typeNarrowing.ts"

echo "=== file ==="
cat -n "$file"

echo "=== package and workspace TypeScript configuration ==="
for f in web/tsconfig.json web/packages/agenta-shared/tsconfig.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    cat -n "$f"
  fi
done

echo "=== declarations and call sites ==="
rg -n -C 3 "getStringOrJson" web --glob '*.{ts,tsx}' || true

echo "=== related formatter usage ==="
rg -n -C 3 "trace text|traceText|formatter|format.*trace|StringOrJson" web/packages web/oss web/ee --glob '*.{ts,tsx}' || true

echo "=== JSON.stringify compiler declarations ==="
rg -n "JSON\.stringify|declare.*stringify" web/packages/agenta-shared web --glob '*.{ts,tsx,d.ts}' | head -80 || true

Repository: Agenta-AI/agenta

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== agenta-shared compiler configuration ==="
cat -n web/packages/tsconfig.base.json 2>/dev/null || true
cat -n web/packages/agenta-shared/package.json 2>/dev/null || true

echo "=== exact references to the shared helper ==="
rg -n --glob '*.{ts,tsx}' '\bgetStringOrJson\b' web/packages/agenta-shared web/oss web/ee \
  | head -100 || true

echo "=== JSON.stringify behavior for relevant inputs ==="
node - <<'JS'
const circular = {}
circular.self = circular

for (const [name, value] of [
  ["undefined", undefined],
  ["function", function f() {}],
  ["symbol", Symbol("x")],
  ["bigint", 1n],
  ["circular", circular],
]) {
  try {
    const result = JSON.stringify(value, null, 2)
    console.log(`${name}: ${result === undefined ? "undefined" : JSON.stringify(result)}`)
  } catch (error) {
    console.log(`${name}: throws ${error.constructor.name}`)
  }
}
JS

echo "=== TypeScript compiler availability ==="
if command -v tsc >/dev/null 2>&1; then
  tsc --version
else
  echo "tsc unavailable"
fi

Repository: Agenta-AI/agenta

Length of output: 7934


Return a string on every path.

With strictNullChecks, JSON.stringify is string | undefined. It returns undefined for functions, symbols, and undefined, and throws for BigInt and circular values. Handle both cases before returning. safeStringify has the same defect.

ardaerzin added a commit that referenced this pull request Aug 15, 2026
Six issues raised on #5954-#5958, each verified against the code first.

`getNodeById` walked `Object.values(node)`, which visits every property rather
than just `children`. Span metadata carries `span_id` too — an annotation
span's `invocationIds` points at a different span — so a single-node lookup
could return that bag instead of the span. The regression test covers exactly
that shape; it passes on an array input either way, which is why the first
version of it caught nothing.

Both annotation queries scoped their request by `projectId` but not their
cache key, so a project switch with the same links reused the previous
project's annotations. The drawer store had inherited the same mistake when it
stopped calling the oss wrapper that resolved the project internally.

Session token and cost totals used `||` down their fallback chains, so a real
incremental total of 0 fell through to the cumulative one and double-counted a
span that reported no new tokens.

`getOperator` can return undefined for an operator in the union but missing
from OPERATORS; dereferencing `hidesValue` threw during validation and took
the dialog's render with it.

Plus two small ones: the docs link opened without `noopener`, and a
secondary-only empty state rendered an orphaned "Or" separator.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoring A code change that neither fixes a bug nor adds a feature size:XXL This PR changes 1000+ lines, ignoring generated files. typescript Pull requests that update typescript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant