import { expect, test } from "@playwright/test"; import { buildOpenClawArtifactProxyUrl, extractOpenClawArtifacts, isOpenClawInteractive3DArtifact, normalizeOpenClawArtifacts, } from "@/lib/openclaw-chat/artifacts"; import { normalizeClawdTaskMetadata } from "@/lib/openclaw-chat/task-state"; const modelFile = { type: "file", filename: "wood-demo-interactive.glb", display_name: "可旋转3D拆件模型", artifact_role: "interactive_3d", display_mode: "interactive", mime_type: "model/gltf-binary", download_url: "/files/wood-demo-interactive.glb", viewer: { type: "model_3d", format: "glb", camera_controls: true, auto_rotate: false, background: "#F7F9FC", fallback_artifact_role: "cad_preview", }, }; const previewFile = { type: "image", filename: "wood-demo-preview.png", artifact_role: "cad_preview", display_mode: "inline", mime_type: "image/png", download_url: "https://files.example.com/wood-demo-preview.png", }; test.describe("OpenClaw 3D artifacts", () => { test("normalizes the documented GLB viewer contract", () => { const artifacts = normalizeOpenClawArtifacts([modelFile, previewFile], { baseUrl: "https://claw.example.com", }); expect(artifacts).toHaveLength(2); expect(artifacts[0]).toMatchObject({ artifactRole: "interactive_3d", displayMode: "interactive", displayName: "可旋转3D拆件模型", downloadUrl: "https://claw.example.com/files/wood-demo-interactive.glb", mimeType: "model/gltf-binary", viewer: { type: "model_3d", format: "glb", cameraControls: true, autoRotate: false, background: "#F7F9FC", fallbackArtifactRole: "cad_preview", }, }); expect(isOpenClawInteractive3DArtifact(artifacts[0])).toBe(true); expect(isOpenClawInteractive3DArtifact(artifacts[1])).toBe(false); }); test("extracts files from durable task results without duplicating them", () => { const artifacts = extractOpenClawArtifacts( { output: { files: [modelFile, previewFile] }, task: { result: { files: [modelFile] } }, }, { baseUrl: "https://claw.example.com" } ); expect(artifacts).toHaveLength(2); expect(artifacts.map((artifact) => artifact.artifactRole)).toEqual([ "interactive_3d", "cad_preview", ]); }); test("rejects private schemes and keeps the original URL out of proxy links", () => { expect( normalizeOpenClawArtifacts([ { ...modelFile, download_url: "file:///tmp/private.glb" }, { ...modelFile, download_url: "javascript:alert(1)" }, ]) ).toEqual([]); const proxyUrl = buildOpenClawArtifactProxyUrl({ chatId: "11111111-1111-4111-8111-111111111111", messageId: "22222222-2222-4222-8222-222222222222", artifactIndex: 0, download: true, }); expect(proxyUrl).toContain("download=1"); expect(proxyUrl).not.toContain("files.example.com"); }); test("restores persisted artifacts with Claw task metadata", () => { const task = normalizeClawdTaskMetadata({ composerMode: "clawd", source: "openclaw", taskKind: "clawd_chat", status: "completed", userMessageId: "user-message-id", assistantMessageId: "assistant-message-id", processingHint: "", progressEvents: [], artifacts: [ { ...modelFile, download_url: "https://files.example.com/model.glb", }, ], createdAt: 100, updatedAt: 200, }); expect(task?.artifacts).toHaveLength(1); expect(task?.artifacts?.[0].viewer?.format).toBe("glb"); }); });