| 1 | import type { AppBindings } from "./bridge"; |
| 2 | import type { ComposerTarget } from "../generated/desktopContract.generated"; |
| 3 | |
| 4 | export interface AttachmentBindings { |
| 5 | StageImageForTab(tabID: string, operationID: string, displayName: string, mime: string, dataUrl: string): Promise<{ draftId: string; displayName: string; mime: string; width: number; height: number; bytes: number }>; |
| 6 | ReadDraftImageForTab(tabID: string, draftID: string): Promise<string>; |
| 7 | ReleaseDraftImageForTab(tabID: string, draftID: string): Promise<void>; |
| 8 | SavePastedImageForTab(tabID: string, dataURL: string): Promise<string>; |
| 9 | SavePastedFileForTab(tabID: string, name: string, dataURL: string): Promise<string>; |
| 10 | SaveClipboardImageForTab(tabID: string): Promise<string>; |
| 11 | AttachmentDataURLForTab(tabID: string, path: string): Promise<string>; |
| 12 | AttachDroppedForTab(tabID: string, path: string): Promise<{ kind: string; path: string; isDir?: boolean; displayPath?: string; previewUrl?: string }>; |
| 13 | ReadSessionAttachmentForTab(tabID: string, digest: string, offset: number): Promise<{ data: string; nextOffset: number; done: boolean }>; |
| 14 | StartTurnForTabWithDrafts(tabID: string, input: string, submissionID: string, draftIDs: string[]): Promise<{ turnId: string; status: string; submissionId: string }>; |
| 15 | CaptureAttachmentTarget?(target: ComposerTarget): Promise<{ token: string; capabilities: string[] }>; |
| 16 | ReleaseAttachmentTarget?(token: string): Promise<void>; |
| 17 | SavePastedFileForTarget?(token: string, name: string, dataURL: string): Promise<string>; |
| 18 | SaveClipboardImageForTarget?(token: string): Promise<string>; |
| 19 | AttachmentDataURLForTarget?(token: string, path: string): Promise<string>; |
| 20 | AttachDroppedForTarget?(token: string, path: string): Promise<{ kind: string; path: string; isDir?: boolean; displayPath?: string; previewUrl?: string }>; |
| 21 | StageImageForTarget?(token: string, operationID: string, displayName: string, mime: string, dataUrl: string): Promise<{ draftId: string; path?: string; displayName: string; mime: string; width: number; height: number; bytes: number }>; |
| 22 | ReadDraftImageForTarget?(token: string, draftID: string): Promise<string>; |
| 23 | RebindDraftImageForTarget?(token: string, draftID: string): Promise<{ draftId: string; displayName: string; mime: string; width: number; height: number; bytes: number }>; |
| 24 | ReleaseDraftImageForTarget?(token: string, draftID: string): Promise<void>; |
| 25 | StartTurnForAttachmentTarget?(token: string, submissionID: string, request: { input: string; display: string; original?: string; goal?: string; toolApprovalMode?: string; invocations?: import("./invocationDisplay").InvocationRequest[]; attachments: import("./invocationDisplay").SubmissionAttachment[] }): Promise<{ turnId: string; status: string; submissionId: string }>; |
| 26 | EnqueueForAttachmentTarget?(token: string, submissionID: string, input: string, display: string, invocations: import("./invocationDisplay").InvocationRequest[], attachments: import("./invocationDisplay").SubmissionAttachment[]): Promise<{ itemId: string; paused?: boolean; error?: string }>; |
| 27 | } |
| 28 | |
| 29 | const mockImages = new Map<string, string>(); |
| 30 | const mockPreviewImageDataURL = |
| 31 | "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='120' viewBox='0 0 160 120'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%23f97316'/%3E%3Cstop offset='1' stop-color='%232563eb'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='160' height='120' rx='14' fill='url(%23g)'/%3E%3Ccircle cx='44' cy='38' r='16' fill='%23fff7ed' opacity='.9'/%3E%3Cpath d='M18 96 62 58l24 22 18-16 38 32z' fill='%23ffffff' opacity='.9'/%3E%3C/svg%3E"; |
| 32 | |
| 33 | export function makeMockAttachmentBindings(): AttachmentBindings & Partial<AppBindings> & ThisType<AppBindings> { |
| 34 | return { |
| 35 | async StageImageForTab(_tabID: string, _operation: string, displayName: string, mime: string, dataURL: string) { |
| 36 | const draftId = crypto.randomUUID().replaceAll("-", ""); |
| 37 | mockImages.set(draftId, dataURL); |
| 38 | return { draftId, displayName, mime, width: 1, height: 1, bytes: dataURL.length }; |
| 39 | }, |
| 40 | async ReadDraftImageForTab(_tabID: string, id: string) { |
| 41 | const image = mockImages.get(id); |
| 42 | if (!image) throw new Error("draft missing"); |
| 43 | return image; |
| 44 | }, |
| 45 | async CaptureAttachmentTarget(target: ComposerTarget) { |
| 46 | return { token: target.tabId || target.draftId || "mock-attachment-target", capabilities: ["attachments-v2"] }; |
| 47 | }, |
| 48 | async ReleaseAttachmentTarget(_token: string) {}, |
| 49 | async StageImageForTarget(_token: string, _operation: string, displayName: string, mime: string, dataURL: string) { |
| 50 | const draftId = crypto.randomUUID().replaceAll("-", ""); |
| 51 | mockImages.set(draftId, dataURL); |
| 52 | return { draftId, displayName, mime, width: 1, height: 1, bytes: dataURL.length }; |
| 53 | }, |
| 54 | async ReadDraftImageForTarget(_token: string, id: string) { const image = mockImages.get(id); if (!image) throw new Error("draft missing"); return image; }, |
| 55 | async ReleaseDraftImageForTarget(_token: string, id: string) { mockImages.delete(id); }, |
| 56 | async ReleaseDraftImageForTab(_tabID: string, id: string) { mockImages.delete(id); }, |
| 57 | async SavePastedImage(dataURL: string) { |
| 58 | const path = `.reasonix/attachments/mock-${mockImages.size + 1}.png`; |
| 59 | mockImages.set(path, dataURL); |
| 60 | return path; |
| 61 | }, |
| 62 | async SavePastedImageForComposerTarget(_target: ComposerTarget, dataURL: string) { return this.SavePastedImage(dataURL); }, |
| 63 | async SavePastedImageForTab(_tabID: string, dataURL: string) { return this.SavePastedImage(dataURL); }, |
| 64 | async SavePastedFile(name: string, dataURL: string) { |
| 65 | const path = `.reasonix/attachments/mock-${name}`; |
| 66 | mockImages.set(path, dataURL); |
| 67 | return path; |
| 68 | }, |
| 69 | async SavePastedFileForComposerTarget(_target: ComposerTarget, name: string, dataURL: string) { return this.SavePastedFile(name, dataURL); }, |
| 70 | async SavePastedFileForTarget(token: string, name: string, dataURL: string) { return this.SavePastedFileForTab(token,name,dataURL); }, |
| 71 | async SavePastedFileForTab(_tabID: string, name: string, dataURL: string) { return this.SavePastedFile(name, dataURL); }, |
| 72 | async SaveClipboardImage() { |
| 73 | const path = `.reasonix/attachments/mock-clipboard-${mockImages.size + 1}.png`; |
| 74 | mockImages.set(path, mockPreviewImageDataURL); |
| 75 | return path; |
| 76 | }, |
| 77 | async SaveClipboardImageForComposerTarget(_target: ComposerTarget) { return this.SaveClipboardImage(); }, |
| 78 | async SaveClipboardImageForTarget(token: string) { return this.SaveClipboardImageForTab(token); }, |
| 79 | async SaveClipboardImageForTab(_tabID: string) { return this.SaveClipboardImage(); }, |
| 80 | async AttachmentDataURL(path: string) { return mockImages.get(path) ?? mockPreviewImageDataURL; }, |
| 81 | async AttachmentDataURLForComposerTarget(_target: ComposerTarget, path: string) { return this.AttachmentDataURL(path); }, |
| 82 | async AttachmentDataURLForTarget(token: string, path: string) { return this.AttachmentDataURLForTab(token,path); }, |
| 83 | async AttachmentDataURLForTab(_tabID: string, path: string) { return this.AttachmentDataURL(path); }, |
| 84 | async AttachDropped(path: string) { |
| 85 | const name = path.split(/[/\\]/).filter(Boolean).pop() ?? path; |
| 86 | if (!/\.\w{1,6}$/i.test(name)) { |
| 87 | const tokenName = name.replace(/[^\w.-]+/g, "-") || "folder"; |
| 88 | return { kind: "workspace", path: `__reasonix_external_folder/mock/${tokenName}`, isDir: true, displayPath: path }; |
| 89 | } |
| 90 | const attachmentPath = `.reasonix/attachments/mock-${name}`; |
| 91 | mockImages.set(attachmentPath, mockPreviewImageDataURL); |
| 92 | return { kind: "attachment", path: attachmentPath }; |
| 93 | }, |
| 94 | async AttachDroppedForComposerTarget(_target: ComposerTarget, path: string) { return this.AttachDropped(path); }, |
| 95 | async AttachDroppedForTarget(token: string, path: string) { return this.AttachDroppedForTab(token,path); }, |
| 96 | async AttachDroppedForTab(_tabID: string, path: string) { return this.AttachDropped(path); }, |
| 97 | async ReadSessionAttachmentForTab(_tabID: string, digest: string, offset: number) { |
| 98 | const image = mockImages.get(digest) ?? "data:image/png;base64,"; |
| 99 | const data = image.split(",", 2)[1] ?? ""; |
| 100 | return { data: offset === 0 ? data : "", nextOffset: data.length, done: true }; |
| 101 | }, |
| 102 | async StartTurnForTabWithDrafts(tabID: string, input: string, submissionID: string) { |
| 103 | if (this.StartTurnForTab) { |
| 104 | const receipt = await this.StartTurnForTab(tabID, input, submissionID); |
| 105 | return { turnId: receipt.turnId, status: receipt.status, submissionId: receipt.submissionId || submissionID }; |
| 106 | } |
| 107 | await this.SubmitToTabWithID(tabID, input, submissionID); |
| 108 | return { turnId: submissionID, status: "queued", submissionId: submissionID }; |
| 109 | }, |
| 110 | async StartTurnForAttachmentTarget(token: string, submissionID: string, request: {input:string;display:string}) { |
| 111 | await this.SubmitDisplayToTabWithID(token,request.display,request.input,submissionID); |
| 112 | return {turnId:submissionID,status:"queued",submissionId:submissionID}; |
| 113 | }, |
| 114 | async EnqueueForAttachmentTarget(token: string, id: string, input: string, display: string) { return this.EnqueueInboxFollowup(token,display,input,id); }, |
| 115 | }; |
| 116 | } |
| 117 | |
| 118 | export function callMockAttachment( |
| 119 | owner: AppBindings, |
| 120 | name: keyof AttachmentBindings, |
| 121 | args: unknown[], |
| 122 | ): unknown { |
| 123 | const method = makeMockAttachmentBindings()[name] as (...values: unknown[]) => unknown; |
| 124 | return method.apply(owner, args); |
| 125 | } |
| 126 |