Skip to content

fix: normalize data component schemas for cross-provider LLM compatibility#2764

Merged
anubra266 merged 4 commits intomainfrom
prd-6070
Mar 24, 2026
Merged

fix: normalize data component schemas for cross-provider LLM compatibility#2764
anubra266 merged 4 commits intomainfrom
prd-6070

Conversation

@anubra266
Copy link
Contributor

@anubra266 anubra266 commented Mar 18, 2026

Problem

Data component JSON schemas pass our validation but fail at runtime when passed to OpenAI and Anthropic structured output:

  • Anthropic: rejects minimum/maximum (and exclusiveMinimum, exclusiveMaximum, multipleOf) on number/integer types
  • OpenAI strict-mode: requires every key in properties to also appear in required — optional fields like limitations caused rejections

This affected both component generation in the UI and runtime agent generation when a data component is attached to a subagent.

…ility

Adds stripUnsupportedConstraints + makeAllPropertiesRequired to agents-core
as a shared normalizeDataComponentSchema utility, fixing two provider errors:

- Anthropic: rejects minimum/maximum on number types in structured output
- OpenAI: requires all properties to appear in the required array

Applies normalization in both affected code paths:
- Runtime agent generation (schema-builder.ts)
- Component generation UI (generate-render/route.ts, previously had no normalization)

Also wraps buildDataComponentsSchema in a try/catch in generate.ts so an
invalid schema no longer crashes the server — falls back to generation
without structured output instead.
@vercel
Copy link

vercel bot commented Mar 18, 2026

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

Project Deployment Actions Updated (UTC)
agents-api Ready Ready Preview, Comment Mar 23, 2026 10:38pm
agents-manage-ui Ready Ready Preview, Comment Mar 23, 2026 10:38pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
agents-docs Skipped Skipped Mar 23, 2026 10:38pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: fcb5952

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pullfrog
Copy link
Contributor

pullfrog bot commented Mar 18, 2026

TL;DR — Data component JSON Schemas are now normalized before being sent to LLMs, stripping numeric constraints that Anthropic rejects (minimum/maximum) and promoting all properties to required for OpenAI strict-mode. The normalization pipeline lives in a shared normalizeDataComponentSchema utility in agents-core, replacing the duplicated logic that previously lived only in SchemaProcessor.

Key changes

  • Add normalizeDataComponentSchema pipeline in agents-core — Composes stripUnsupportedConstraintsmakeAllPropertiesRequired into a single cross-provider normalization pass, exported from schema-conversion.ts.
  • Deduplicate SchemaProcessor.makeAllPropertiesRequired — The inline implementation is replaced with a thin delegate to the new shared makeAllPropertiesRequired; a new stripUnsupportedConstraints static method is added for API parity.
  • Normalize artifact component schemasArtifactCreateSchema now calls normalizeDataComponentSchema instead of SchemaProcessor.makeAllPropertiesRequired, picking up the constraint-stripping step for artifact structured output.
  • Normalize in schema-builder.ts and both manage UI preview routesbuildDataComponentsSchema, the data-component generate-render route, and the artifact-component generate-render route all use the shared pipeline, fixing Anthropic rejection in runtime and both preview flows.
  • Graceful fallback on schema errorsgenerate.ts wraps buildDataComponentsSchema in try/catch so a malformed schema logs an error and falls back to no structured output instead of aborting generation.
  • Add schema-normalization.test.ts — Unit tests covering constraint stripping, required promotion, the composed pipeline, edge cases, and the exact schema from the bug report.

Summary | 8 files | 4 commits | base: mainprd-6070


Shared normalization pipeline in agents-core

Before: SchemaProcessor.makeAllPropertiesRequired handled the OpenAI required constraint inline but nothing stripped Anthropic-unsupported numeric keywords. The logic was duplicated in SchemaProcessor and not reusable.
After: A composable pipeline in schema-conversion.ts: stripUnsupportedConstraintsmakeAllPropertiesRequired, exported as normalizeDataComponentSchema.

Both transforms recurse into properties, items, anyOf, oneOf, and allOf. stripUnsupportedConstraints removes minimum, maximum, exclusiveMinimum, exclusiveMaximum, and multipleOf from number/integer nodes. makeAllPropertiesRequired promotes every property key to required and wraps originally-optional fields in { anyOf: [<schema>, { type: 'null' }] }.

Why strip numeric constraints instead of mapping them to descriptions? Anthropic's structured output API hard-rejects these keywords rather than ignoring them. Stripping is the simplest cross-provider fix — the LLM still sees the description text if the schema author included one.

schema-conversion.ts · schema-normalization.test.ts


SchemaProcessor delegates to shared utilities

Before: SchemaProcessor owned a ~40-line makeAllPropertiesRequired implementation with no constraint stripping.
After: Both makeAllPropertiesRequired and stripUnsupportedConstraints are thin delegates to the agents-core exports; the class retains its static API for callers that still reference it directly.

SchemaProcessor.ts


Callers updated across runtime and manage UI

Before: schema-builder.ts called SchemaProcessor.makeAllPropertiesRequired; ArtifactCreateSchema did the same; both manage UI preview routes (data-components and artifact-components generate-render) passed raw dataComponent.props / artifactComponent.props to z.fromJSONSchema without normalization.
After: All five call sites use normalizeDataComponentSchema, getting constraint stripping and required-promotion in one step.

The generate.ts wrapper now catches errors from buildDataComponentsSchema so a malformed schema degrades gracefully instead of aborting the entire generation.

schema-builder.ts · artifact-component-schema.ts · generate.ts · artifact-components/.../route.ts · data-components/.../route.ts

Pullfrog  | View workflow run | Triggered by Pullfrogpullfrog.com𝕏

Copy link
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

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

Good fix — the new shared normalization utilities are well-structured, the try/catch fallback in generate.ts prevents server crashes, and the test coverage is solid. Two items worth addressing: a duplication concern and a gap in the artifact-component-schema.ts call sites.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow runpullfrog.com𝕏

Comment on lines +91 to +135
export function stripUnsupportedConstraints<T extends Record<string, unknown> | null | undefined>(
schema: T
): T {
if (!schema || typeof schema !== 'object') {
return schema;
}

const stripped: any = { ...schema };

if (stripped.type === 'number' || stripped.type === 'integer') {
delete stripped.minimum;
delete stripped.maximum;
delete stripped.exclusiveMinimum;
delete stripped.exclusiveMaximum;
delete stripped.multipleOf;
}

if (stripped.properties && typeof stripped.properties === 'object') {
const strippedProperties: any = {};
for (const [key, value] of Object.entries(stripped.properties)) {
strippedProperties[key] = stripUnsupportedConstraints(value as Record<string, unknown>);
}
stripped.properties = strippedProperties;
}

if (stripped.items) {
stripped.items = stripUnsupportedConstraints(stripped.items as Record<string, unknown>);
}
if (Array.isArray(stripped.anyOf)) {
stripped.anyOf = stripped.anyOf.map((s: any) =>
stripUnsupportedConstraints(s as Record<string, unknown>)
);
}
if (Array.isArray(stripped.oneOf)) {
stripped.oneOf = stripped.oneOf.map((s: any) =>
stripUnsupportedConstraints(s as Record<string, unknown>)
);
}
if (Array.isArray(stripped.allOf)) {
stripped.allOf = stripped.allOf.map((s: any) =>
stripUnsupportedConstraints(s as Record<string, unknown>)
);
}

return stripped;
Copy link
Contributor

Choose a reason for hiding this comment

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

Neither stripUnsupportedConstraints nor makeAllPropertiesRequired recurse into $defs / definitions or follow $ref pointers. If a user defines a data component schema that uses JSON Schema references (e.g., shared types via $defs), constraints inside those definitions will survive stripping.

This is likely fine for current data component schemas (they tend to be flat), but worth noting as a known limitation — or adding a $defs/definitions traversal for completeness.

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(4) Total Issues | Risk: Medium

🟠⚠️ Major (3) 🟠⚠️

🟠 1) SchemaProcessor.ts Duplicate makeAllPropertiesRequired should delegate to agents-core

Issue: The PR correctly established a delegation pattern for stripUnsupportedConstraints (lines 289-293), but makeAllPropertiesRequired (lines 232-283) remains a full 50-line duplicate of the identical implementation in agents-core/src/utils/schema-conversion.ts:145-196.

Why: Code duplication creates maintenance burden — if a bug is found in one implementation, it must be fixed in both. The delegation pattern is already established in this file; makeAllPropertiesRequired should follow the same pattern for consistency. This is especially concerning since both implementations handle the same edge cases (nullable detection, anyOf handling).

Fix: Replace the full implementation with a delegation wrapper (same pattern as stripUnsupportedConstraints):

import { makeAllPropertiesRequired as makeAllPropertiesRequiredCore } from '@inkeep/agents-core';

static makeAllPropertiesRequired<T extends JSONSchema.BaseSchema | Record<string, unknown> | null | undefined>(schema: T): T {
  return makeAllPropertiesRequiredCore(schema as Record<string, unknown>) as T;
}

Then remove lines 232-283.

Refs:

🟠 2) generate.ts:228-238 Silent fallback masks failures from users

Issue: When buildDataComponentsSchema() fails, the error is logged but generation silently continues without structured output. Users who configured data components expecting JSON responses will receive unstructured text instead, with no visible indication that anything went wrong.

Why: This creates a debugging nightmare. Users see their agent "working" but not producing the expected data components. The catch block swallows ALL exceptions indiscriminately — schema validation errors, type errors, or unexpected runtime bugs are all hidden. The only diagnostic path is server log correlation, which most users cannot access.

Fix: Consider surfacing the failure to users. Options:

  1. Fail fast — throw a user-facing error explaining the schema issue
  2. Graceful with warning — add span.setAttribute('generation.structured_output_fallback', true) and send a warning via ctx.streamHelper if available
  3. At minimum — include dataComponentIds in the error log for debugging

Refs:

🟠 3) generate-render/route.ts:63-66 Missing error handling for schema normalization

Issue: normalizeDataComponentSchema() and z.fromJSONSchema() are called without specific error handling. If these fail, the outer try/catch returns a generic HTTP 500 with no context about which data component failed or why.

Why: When "Generate Component" fails in the Agent Builder UI, users cannot self-diagnose. They see a generic error toast with no actionable information. This generates support tickets that could be avoided with better error messaging.

Fix: Add specific error handling that returns HTTP 400 with actionable feedback:

try {
  normalizedProps = normalizeDataComponentSchema(dataComponent.props as Record<string, unknown>);
  mockDataSchema = z.fromJSONSchema(normalizedProps);
} catch (error) {
  console.error('Schema normalization failed:', { dataComponentId, dataComponentName: dataComponent.name, error });
  return new Response(JSON.stringify({
    error: 'Invalid data component schema',
    message: 'Please check that all field types are valid.',
  }), { status: 400, headers: { 'Content-Type': 'application/json' } });
}

Refs:

Inline Comments:

  • 🟠 Major: SchemaProcessor.ts:293 Duplicate makeAllPropertiesRequired should delegate
  • 🟠 Major: generate.ts:228-238 Silent fallback from structured to unstructured output
  • 🟠 Major: generate-render/route.ts:63-66 Missing error handling for schema normalization

🟡 Minor (1) 🟡

🟡 1) schema-normalization.test.ts Additional edge case coverage recommended

Issue: The test suite covers core functionality well but misses some edge cases handled by the implementation: oneOf/allOf recursive stripping (only anyOf tested), nullable: true property handling, and deeply nested schemas (3+ levels).

Why: These code paths exist in the implementation but aren't validated by tests. Future refactoring could silently break them.

Fix: Consider adding tests for oneOf/allOf stripping and nullable: true handling. See inline comment for examples.

Refs:

Inline Comments:

  • 🟡 Minor: schema-normalization.test.ts:175 Consider adding edge case coverage

💭 Consider (2) 💭

💭 1) artifact-component-schema.ts:105,159 Partial normalization inconsistency

Issue: This file calls SchemaProcessor.makeAllPropertiesRequired but NOT stripUnsupportedConstraints, meaning artifact component schemas only get OpenAI compatibility fixes but NOT Anthropic compatibility fixes. By contrast, schema-builder.ts and generate-render/route.ts now use the full normalizeDataComponentSchema helper.

Why: Split-world scenario where artifact components behave differently from data components. If artifact schemas include numeric constraints, they'll fail with Anthropic models.

Fix: Consider using normalizeDataComponentSchema here as well for consistency, or document why partial normalization is intentional for artifact schemas.

💭 2) Missing changeset

Issue: The changeset-bot flagged this PR has no changeset. This PR modifies agents-core, agents-api, and agents-manage-ui with bug fixes that affect runtime behavior.

Fix: Add a changeset: pnpm bump patch --pkg agents-core --pkg agents-api "Fix data component schema normalization for cross-provider LLM compatibility"


💡 APPROVE WITH SUGGESTIONS

Summary: The core fix is solid — the new normalizeDataComponentSchema utility correctly addresses the Anthropic/OpenAI compatibility issues. However, the PR introduces code duplication (SchemaProcessor.makeAllPropertiesRequired should delegate to agents-core like stripUnsupportedConstraints does) and the silent fallback behavior in generate.ts could mask configuration issues from users. Address the duplication and consider improving error visibility.

Discarded (3)
Location Issue Reason Discarded
schema-conversion.ts:91-136 Unsafe any type casting in recursive schema processing Pragmatic choice for JSON Schema processing; type guards at entry points provide sufficient safety
schema-conversion.ts:1 File should be named schema-normalization.ts Cosmetic naming suggestion; file already exports schema conversion utilities so current name is acceptable
generate.ts:303 hasStructuredOutput variable is misleading after fallback Minor observability concern; the variable reflects config intent, not runtime state — acceptable semantics
Reviewers (4)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
pr-review-standards 4 1 0 0 1 0 2
pr-review-tests 8 0 0 0 1 0 7
pr-review-errors 3 1 0 0 2 0 0
pr-review-consistency 3 1 2 0 0 0 0
Total 18 3 2 0 4 0 9

Comment on lines +228 to +238
let dataComponentsSchema: ReturnType<typeof buildDataComponentsSchema> | null = null;
if (hasStructuredOutput) {
try {
dataComponentsSchema = buildDataComponentsSchema(ctx);
} catch (err) {
logger.error(
{ agentId: ctx.config.id, err },
'Failed to build data components schema — continuing without structured output'
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟠 MAJOR: Silent fallback from structured to unstructured output

Issue: When buildDataComponentsSchema() fails, the error is logged but generation silently continues without structured output. Users configured data components expecting JSON output will receive unstructured text instead, with no visible indication of the failure.

Why: This degrades user experience silently. The user configured data components, expects structured output, but receives plain text. The catch block swallows ALL exception types — it could hide schema validation errors, type errors, or unexpected runtime bugs. Debugging requires server log correlation.

Fix: Consider one of these approaches:

Option A — Fail fast (recommended for data integrity):

let dataComponentsSchema: ReturnType<typeof buildDataComponentsSchema> | null = null;
if (hasStructuredOutput) {
  try {
    dataComponentsSchema = buildDataComponentsSchema(ctx);
  } catch (err) {
    logger.error(
      { agentId: ctx.config.id, dataComponentIds: ctx.config.dataComponents?.map(dc => dc.id), err },
      'Data component schema build failed'
    );
    throw new Error(
      'Failed to build structured output schema from data components. ' +
      'Please review your data component schemas in the agent builder.'
    );
  }
}

Option B — Surface warning to user (graceful with awareness):

// Add span attribute for observability
span.setAttribute('generation.structured_output_fallback', true);
// Send warning via stream helper if available
if (ctx.streamHelper) {
  ctx.streamHelper.sendWarning('Structured output unavailable due to schema issues. Returning text response.');
}

Refs:

  • PR description — mentions "graceful fallback" but doesn't address user visibility

Comment on lines +63 to +66
const normalizedProps = normalizeDataComponentSchema(
dataComponent.props as Record<string, unknown>
);
const mockDataSchema = z.fromJSONSchema(normalizedProps);
Copy link
Contributor

Choose a reason for hiding this comment

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

🟠 MAJOR: Missing error handling for schema normalization

Issue: normalizeDataComponentSchema() and z.fromJSONSchema() are called without specific error handling. If normalization fails, the outer try/catch (line 124) catches it and returns a generic HTTP 500 with error.message. Users see "Internal server error" with no context about which data component failed or why.

Why: When clicking "Generate Component" in the Agent Builder UI fails, users cannot self-diagnose. They see a generic error toast with no actionable information. Every schema issue becomes a support ticket.

Fix: Add specific error handling with actionable feedback:

let normalizedProps: Record<string, unknown>;
let mockDataSchema: z.ZodType<any>;

try {
  normalizedProps = normalizeDataComponentSchema(
    dataComponent.props as Record<string, unknown>
  );
  mockDataSchema = z.fromJSONSchema(normalizedProps);
} catch (error) {
  console.error('Schema normalization failed:', {
    dataComponentId,
    dataComponentName: dataComponent.name,
    error: error instanceof Error ? error.message : String(error),
  });
  
  return new Response(
    JSON.stringify({
      error: 'Invalid data component schema',
      message: 'The data component schema could not be processed. Please check that all field types are valid.',
      details: error instanceof Error ? error.message : undefined,
    }),
    { 
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    }
  );
}

Refs:

expect(result.properties.b).not.toHaveProperty('minimum');
expect(result.properties.b).not.toHaveProperty('maximum');
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Minor: Consider adding test coverage for edge cases

Issue: The test suite covers the core functionality well, but could benefit from additional edge cases:

  1. oneOf/allOf recursive stripping — only anyOf is tested (line 76-82)
  2. nullable: true property handling — the code checks for this (schema-conversion.ts:167) but no test validates it
  3. Deeply nested schemas (3+ levels) — only one level of nesting is tested

Why: These edge cases are handled in the implementation but not validated by tests. Future refactoring could silently break them.

Fix: Consider adding these test cases:

it('strips recursively in oneOf', () => {
  const schema = {
    oneOf: [{ type: 'number', minimum: 0 }, { type: 'string' }]
  };
  const result = stripUnsupportedConstraints(schema) as any;
  expect(result.oneOf[0]).not.toHaveProperty('minimum');
});

it('does not double-wrap properties already marked as nullable', () => {
  const schema = {
    type: 'object',
    properties: { field: { type: 'string', nullable: true } },
    required: []
  };
  const result = makeAllPropertiesRequired(schema) as any;
  expect(result.properties.field).not.toHaveProperty('anyOf');
});

Refs:

@github-actions github-actions bot deleted a comment from claude bot Mar 18, 2026
Copy link
Contributor

@sarah-inkeep sarah-inkeep left a comment

Choose a reason for hiding this comment

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

Could you look through the pullfrog / claude code comments, I think this one in particular seems relevant https://github.com/inkeep/agents/pull/2764/changes#r2956834716?
Could you also apply this fix to artifacts as well? I think the issue exists there as well.

…emas

- SchemaProcessor.makeAllPropertiesRequired now delegates to agents-core
  (same pattern as stripUnsupportedConstraints)
- artifact-component-schema.ts now uses normalizeDataComponentSchema
  (adds missing stripUnsupportedConstraints for Anthropic compatibility)
@vercel vercel bot temporarily deployed to Preview – agents-docs March 23, 2026 21:41 Inactive
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Cancelling pending review to start fresh.

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

✅ Prior Issues Resolved

The key issues from the prior reviews have been addressed in this latest commit:

  1. ✅ SchemaProcessor duplication fixedmakeAllPropertiesRequired now delegates to makeAllPropertiesRequiredCore from agents-core (lines 235-238), following the same pattern as stripUnsupportedConstraints. The ~40-line duplicate implementation has been removed.

  2. ✅ Artifacts now use full normalization — Both ArtifactCreateSchema.getSchemas() (line 106) and ArtifactCreateSchema.getDataComponents() (line 160) now call normalizeDataComponentSchema() instead of just SchemaProcessor.makeAllPropertiesRequired(). This ensures Anthropic compatibility (constraint stripping) is applied to artifact schemas, addressing sarah-inkeep's feedback.

🕐 Pending Recommendations (2)

These items were flagged in prior automated reviews but were not specifically requested by the human reviewer. They remain as considerations for future work:

💭 Consider (3)

Test coverage enhancements (all MINOR, optional):

  • Add tests for oneOf/allOf recursion in stripUnsupportedConstraints (currently only anyOf is tested)
  • Add tests for makeAllPropertiesRequired recursion through nested arrays
  • Add null/undefined input test for the composed normalizeDataComponentSchema pipeline

⚠️ Changeset Reminder

The changeset-bot flagged this PR has no changeset. Consider adding:

pnpm bump patch --pkg agents-core --pkg agents-api "Fix data component schema normalization for cross-provider LLM compatibility"

✅ APPROVE

Summary: The requested fixes have been implemented correctly. SchemaProcessor now properly delegates both makeAllPropertiesRequired and stripUnsupportedConstraints to the shared utilities in agents-core, eliminating the code duplication. Artifact schemas now receive full cross-provider normalization (constraint stripping + required promotion), which resolves the Anthropic compatibility issue sarah-inkeep flagged. The implementation is clean and the test coverage validates the core functionality. 🎉

Reviewers (4)
Reviewer Returned Main Findings Consider Pending Recs Discarded
pr-review-standards 0 0 0 0 0
pr-review-tests 3 0 3 0 0
pr-review-consistency 0 0 0 0 0
pr-review-errors 2 0 0 2 0
Total 5 0 3 2 0

Note: pr-review-standards and pr-review-consistency returned zero findings because all prior issues have been addressed in this commit.

@github-actions github-actions bot deleted a comment from claude bot Mar 23, 2026
The artifact generate-render route was passing raw schema to z.fromJSONSchema
without normalization, causing OpenAI strict-mode rejection when properties
exist but aren't all in required.
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

PR Review Summary

(0) Total Issues | Risk: Low

✅ Delta Review — Clean

This is a re-review scoped to 1 commit since the last automated review.

What changed:

  • agents-manage-ui/src/app/api/artifact-components/[artifactComponentId]/generate-render/route.ts: Added normalizeDataComponentSchema() call before passing artifactComponent.props to z.fromJSONSchema() — this mirrors the fix already applied to the data-component route.

Assessment: The change correctly addresses sarah-inkeep's request to apply the normalization fix to artifact components. The implementation is consistent with the data-component route (generate-render/route.ts lines 58-61) and properly handles the optional props field.

✅ Prior Issues Resolved

All Main issues from prior reviews have been addressed:

  1. ✅ SchemaProcessor duplicationmakeAllPropertiesRequired now delegates to makeAllPropertiesRequiredCore from agents-core (commit af5a9524b)
  2. ✅ Artifact schemas use full normalization — Both ArtifactCreateSchema.getSchemas() and ArtifactCreateSchema.getDataComponents() call normalizeDataComponentSchema() (commit af5a9524b)
  3. ✅ Artifact generate-render route normalized — This delta adds the missing normalization to the UI preview route (commit fcb5952ce)

🕐 Pending Recommendations (2)

These items were flagged in prior reviews but not specifically requested by the human reviewer. They remain as considerations for future work:

⚠️ Changeset Reminder

The changeset-bot flagged this PR has no changeset. This PR modifies agents-core, agents-api, and agents-manage-ui with runtime behavior changes. Consider adding:

pnpm bump patch --pkg agents-core --pkg agents-api "Fix data component schema normalization for cross-provider LLM compatibility"

✅ APPROVE

Summary: The human-requested fix has been implemented correctly. Artifact component schemas in the generate-render route now receive the same cross-provider normalization (constraint stripping + required promotion) as data components. All prior blocking issues have been addressed. Ship it! 🚀

Reviewers (1)
Reviewer Returned Main Findings Consider While You're Here Inline Comments Pending Recs Discarded
orchestrator 0 0 0 0 0 2 0
Total 0 0 0 0 0 2 0

Note: This is a delta review scoped to changes since the last automated review. No new issues were identified in the delta.

@github-actions github-actions bot deleted a comment from claude bot Mar 23, 2026
@itoqa
Copy link

itoqa bot commented Mar 23, 2026

Ito Test Report ❌

15 test cases ran. 3 failed, 12 passed.

Overall, 15 test cases ran with 12 passing and 3 failing, showing that core data/artifact generate and regenerate flows, runtime SSE compatibility, resilience under interruption/rage-click/cancel stress, prompt-injection and malicious-schema handling, and mobile viewport usability all worked as expected. The key confirmed issues are three pre-existing API defects in generate-render routes: unauthenticated access is not blocked (High), unknown component IDs return 500 instead of 404, and cross-tenant/not-found upstream errors are incorrectly collapsed into generic 500 responses (while missing tenantId/projectId validation correctly returns 400).

❌ Failed (3)
Category Summary Screenshot
Adversarial ⚠️ Logged-out users can still hit internal generate-render APIs because API routes bypass auth middleware coverage. ADV-3
Adversarial 🟠 Cross-tenant denial errors are masked as HTTP 500 due to route-level catch blocks that discard upstream status codes. ADV-4
Edge 🟠 Unknown component IDs return HTTP 500 instead of a controlled HTTP 404 response. EDGE-2
⚠️ Logged-out access to generate-render endpoints is blocked
  • What failed: Unauthenticated requests should be blocked by auth checks, but these API routes execute and reach backend fetch/generation code paths.
  • Impact: Unauthenticated callers can invoke internal generation endpoints that should require a signed-in session. This weakens tenant/project boundary enforcement and increases abuse risk for server-side generation resources.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Clear or remove auth session cookies in the browser context.
    2. Send POST requests to /api/data-components/{id}/generate-render and /api/artifact-components/{id}/generate-render with tenant and project IDs.
    3. Observe that the handlers execute backend fetch/generation logic instead of being rejected by auth middleware.
  • Code analysis: I reviewed the Next proxy matcher, both generate-render route handlers, and management API request configuration. The proxy explicitly excludes /api paths from auth redirect logic, route handlers perform no session/user validation before fetching component/project data, and server-side management calls can include a bypass Authorization header from env.
  • Why this is likely a bug: Production code paths show missing auth enforcement on these internal API handlers while middleware intentionally skips /api, so unauthorized execution is plausible without relying on test-only behavior.

Relevant code:

agents-manage-ui/src/proxy.ts (lines 104-107)

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|assets/|favicon\\.ico|manifest\\.json|api|monitoring).*)',
  ],
};

agents-manage-ui/src/app/api/data-components/[dataComponentId]/generate-render/route.ts (lines 23-34)

try {
  const { dataComponentId } = await context.params;
  const body = await request.json();
  const { tenantId, projectId, instructions, existingCode } = body;

  if (!tenantId || !projectId) {
    return new Response('Missing tenantId or projectId', { status: 400 });
  }

  const dataComponent = await fetchDataComponent(tenantId, projectId, dataComponentId);

agents-manage-ui/src/lib/api/api-config.ts (lines 67-69)

...(process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET && {
  Authorization: `Bearer ${process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}`,
}),
🟠 Cross-tenant body tampering does not bypass authorization
  • What failed: Authorization/not-found failures from upstream are converted to generic HTTP 500 responses instead of preserving accurate denial/not-found status.
  • Impact: Clients and operators receive misleading 500 errors instead of actionable authorization/not-found results. This obscures real failure causes and degrades incident triage and security signal quality.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Authenticate in the manage UI and capture a generate-render request.
    2. Modify tenantId or projectId in the request body to foreign or invalid values.
    3. Replay the request against both data and artifact generate-render routes.
    4. Observe that errors return as generic 500 responses instead of preserving denial/not-found status.
  • Code analysis: I traced error propagation from management API fetch helpers into both route handlers. The API layer preserves upstream status through ApiError.status, but both route handlers catch all errors and always return 500, masking the original response class.
  • Why this is likely a bug: The code already carries precise upstream status codes via ApiError, so converting all exceptions to 500 is a clear route-level logic defect rather than a test artifact.

Relevant code:

agents-manage-ui/src/lib/api/api-config.ts (lines 131-137)

throw new ApiError(
  {
    code: errorCode,
    message: errorMessage,
  },
  response.status
);

agents-manage-ui/src/app/api/data-components/[dataComponentId]/generate-render/route.ts (lines 119-123)

} catch (error) {
  console.error('Error generating component render:', error);
  return new Response(error instanceof Error ? error.message : 'Internal server error', {
    status: 500,
  });
}

agents-manage-ui/src/app/api/artifact-components/[artifactComponentId]/generate-render/route.ts (lines 124-128)

} catch (error) {
  console.error('Error generating artifact component render:', error);
  return new Response(error instanceof Error ? error.message : 'Internal server error', {
    status: 500,
  });
}
🟠 Unknown component IDs return 500 instead of controlled 404
  • What failed: The routes should return controlled not-found behavior (404), but upstream 404s are converted into thrown ApiError instances and final route responses become 500.
  • Impact: Invalid component IDs surface as internal server errors instead of expected not-found responses. This breaks error semantics for clients and can trigger unnecessary retry/error handling paths.
  • Introduced by this PR: No – pre-existing bug (code not changed in this PR)
  • Steps to reproduce:
    1. Authenticate in Manage UI and keep a valid tenantId and projectId.
    2. Send POST /api/data-components/nonexistent-id/generate-render with valid tenantId/projectId.
    3. Send POST /api/artifact-components/nonexistent-id/generate-render with valid tenantId/projectId.
    4. Observe HTTP 500 responses where controlled 404 not-found behavior is expected.
  • Code analysis: I traced the request path from each generate-render route into the shared management API client. The client throws on non-OK responses, so the route-level null guard is effectively bypassed and the route catch block returns 500.
  • Why this is likely a bug: The code path deterministically maps upstream not-found responses into thrown errors that are handled as 500s, which contradicts the route's intended not-found handling.

Relevant code:

agents-manage-ui/src/lib/api/api-config.ts (lines 131-137)

throw new ApiError(
  {
    code: errorCode,
    message: errorMessage,
  },
  response.status
);

agents-manage-ui/src/app/api/data-components/[dataComponentId]/generate-render/route.ts (lines 33-37)

const dataComponent = await fetchDataComponent(tenantId, projectId, dataComponentId);

if (!dataComponent) {
  return new Response('Data component not found', { status: 404 });
}

agents-manage-ui/src/app/api/artifact-components/[artifactComponentId]/generate-render/route.ts (lines 124-128)

} catch (error) {
  console.error('Error generating artifact component render:', error);
  return new Response(error instanceof Error ? error.message : 'Internal server error', {
    status: 500,
  });
}
✅ Passed (12)
Category Summary Screenshot
Adversarial Verified rage-click throttling behavior is enforced by UI generation state, with no app defect indicated. ADV-1
Adversarial Rapid regenerate/cancel interactions returned to a stable final state with coherent output and enabled controls. ADV-2
Adversarial Prompt-injection-like regenerate instructions completed without NDJSON parser crash or UI failure. ADV-5
Adversarial Malicious schema/sample strings did not trigger script execution or console-error instability. ADV-6
Adversarial Mobile viewport generate/regenerate flows, tab navigation, and sample-data editing were usable across data and artifact editors. ADV-7
Edge Data and artifact generate-render endpoints correctly return HTTP 400 when tenantId or projectId is missing. EDGE-1
Edge Executed interruption/back-forward/rage-click/regenerate-cancel resilience checks; editors recovered cleanly and stayed interactive. EDGE-8
Happy-path Data component generation completed with Render/Code/Sample Data populated under verified run conditions. ROUTE-1
Happy-path Artifact component generation completed with Render/Code/Sample Data populated. ROUTE-2
Happy-path /run/v1/chat/completions returned HTTP 200 SSE with terminal completion, confirming runtime compatibility for the normalized-schema flow in this run. ROUTE-4
Happy-path Not a real application bug; code-level verification confirms regenerate-with-instructions preserves existing mockData when model output omits it. ROUTE-5
Happy-path Artifact component generate/regenerate flow completed and remained coherent after refresh. ROUTE-6

Commit: fcb5952

View Full Run


Tell us how we did: Give Ito Feedback

@anubra266 anubra266 added this pull request to the merge queue Mar 24, 2026
Merged via the queue into main with commit 3bcdad3 Mar 24, 2026
19 of 20 checks passed
@anubra266 anubra266 deleted the prd-6070 branch March 24, 2026 09:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants