mirror of
https://github.com/harivansh-afk/clanker-agent.git
synced 2026-04-15 07:04:45 +00:00
- Copy all pi-mono source into apps/companion-os/ - Update Dockerfile to COPY pre-built binary instead of downloading from GitHub Releases - Update deploy-staging.yml to build pi from source (bun compile) before Docker build - Add apps/companion-os/** to path triggers - No more cross-repo dispatch needed Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
import type { SandboxRuntimeProvider } from "./SandboxRuntimeProvider.js";
|
|
|
|
export interface DownloadableFile {
|
|
fileName: string;
|
|
content: string | Uint8Array;
|
|
mimeType: string;
|
|
}
|
|
|
|
/**
|
|
* File Download Runtime Provider
|
|
*
|
|
* Provides returnDownloadableFile() for creating user downloads.
|
|
* Files returned this way are NOT accessible to the LLM later (one-time download).
|
|
* Works both online (sends to extension) and offline (triggers browser download directly).
|
|
* Collects files for retrieval by caller.
|
|
*/
|
|
export class FileDownloadRuntimeProvider implements SandboxRuntimeProvider {
|
|
private files: DownloadableFile[] = [];
|
|
|
|
getData(): Record<string, any> {
|
|
// No data needed
|
|
return {};
|
|
}
|
|
|
|
getRuntime(): (sandboxId: string) => void {
|
|
return (_sandboxId: string) => {
|
|
(window as any).returnDownloadableFile = async (
|
|
fileName: string,
|
|
content: any,
|
|
mimeType?: string,
|
|
) => {
|
|
let finalContent: any, finalMimeType: string;
|
|
|
|
if (content instanceof Blob) {
|
|
const arrayBuffer = await content.arrayBuffer();
|
|
finalContent = new Uint8Array(arrayBuffer);
|
|
finalMimeType =
|
|
mimeType || content.type || "application/octet-stream";
|
|
if (!mimeType && !content.type) {
|
|
throw new Error(
|
|
"returnDownloadableFile: MIME type is required for Blob content. Please provide a mimeType parameter (e.g., 'image/png').",
|
|
);
|
|
}
|
|
} else if (content instanceof Uint8Array) {
|
|
finalContent = content;
|
|
if (!mimeType) {
|
|
throw new Error(
|
|
"returnDownloadableFile: MIME type is required for Uint8Array content. Please provide a mimeType parameter (e.g., 'image/png').",
|
|
);
|
|
}
|
|
finalMimeType = mimeType;
|
|
} else if (typeof content === "string") {
|
|
finalContent = content;
|
|
finalMimeType = mimeType || "text/plain";
|
|
} else {
|
|
finalContent = JSON.stringify(content, null, 2);
|
|
finalMimeType = mimeType || "application/json";
|
|
}
|
|
|
|
// Send to extension if in extension context (online mode)
|
|
if ((window as any).sendRuntimeMessage) {
|
|
const response = await (window as any).sendRuntimeMessage({
|
|
type: "file-returned",
|
|
fileName,
|
|
content: finalContent,
|
|
mimeType: finalMimeType,
|
|
});
|
|
if (response.error) throw new Error(response.error);
|
|
} else {
|
|
// Offline mode: trigger browser download directly
|
|
const blob = new Blob(
|
|
[finalContent instanceof Uint8Array ? finalContent : finalContent],
|
|
{
|
|
type: finalMimeType,
|
|
},
|
|
);
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = fileName;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
};
|
|
};
|
|
}
|
|
|
|
async handleMessage(
|
|
message: any,
|
|
respond: (response: any) => void,
|
|
): Promise<void> {
|
|
if (message.type === "file-returned") {
|
|
// Collect file for caller
|
|
this.files.push({
|
|
fileName: message.fileName,
|
|
content: message.content,
|
|
mimeType: message.mimeType,
|
|
});
|
|
|
|
respond({ success: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get collected files
|
|
*/
|
|
getFiles(): DownloadableFile[] {
|
|
return this.files;
|
|
}
|
|
|
|
/**
|
|
* Reset state for reuse
|
|
*/
|
|
reset(): void {
|
|
this.files = [];
|
|
}
|
|
|
|
getDescription(): string {
|
|
return "returnDownloadableFile(filename, content, mimeType?) - Create downloadable file for user (one-time download, not accessible later)";
|
|
}
|
|
}
|