feat: stream files as binary frames over WebSocket (#659)
Replace base64-in-JSON file transfers with binary WebSocket frames to
unblock the JS thread when assistant markdown messages contain images.
Receive cost (JSON.parse over multi-MB base64) and persist cost (base64
re-encode to disk) both removed.
- Split daemon-client `exploreFileSystem` into `listDirectory` + `readFile`;
`readFile` returns `{ bytes, mime, size, modifiedAt }`. Encoding stays
private to the seam.
- New `packages/server/src/shared/binary-frames/` module hosts terminal
frames (moved from `terminal-stream-protocol.ts`) and new file-transfer
opcodes (`FileBegin` / `FileChunk` / `FileEnd`) with request-id correlation.
- Add optional `acceptBinary` to `FileExplorerRequestSchema`. New daemons
emit binary frames when set; legacy JSON path remains for old clients.
Compat fallback lives only inside `readFile`.
- New `persistAttachmentFromBytes` writes bytes directly: native via
`expo-file-system` `File.write(Uint8Array)`, web via Blob -> IndexedDB,
desktop via new `write_attachment_bytes` IPC. `persistAttachmentFromBase64`
removed.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost, resolveAgentId } from "../../utils/client.js";
|
||||
import type {
|
||||
CommandOptions,
|
||||
@@ -55,7 +56,7 @@ export async function runArchiveCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import { isSameOrDescendantPath } from "../../utils/paths.js";
|
||||
|
||||
@@ -49,7 +50,7 @@ export async function runDeleteCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost, resolveAgentId } from "../../utils/client.js";
|
||||
import type {
|
||||
CommandOptions,
|
||||
@@ -50,7 +51,7 @@ export async function runReloadCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import { isSameOrDescendantPath } from "../../utils/paths.js";
|
||||
import type {
|
||||
@@ -53,7 +54,7 @@ export async function runStopCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host as string | undefined });
|
||||
} catch (err) {
|
||||
|
||||
@@ -12,6 +12,18 @@ export interface ModelListItem {
|
||||
thinkingOptions: string;
|
||||
}
|
||||
|
||||
interface ProviderModelOption {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface ProviderModel {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
thinkingOptions?: ProviderModelOption[];
|
||||
defaultThinkingOptionId?: string | null;
|
||||
}
|
||||
|
||||
/** Schema for provider models output */
|
||||
export const providerModelsSchema: OutputSchema<ModelListItem> = {
|
||||
idField: "id",
|
||||
@@ -61,7 +73,7 @@ export async function runModelsCommand(
|
||||
};
|
||||
}
|
||||
|
||||
const models: ModelListItem[] = (result.models ?? []).map((m) => ({
|
||||
const models: ModelListItem[] = (result.models ?? []).map((m: ProviderModel) => ({
|
||||
model: m.label,
|
||||
id: m.id,
|
||||
description: m.description ?? "",
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
} from "./shared.js";
|
||||
import { terminalSchema, type TerminalRow, toTerminalRow } from "./schema.js";
|
||||
|
||||
type TerminalListEntry = Parameters<typeof toTerminalRow>[0];
|
||||
|
||||
export interface TerminalLsOptions extends TerminalCommandOptions {
|
||||
all?: boolean;
|
||||
cwd?: string;
|
||||
@@ -24,7 +26,9 @@ export async function runLsCommand(
|
||||
cwd === undefined ? await client.listTerminals() : await client.listTerminals(cwd);
|
||||
return {
|
||||
type: "list",
|
||||
data: payload.terminals.map((terminal) => toTerminalRow(terminal, payload.cwd ?? cwd)),
|
||||
data: payload.terminals.map((terminal: TerminalListEntry) =>
|
||||
toTerminalRow(terminal, payload.cwd ?? cwd),
|
||||
),
|
||||
schema: terminalSchema,
|
||||
};
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import path from "path";
|
||||
import type { Command } from "commander";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import type {
|
||||
CommandOptions,
|
||||
@@ -51,7 +52,7 @@ export async function runArchiveCommand(
|
||||
throw error;
|
||||
}
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host });
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { homedir } from "node:os";
|
||||
import { basename, join, sep } from "node:path";
|
||||
import type { DaemonClient } from "@getpaseo/server";
|
||||
import { connectToDaemon, getDaemonHost } from "../../utils/client.js";
|
||||
import type { CommandOptions, ListResult, OutputSchema, CommandError } from "../../output/index.js";
|
||||
|
||||
@@ -62,7 +63,7 @@ export async function runLsCommand(
|
||||
): Promise<WorktreeLsResult> {
|
||||
const host = getDaemonHost({ host: options.host });
|
||||
|
||||
let client;
|
||||
let client: DaemonClient;
|
||||
try {
|
||||
client = await connectToDaemon({ host: options.host });
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user