mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-02 21:32:10 +03:00
fix(agent-bridge): strip non-serializable handler before Server→Client boundary
The /dashboard/tools/agent-bridge page (Server Component) passed ALL_TARGETS
directly to AgentBridgePageClient (a Client Component). Each MitmTarget carries
a `handler: () => Promise<...>` function, which Next.js forbids across the
Server/Client boundary, raising at SSR time:
"Functions cannot be passed directly to Client Components ..."
This broke the whole page ("erro ao carregar").
Fix: introduce MitmTargetView = Omit<MitmTarget, "handler"> and pass a
sanitized array (ALL_TARGETS.map(({ handler, ...rest }) => rest)). The UI never
invokes handler, so behavior is unchanged. Adds a regression test asserting the
sanitized targets are function-free and JSON-serializable.
This commit is contained in:
@@ -8,7 +8,7 @@ import { AgentBridgeServerCard } from "./components/AgentBridgeServerCard";
|
||||
import { AgentList } from "./components/AgentList";
|
||||
import { EmptyStateNoProviders } from "./components/EmptyStateNoProviders";
|
||||
import { useAgentBridgeState } from "./hooks/useAgentBridgeState";
|
||||
import type { MitmTarget } from "@/mitm/types";
|
||||
import type { MitmTargetView } from "@/mitm/types";
|
||||
import type { MappingRow } from "./components/ModelMappingTable";
|
||||
|
||||
// ── Types ────────────────────────────────────────────────────────────────────
|
||||
@@ -43,7 +43,7 @@ export interface AgentBridgePageData {
|
||||
|
||||
interface AgentBridgePageClientProps {
|
||||
initialData: AgentBridgePageData;
|
||||
targets: MitmTarget[];
|
||||
targets: MitmTargetView[];
|
||||
hasProviders: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DnsStatusBadge } from "./shared/DnsStatusBadge";
|
||||
import { ModelMappingTable } from "./ModelMappingTable";
|
||||
import { SetupWizard } from "./SetupWizard";
|
||||
import { RiskNoticeModal } from "@/shared/components/RiskNoticeModal";
|
||||
import type { MitmTarget } from "@/mitm/types";
|
||||
import type { MitmTargetView } from "@/mitm/types";
|
||||
import type { AgentStateEntry } from "../AgentBridgePageClient";
|
||||
import type { MappingRow } from "./ModelMappingTable";
|
||||
|
||||
@@ -23,7 +23,7 @@ function hasAcceptedRisk(agentId: string): boolean {
|
||||
|
||||
|
||||
interface AgentCardProps {
|
||||
target: MitmTarget;
|
||||
target: MitmTargetView;
|
||||
agentState: AgentStateEntry | undefined;
|
||||
serverRunning: boolean;
|
||||
mappings: MappingRow[];
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { AgentCard } from "./AgentCard";
|
||||
import type { MitmTarget } from "@/mitm/types";
|
||||
import type { MitmTargetView } from "@/mitm/types";
|
||||
import type { AgentStateEntry, AgentMappingsMap } from "../AgentBridgePageClient";
|
||||
import type { MappingRow } from "./ModelMappingTable";
|
||||
|
||||
interface AgentListProps {
|
||||
targets: MitmTarget[];
|
||||
targets: MitmTargetView[];
|
||||
agentStates: AgentStateEntry[];
|
||||
serverRunning: boolean;
|
||||
mappingsMap: AgentMappingsMap;
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { AgentStateEntry } from "../AgentBridgePageClient";
|
||||
import type { MitmTarget } from "@/mitm/types";
|
||||
import type { MitmTargetView } from "@/mitm/types";
|
||||
|
||||
interface SetupWizardProps {
|
||||
target: MitmTarget;
|
||||
target: MitmTargetView;
|
||||
agentState: AgentStateEntry | undefined;
|
||||
serverRunning: boolean;
|
||||
onClose: () => void;
|
||||
|
||||
@@ -54,7 +54,7 @@ export default async function AgentBridgePage() {
|
||||
return (
|
||||
<AgentBridgePageClient
|
||||
initialData={initialData}
|
||||
targets={ALL_TARGETS}
|
||||
targets={ALL_TARGETS.map(({ handler, ...rest }) => rest)}
|
||||
hasProviders={hasProviders}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -38,6 +38,13 @@ export interface MitmTarget {
|
||||
viability?: "investigating" | "supported" | "deprecated"; // Trae = "investigating"
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializable view of a MitmTarget for Server→Client Component props.
|
||||
* Omits `handler` (a function): Next.js forbids passing functions across the
|
||||
* Server/Client boundary, and the UI never invokes it. See agent-bridge/page.tsx.
|
||||
*/
|
||||
export type MitmTargetView = Omit<MitmTarget, "handler">;
|
||||
|
||||
export const MitmTargetSchema = z.object({
|
||||
id: z.enum([
|
||||
"antigravity", "kiro", "copilot", "codex", "cursor", "zed",
|
||||
|
||||
33
tests/unit/agent-bridge-targets-serializable.test.ts
Normal file
33
tests/unit/agent-bridge-targets-serializable.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ALL_TARGETS } from "../../src/mitm/targets/index.ts";
|
||||
|
||||
// Regression guard for the agent-bridge "erro ao carregar" bug.
|
||||
//
|
||||
// `agent-bridge/page.tsx` is a Server Component that passes `targets` to the
|
||||
// `AgentBridgePageClient` Client Component. Each MitmTarget carries a
|
||||
// `handler: () => Promise<...>` function. Next.js forbids passing functions
|
||||
// across the Server/Client boundary, raising at runtime:
|
||||
// "Functions cannot be passed directly to Client Components ..."
|
||||
// which broke SSR for the whole page. The fix sanitizes the array via
|
||||
// ALL_TARGETS.map(({ handler, ...rest }) => rest)
|
||||
// (a MitmTargetView). These tests pin both halves of that contract.
|
||||
|
||||
test("agent-bridge: sanitized targets (no handler) are fully serializable for Client Components", () => {
|
||||
const views = ALL_TARGETS.map(({ handler, ...rest }) => rest);
|
||||
assert.ok(views.length > 0, "expected at least one MITM target");
|
||||
for (const v of views) {
|
||||
const id = (v as { id?: string }).id ?? "<unknown>";
|
||||
assert.equal("handler" in v, false, `${id}: handler must be stripped before crossing to a Client Component`);
|
||||
for (const [key, value] of Object.entries(v)) {
|
||||
assert.notEqual(typeof value, "function", `${id}.${key} must not be a function (non-serializable)`);
|
||||
}
|
||||
assert.doesNotThrow(() => JSON.parse(JSON.stringify(v)), `${id} must be JSON-serializable`);
|
||||
}
|
||||
});
|
||||
|
||||
test("agent-bridge: raw ALL_TARGETS still carry a handler function (so the sanitization is required)", () => {
|
||||
for (const t of ALL_TARGETS) {
|
||||
assert.equal(typeof t.handler, "function", `${t.id} should expose a lazy handler function`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user