Merge pull request #7248 from paperclipai/PAP-10162-pap-10161-made-a-video-how-can-i-see-it-i-m-accessing-this-instance-through-the-cloud-and-don-t-have-access-to

Add issue artifact upload and output playback
This commit is contained in:
Dotta 2026-05-31 20:44:08 -10:00 committed by GitHub
commit 9f8636cf49
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1969 additions and 24 deletions

View file

@ -411,6 +411,7 @@ export type {
DocumentTextProjection,
DocumentTextRange,
UpdateDocumentAnnotationThreadRequest,
AttachmentArtifactWorkProductMetadata,
Issue,
IssueAssigneeAdapterOverrides,
IssueBlockerAttention,
@ -921,6 +922,7 @@ export {
createIssueAttachmentMetadataSchema,
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
attachmentArtifactWorkProductMetadataSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,

View file

@ -172,6 +172,7 @@ export type {
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
AttachmentArtifactWorkProductMetadata,
} from "./work-product.js";
export type {
Issue,

View file

@ -845,4 +845,6 @@ export interface IssueAttachment {
createdAt: Date;
updatedAt: Date;
contentPath: string;
openPath?: string;
downloadPath?: string;
}

View file

@ -53,3 +53,13 @@ export interface IssueWorkProduct {
createdAt: Date;
updatedAt: Date;
}
export interface AttachmentArtifactWorkProductMetadata {
attachmentId: string;
contentType: string;
byteSize: number;
contentPath: string;
openPath: string;
downloadPath: string;
originalFilename?: string | null;
}

View file

@ -282,6 +282,7 @@ export {
export {
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
attachmentArtifactWorkProductMetadataSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,

View file

@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { attachmentArtifactWorkProductMetadataSchema } from "./work-product.js";
describe("attachmentArtifactWorkProductMetadataSchema", () => {
it("accepts the attachment-backed artifact metadata contract", () => {
const parsed = attachmentArtifactWorkProductMetadataSchema.parse({
attachmentId: "11111111-1111-4111-8111-111111111111",
contentType: "video/mp4",
byteSize: 1234,
contentPath: "/api/attachments/11111111-1111-4111-8111-111111111111/content",
openPath: "/api/attachments/11111111-1111-4111-8111-111111111111/content",
downloadPath: "/api/attachments/11111111-1111-4111-8111-111111111111/content?download=1",
originalFilename: "demo.mp4",
});
expect(parsed.contentType).toBe("video/mp4");
expect(parsed.downloadPath).toContain("download=1");
});
it("rejects off-route or scriptable paths", () => {
const parsed = attachmentArtifactWorkProductMetadataSchema.safeParse({
attachmentId: "11111111-1111-4111-8111-111111111111",
contentType: "video/mp4",
byteSize: 1234,
contentPath: "https://evil.example/video.mp4",
openPath: "javascript:alert(1)",
downloadPath: "/api/attachments/11111111-1111-4111-8111-111111111111/content",
originalFilename: "demo.mp4",
});
expect(parsed.success).toBe(false);
if (parsed.success) {
throw new Error("Expected invalid attachment artifact metadata");
}
expect(parsed.error.issues.map((issue) => issue.path.join("."))).toEqual([
"contentPath",
"openPath",
"downloadPath",
]);
});
});

View file

@ -1,5 +1,9 @@
import { z } from "zod";
function attachmentContentPath(attachmentId: string): string {
return `/api/attachments/${attachmentId}/content`;
}
export const issueWorkProductTypeSchema = z.enum([
"preview_url",
"runtime_service",
@ -29,6 +33,41 @@ export const issueWorkProductReviewStateSchema = z.enum([
"changes_requested",
]);
export const attachmentArtifactWorkProductMetadataSchema = z.object({
attachmentId: z.string().uuid(),
contentType: z.string().min(1),
byteSize: z.number().int().nonnegative(),
contentPath: z.string().min(1),
openPath: z.string().min(1),
downloadPath: z.string().min(1),
originalFilename: z.string().optional().nullable(),
}).superRefine((value, ctx) => {
const contentPath = attachmentContentPath(value.attachmentId);
if (value.contentPath !== contentPath) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["contentPath"],
message: "contentPath must point to the same-origin attachment content route",
});
}
if (value.openPath !== contentPath) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["openPath"],
message: "openPath must point to the same-origin attachment content route",
});
}
if (value.downloadPath !== `${contentPath}?download=1`) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ["downloadPath"],
message: "downloadPath must point to the same-origin attachment download route",
});
}
});
export type AttachmentArtifactWorkProductMetadata = z.infer<typeof attachmentArtifactWorkProductMetadataSchema>;
export const createIssueWorkProductSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),