fix: tighten canvas operation protocol
This commit is contained in:
parent
64ba6a6f29
commit
d5ac630d01
@ -1,6 +1,13 @@
|
||||
import { z } from "zod";
|
||||
import type { CanvasOperationPlan } from "@/types/canvas/agent-protocol";
|
||||
|
||||
const httpMediaUrlSchema = z
|
||||
.string()
|
||||
.url()
|
||||
.refine((url) => /^https?:\/\//i.test(url), {
|
||||
message: "仅支持 http/https 媒体 URL",
|
||||
});
|
||||
|
||||
export const canvasAgentOperationPlanSchema = z
|
||||
.object({
|
||||
version: z.literal(1),
|
||||
@ -89,7 +96,7 @@ export const canvasAgentOperationPlanSchema = z
|
||||
media: z
|
||||
.object({
|
||||
type: z.enum(["image", "video"]),
|
||||
url: z.string().url(),
|
||||
url: httpMediaUrlSchema,
|
||||
assetId: z.string().uuid().optional(),
|
||||
width: z.number().positive().optional(),
|
||||
height: z.number().positive().optional(),
|
||||
@ -104,7 +111,7 @@ export const canvasAgentOperationPlanSchema = z
|
||||
z.object({
|
||||
mediaItemId: z.string().min(1).max(160),
|
||||
type: z.enum(["image", "video"]),
|
||||
url: z.string().url(),
|
||||
url: httpMediaUrlSchema,
|
||||
assetId: z.string().uuid().optional(),
|
||||
width: z.number().positive().optional(),
|
||||
height: z.number().positive().optional(),
|
||||
|
||||
@ -75,10 +75,14 @@ export async function handleCanvasAgentOperationRouteRequest(params: {
|
||||
},
|
||||
};
|
||||
}
|
||||
const operationPlan: CanvasOperationPlan = {
|
||||
...body.operationPlan,
|
||||
projectId: planProjectId || params.projectId,
|
||||
};
|
||||
const result = await params.applyOperation({
|
||||
userId,
|
||||
projectId: params.projectId,
|
||||
plan: body.operationPlan,
|
||||
plan: operationPlan,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -324,6 +324,30 @@ test.describe("canvas agent operation request schema", () => {
|
||||
],
|
||||
})
|
||||
).toContain("operationPlan.mediaItems.0.url");
|
||||
|
||||
expect(
|
||||
issuePathsFor({
|
||||
operation: "insert_beside",
|
||||
anchorNodeId: "image-1",
|
||||
mediaItems: [
|
||||
{
|
||||
...buildMediaItem(),
|
||||
url: "ftp://example.com/generated.png",
|
||||
},
|
||||
],
|
||||
})
|
||||
).toContain("operationPlan.mediaItems.0.url");
|
||||
|
||||
expect(
|
||||
issuePathsFor({
|
||||
operation: "append_revision",
|
||||
sourceNodeIds: ["image-1"],
|
||||
media: {
|
||||
type: "image",
|
||||
url: "file:///tmp/generated.png",
|
||||
},
|
||||
})
|
||||
).toContain("operationPlan.media.url");
|
||||
});
|
||||
});
|
||||
|
||||
@ -391,6 +415,7 @@ test.describe("canvas agent operation route handler", () => {
|
||||
const calls: Array<{
|
||||
userId: string;
|
||||
projectId: string;
|
||||
planProjectId?: string;
|
||||
operationId: string;
|
||||
}> = [];
|
||||
const expectedResult = buildRouteSuccessResult();
|
||||
@ -408,6 +433,7 @@ test.describe("canvas agent operation route handler", () => {
|
||||
calls.push({
|
||||
userId: input.userId,
|
||||
projectId: input.projectId,
|
||||
planProjectId: input.plan.projectId,
|
||||
operationId: input.plan.operationId,
|
||||
});
|
||||
return expectedResult;
|
||||
@ -423,11 +449,35 @@ test.describe("canvas agent operation route handler", () => {
|
||||
{
|
||||
userId: "user-1",
|
||||
projectId,
|
||||
planProjectId: projectId,
|
||||
operationId: "message-1:canvas-agent:test",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("fills missing operation plan project id from route project id", async () => {
|
||||
let appliedPlanProjectId = "";
|
||||
|
||||
const response = await handleCanvasAgentOperationRouteRequest({
|
||||
userId: "user-1",
|
||||
projectId,
|
||||
rawBody: buildRouteRequestBody({
|
||||
projectId: undefined,
|
||||
operation: "insert_beside",
|
||||
anchorNodeId: "image-1",
|
||||
mediaItems: [buildMediaItem(1)],
|
||||
}),
|
||||
features: enabledRouteFeatures,
|
||||
applyOperation: async (input) => {
|
||||
appliedPlanProjectId = String(input.plan.projectId || "");
|
||||
return buildRouteSuccessResult();
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(appliedPlanProjectId).toBe(projectId);
|
||||
});
|
||||
|
||||
test("rejects operation plans for a different project before applying", async () => {
|
||||
let operationApplied = false;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user