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:
Mohamed Boudra
2026-06-06 20:37:38 +08:00
committed by GitHub
parent 9a8912b3ef
commit db4376d17a
16 changed files with 917 additions and 21 deletions

View 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();
});
});

View 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;
}
}

View File

@@ -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 {

View File

@@ -1,2 +1,3 @@
export * from "./demux.js";
export * from "./file-transfer.js";
export * from "./terminal.js";