Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

68 lines
2.3 KiB
TypeScript

import { z } from "zod";
import type { GenerativeUISpec } from "@assistant-ui/react";
export const RENDER_GUI_TOOL_NAME = "render_gui" as const;
const generativeUINodeSchema: z.ZodType<unknown> = z.lazy(() =>
z.union([
z.string(),
z.object({
component: z.string().min(1),
props: z.record(z.string(), z.unknown()).optional(),
children: z.array(generativeUINodeSchema).optional(),
key: z.string().optional(),
}),
]),
);
export const generativeUISpecSchema = z.object({
root: z.union([
generativeUINodeSchema,
z.array(generativeUINodeSchema).min(1),
]),
});
export const renderGuiToolInputSchema = z.object({
spec: generativeUISpecSchema,
});
export const renderGuiToolResultSchema = renderGuiToolInputSchema;
export const parseRenderGuiResult = (
result: unknown,
): GenerativeUISpec | undefined => {
const parsed = renderGuiToolResultSchema.safeParse(result);
return parsed.success ? (parsed.data.spec as GenerativeUISpec) : undefined;
};
export const renderGuiToolDescription =
"Compose inline UI from the allowlisted component library (Card, Text, Button, Stack, Stat, Heading). " +
"Pass a JSON spec with a root node tree. Use for dashboards, status panels, and structured layouts — not for user input forms (use interactive tool UI instead).";
export const renderGuiChatInstructions =
"When the user asks for UI, call render_gui with the spec. " +
"Do not paste JSON, code blocks, or the tool result in your text reply — the client renders the UI from the tool output automatically. " +
"Keep any text reply brief or omit it when the UI alone answers the request.";
const stripMarkdownJsonFence = (text: string): string => {
const trimmed = text.trim();
const match = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```\s*$/i);
return match ? match[1]!.trim() : trimmed;
};
/** Text echo of a render_gui tool result/spec — hide when the bridge already rendered it. */
export const isLeakedRenderGuiText = (text: string): boolean => {
const candidate = stripMarkdownJsonFence(text);
if (!candidate.startsWith("{")) return false;
try {
const parsed = JSON.parse(candidate);
if (renderGuiToolResultSchema.safeParse(parsed).success) return true;
if (generativeUISpecSchema.safeParse(parsed).success) return true;
} catch {
return false;
}
return false;
};