mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Prepare prompt file attachments for future UI (#1376)
* Support file uploads to agents * Harden prompt file upload handling * Fix duplicate upload retry handling * Update relay reconnect tests for queued messages * Make upload timeout track idle progress * Clean failed upload directories
This commit is contained in:
53
packages/protocol/src/binary-frames/demux.test.ts
Normal file
53
packages/protocol/src/binary-frames/demux.test.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
decodeBinaryFrame,
|
||||
encodeFileTransferFrame,
|
||||
encodeTerminalStreamFrame,
|
||||
FileTransferOpcode,
|
||||
TerminalStreamOpcode,
|
||||
} from "./index.js";
|
||||
|
||||
describe("binary frame demux", () => {
|
||||
it("routes terminal frames by opcode", () => {
|
||||
expect(
|
||||
decodeBinaryFrame(
|
||||
encodeTerminalStreamFrame({
|
||||
opcode: TerminalStreamOpcode.Input,
|
||||
slot: 7,
|
||||
payload: "ls",
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
kind: "terminal",
|
||||
frame: {
|
||||
opcode: TerminalStreamOpcode.Input,
|
||||
slot: 7,
|
||||
payload: new TextEncoder().encode("ls"),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("routes file-transfer frames by opcode", () => {
|
||||
expect(
|
||||
decodeBinaryFrame(
|
||||
encodeFileTransferFrame({
|
||||
opcode: FileTransferOpcode.FileChunk,
|
||||
requestId: "req-upload",
|
||||
payload: new TextEncoder().encode("hello"),
|
||||
}),
|
||||
),
|
||||
).toEqual({
|
||||
kind: "file_transfer",
|
||||
frame: {
|
||||
opcode: FileTransferOpcode.FileChunk,
|
||||
requestId: "req-upload",
|
||||
payload: new TextEncoder().encode("hello"),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unknown binary opcodes", () => {
|
||||
expect(decodeBinaryFrame(new Uint8Array([0xff, 0]))).toBeNull();
|
||||
});
|
||||
});
|
||||
35
packages/protocol/src/binary-frames/demux.ts
Normal file
35
packages/protocol/src/binary-frames/demux.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
decodeFileTransferFrame,
|
||||
FileTransferOpcode,
|
||||
type FileTransferFrame,
|
||||
} from "./file-transfer.js";
|
||||
import {
|
||||
decodeTerminalStreamFrame,
|
||||
TerminalStreamOpcode,
|
||||
type TerminalStreamFrame,
|
||||
} from "./terminal.js";
|
||||
|
||||
export type BinaryFrame =
|
||||
| { kind: "terminal"; frame: TerminalStreamFrame }
|
||||
| { kind: "file_transfer"; frame: FileTransferFrame };
|
||||
|
||||
export function decodeBinaryFrame(bytes: Uint8Array): BinaryFrame | null {
|
||||
switch (bytes[0]) {
|
||||
case TerminalStreamOpcode.Output:
|
||||
case TerminalStreamOpcode.Input:
|
||||
case TerminalStreamOpcode.Resize:
|
||||
case TerminalStreamOpcode.Snapshot:
|
||||
case TerminalStreamOpcode.Restore: {
|
||||
const frame = decodeTerminalStreamFrame(bytes);
|
||||
return frame ? { kind: "terminal", frame } : null;
|
||||
}
|
||||
case FileTransferOpcode.FileBegin:
|
||||
case FileTransferOpcode.FileChunk:
|
||||
case FileTransferOpcode.FileEnd: {
|
||||
const frame = decodeFileTransferFrame(bytes);
|
||||
return frame ? { kind: "file_transfer", frame } : null;
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ export const FileBeginMetadataSchema = z.object({
|
||||
size: z.number().int().nonnegative(),
|
||||
encoding: z.enum(["utf-8", "binary"]),
|
||||
modifiedAt: z.string(),
|
||||
fileName: z.string().optional(),
|
||||
});
|
||||
|
||||
export interface FileBegin {
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from "./demux.js";
|
||||
export * from "./file-transfer.js";
|
||||
export * from "./terminal.js";
|
||||
|
||||
@@ -839,11 +839,21 @@ export const ReviewAttachmentSchema = z.object({
|
||||
comments: z.array(ReviewAttachmentCommentSchema),
|
||||
});
|
||||
|
||||
export const UploadedFileAttachmentSchema = z.object({
|
||||
type: z.literal("uploaded_file"),
|
||||
id: z.string(),
|
||||
fileName: z.string(),
|
||||
mimeType: z.string(),
|
||||
size: z.number().int().nonnegative(),
|
||||
path: z.string(),
|
||||
});
|
||||
|
||||
export const AgentAttachmentSchema = z.discriminatedUnion("type", [
|
||||
GitHubPrAttachmentSchema,
|
||||
GitHubIssueAttachmentSchema,
|
||||
TextAttachmentSchema,
|
||||
ReviewAttachmentSchema,
|
||||
UploadedFileAttachmentSchema,
|
||||
]);
|
||||
|
||||
function normalizeAgentAttachments(input: unknown): AgentAttachment[] {
|
||||
@@ -1680,6 +1690,15 @@ export const FileDownloadTokenRequestSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const FileUploadRequestSchema = z.object({
|
||||
type: z.literal("file.upload.request"),
|
||||
fileName: z.string().min(1),
|
||||
mimeType: z.string().min(1),
|
||||
size: z.number().int().nonnegative(),
|
||||
modifiedAt: z.string(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ClearAgentAttentionMessageSchema = z.object({
|
||||
type: z.literal("clear_agent_attention"),
|
||||
agentId: z.union([z.string(), z.array(z.string())]),
|
||||
@@ -1911,6 +1930,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
FileExplorerRequestSchema,
|
||||
ProjectIconRequestSchema,
|
||||
FileDownloadTokenRequestSchema,
|
||||
FileUploadRequestSchema,
|
||||
ClearAgentAttentionMessageSchema,
|
||||
ClientHeartbeatMessageSchema,
|
||||
PingMessageSchema,
|
||||
@@ -3436,6 +3456,15 @@ export const FileDownloadTokenResponseSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const FileUploadResponseSchema = z.object({
|
||||
type: z.literal("file.upload.response"),
|
||||
payload: z.object({
|
||||
requestId: z.string(),
|
||||
file: UploadedFileAttachmentSchema.nullable(),
|
||||
error: z.string().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ListProviderModelsResponseMessageSchema = z.object({
|
||||
type: z.literal("list_provider_models_response"),
|
||||
payload: z.object({
|
||||
@@ -3756,6 +3785,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
FileExplorerResponseSchema,
|
||||
ProjectIconResponseSchema,
|
||||
FileDownloadTokenResponseSchema,
|
||||
FileUploadResponseSchema,
|
||||
ListProviderModelsResponseMessageSchema,
|
||||
ListProviderModesResponseMessageSchema,
|
||||
ListProviderFeaturesResponseMessageSchema,
|
||||
@@ -4056,6 +4086,8 @@ export type ProjectIconResponse = z.infer<typeof ProjectIconResponseSchema>;
|
||||
export type ProjectIcon = z.infer<typeof ProjectIconSchema>;
|
||||
export type FileDownloadTokenRequest = z.infer<typeof FileDownloadTokenRequestSchema>;
|
||||
export type FileDownloadTokenResponse = z.infer<typeof FileDownloadTokenResponseSchema>;
|
||||
export type FileUploadRequest = z.infer<typeof FileUploadRequestSchema>;
|
||||
export type FileUploadResponse = z.infer<typeof FileUploadResponseSchema>;
|
||||
export type RestartServerRequestMessage = z.infer<typeof RestartServerRequestMessageSchema>;
|
||||
export type ShutdownServerRequestMessage = z.infer<typeof ShutdownServerRequestMessageSchema>;
|
||||
export type ClearAgentAttentionMessage = z.infer<typeof ClearAgentAttentionMessageSchema>;
|
||||
|
||||
Reference in New Issue
Block a user