@hyperdrive.bot/paseo-client 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,282 @@
1
+ import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload } from "@hyperdrive.bot/paseo-protocol/messages";
2
+ import { DaemonClient } from "./daemon-client.js";
3
+ import type { FetchAgentTimelineCursor, FetchAgentTimelineDirection, FetchAgentTimelinePayload, FetchAgentTimelineProjection } from "./daemon-client.js";
4
+ export { DaemonClient };
5
+ export { UploadCancelledError, UploadCapExceededError, UploadFailedError, } from "./daemon-client.js";
6
+ export type { DaemonClientConfig, DaemonEvent, BrowserAutomationExecuteRequestMessage, BrowserAutomationExecuteResponseMessage, WebSocketFactory, WebSocketLike, } from "./daemon-client.js";
7
+ export type ConnectionState = {
8
+ status: "idle";
9
+ } | {
10
+ status: "connecting";
11
+ attempt: number;
12
+ } | {
13
+ status: "connected";
14
+ } | {
15
+ status: "disconnected";
16
+ reason?: string;
17
+ } | {
18
+ status: "disposed";
19
+ };
20
+ export interface PaseoLogger {
21
+ debug(obj: object, msg?: string): void;
22
+ info(obj: object, msg?: string): void;
23
+ warn(obj: object, msg?: string): void;
24
+ error(obj: object, msg?: string): void;
25
+ }
26
+ export interface PaseoClientConfig {
27
+ url: string;
28
+ clientId?: string;
29
+ appVersion?: string;
30
+ runtimeGeneration?: number | null;
31
+ password?: string;
32
+ authHeader?: string;
33
+ suppressSendErrors?: boolean;
34
+ logger?: PaseoLogger;
35
+ connectTimeoutMs?: number;
36
+ e2ee?: {
37
+ enabled?: boolean;
38
+ daemonPublicKeyB64?: string;
39
+ };
40
+ reconnect?: {
41
+ enabled?: boolean;
42
+ baseDelayMs?: number;
43
+ maxDelayMs?: number;
44
+ };
45
+ runtimeMetricsIntervalMs?: number;
46
+ runtimeMetricsWindowMs?: number;
47
+ }
48
+ export type PaseoWorkspace = WorkspaceDescriptorPayload;
49
+ export type PaseoAgent = AgentSnapshotPayload;
50
+ export type PaseoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "type" | "requestId"> & {
51
+ requestId?: string;
52
+ };
53
+ export interface PaseoWorkspaceListResult {
54
+ requestId: string;
55
+ subscriptionId?: string | null;
56
+ entries: PaseoWorkspace[];
57
+ pageInfo: FetchWorkspacesResponseMessage["payload"]["pageInfo"];
58
+ }
59
+ export interface PaseoWorkspaceOpenOptions {
60
+ cwd: string;
61
+ requestId?: string;
62
+ }
63
+ export interface PaseoWorkspaceOpenResult {
64
+ requestId: string;
65
+ workspace: PaseoWorkspaceHandle | null;
66
+ error: string | null;
67
+ }
68
+ export interface PaseoWorkspaceArchiveResult {
69
+ requestId: string;
70
+ workspaceId: string;
71
+ archivedAt: string | null;
72
+ error: string | null;
73
+ }
74
+ export type PaseoWorkspaceUpdate = Extract<SessionOutboundMessage, {
75
+ type: "workspace_update";
76
+ }>["payload"];
77
+ export type PaseoWorkspaceUpdateHandler = (update: PaseoWorkspaceUpdate) => void;
78
+ /**
79
+ * A handle is a stable typed reference to a daemon resource. Its identity is the
80
+ * daemon id, and `latest()` only returns the most recent snapshot this handle has
81
+ * seen through construction, `refetch()`, or this handle's local subscription.
82
+ */
83
+ export interface PaseoWorkspaceHandle {
84
+ readonly id: string;
85
+ latest(): PaseoWorkspace | null;
86
+ /**
87
+ * Fetches a fresh workspace snapshot through the existing workspace list RPC,
88
+ * exact-matches this handle id from the result, and updates `latest()`.
89
+ */
90
+ refetch(options?: {
91
+ requestId?: string;
92
+ }): Promise<PaseoWorkspace | null>;
93
+ archive(requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
94
+ /**
95
+ * Subscribes to already-emitted daemon workspace_update events for this id.
96
+ * This returns a local unsubscribe function; it does not own app cache state or
97
+ * send a daemon unsubscribe RPC. Call `workspaces.list({ subscribe: {} })` when
98
+ * the daemon should start streaming workspace directory updates.
99
+ */
100
+ subscribe(handler: (update: PaseoWorkspaceUpdate) => void): () => void;
101
+ }
102
+ export interface PaseoWorkspaceActions {
103
+ list(options?: PaseoWorkspaceListOptions): Promise<PaseoWorkspaceListResult>;
104
+ ref(workspace: string | PaseoWorkspace): PaseoWorkspaceHandle;
105
+ open(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
106
+ create(input: string | PaseoWorkspaceOpenOptions, requestId?: string): Promise<PaseoWorkspaceOpenResult>;
107
+ archive(workspace: string | PaseoWorkspaceHandle, requestId?: string): Promise<PaseoWorkspaceArchiveResult>;
108
+ /**
109
+ * Local event subscription over the low-level driver's workspace_update stream.
110
+ * The returned function only removes this SDK listener.
111
+ */
112
+ subscribe(handler: PaseoWorkspaceUpdateHandler): () => void;
113
+ }
114
+ type PaseoAgentSessionConfig = CreateAgentRequestMessage["config"];
115
+ type PaseoAgentProvider = PaseoAgentSessionConfig["provider"];
116
+ type PaseoAgentConfigOverrides = Partial<Omit<PaseoAgentSessionConfig, "provider" | "cwd">>;
117
+ export interface PaseoAgentCreateOptions extends PaseoAgentConfigOverrides {
118
+ config?: PaseoAgentSessionConfig;
119
+ provider?: CreateAgentRequestMessage["config"]["provider"];
120
+ cwd?: string;
121
+ workspaceId?: string;
122
+ initialPrompt?: string;
123
+ clientMessageId?: string;
124
+ outputSchema?: Record<string, unknown>;
125
+ images?: CreateAgentRequestMessage["images"];
126
+ attachments?: CreateAgentRequestMessage["attachments"];
127
+ git?: CreateAgentRequestMessage["git"];
128
+ worktreeName?: string;
129
+ requestId?: string;
130
+ labels?: Record<string, string>;
131
+ }
132
+ export interface PaseoAgentRefetchResult {
133
+ agent: PaseoAgent;
134
+ project: ProjectPlacementPayload | null;
135
+ }
136
+ export interface PaseoAgentTimelineRefetchOptions {
137
+ direction?: FetchAgentTimelineDirection;
138
+ cursor?: FetchAgentTimelineCursor;
139
+ limit?: number;
140
+ projection?: FetchAgentTimelineProjection;
141
+ requestId?: string;
142
+ }
143
+ export interface PaseoAgentSendOptions {
144
+ messageId?: string;
145
+ images?: Array<{
146
+ data: string;
147
+ mimeType: string;
148
+ }>;
149
+ attachments?: SendAgentMessageRequest["attachments"];
150
+ }
151
+ export type PaseoAgentUpdate = Extract<SessionOutboundMessage, {
152
+ type: "agent_update";
153
+ }>["payload"];
154
+ export type PaseoAgentStream = Extract<SessionOutboundMessage, {
155
+ type: "agent_stream";
156
+ }>["payload"];
157
+ export type PaseoAgentUpdateHandler = (update: PaseoAgentUpdate) => void;
158
+ export interface PaseoAgentTimelineHandle {
159
+ /**
160
+ * Fetches a fresh timeline page through the existing daemon RPC. If the daemon
161
+ * includes an agent snapshot in the response, the parent handle's `latest()`
162
+ * is updated to that snapshot.
163
+ */
164
+ refetch(options?: PaseoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
165
+ /**
166
+ * Local listener for agent_stream events matching this handle id. It does not
167
+ * retain timeline entries or own application cache state.
168
+ */
169
+ subscribe(handler: (event: PaseoAgentStream) => void): () => void;
170
+ }
171
+ /**
172
+ * Agent handles follow the same identity/snapshot rule as workspace handles:
173
+ * `id` is stable, while `latest()` is only the newest snapshot observed by this
174
+ * handle through construction, `refetch()`, timeline refetch, archive, or local
175
+ * agent_update subscription.
176
+ */
177
+ export interface PaseoAgentHandle {
178
+ readonly id: string;
179
+ readonly timeline: PaseoAgentTimelineHandle;
180
+ latest(): PaseoAgent | null;
181
+ refetch(requestId?: string): Promise<PaseoAgentRefetchResult | null>;
182
+ send(text: string, options?: PaseoAgentSendOptions): Promise<void>;
183
+ archive(): Promise<{
184
+ archivedAt: string;
185
+ }>;
186
+ detach(): Promise<void>;
187
+ subscribe(handler: (update: PaseoAgentUpdate) => void): () => void;
188
+ }
189
+ export interface PaseoAgentActions {
190
+ ref(agent: string | PaseoAgent): PaseoAgentHandle;
191
+ create(options: PaseoAgentCreateOptions): Promise<PaseoAgentHandle>;
192
+ /**
193
+ * Local event subscription over the low-level driver's agent_update stream.
194
+ * The returned function only removes this SDK listener.
195
+ */
196
+ subscribe(handler: PaseoAgentUpdateHandler): () => void;
197
+ }
198
+ export interface PaseoProviderConfig extends PaseoProviderConfigInput {
199
+ provider: PaseoAgentProvider;
200
+ }
201
+ export type PaseoProviderFeatureValues = Record<string, unknown>;
202
+ export interface PaseoProviderConfigInput {
203
+ model?: string;
204
+ modeId?: string;
205
+ thinkingOptionId?: string;
206
+ featureValues?: PaseoProviderFeatureValues;
207
+ }
208
+ export type PaseoProviderModelsResult = ListProviderModelsResponseMessage["payload"];
209
+ export type PaseoProviderModesResult = ListProviderModesResponseMessage["payload"];
210
+ export type PaseoProviderFeaturesInput = ListProviderFeaturesRequestMessage["draftConfig"];
211
+ export type PaseoProviderFeaturesResult = ListProviderFeaturesResponseMessage["payload"];
212
+ export type PaseoProviderAvailabilityResult = ListAvailableProvidersResponse["payload"];
213
+ export type PaseoProviderSnapshotResult = GetProvidersSnapshotResponseMessage["payload"];
214
+ export type PaseoProviderSnapshotUpdate = Extract<SessionOutboundMessage, {
215
+ type: "providers_snapshot_update";
216
+ }>["payload"];
217
+ export type PaseoProviderRefreshResult = RefreshProvidersSnapshotResponseMessage["payload"];
218
+ export type PaseoProviderDiagnosticResult = ProviderDiagnosticResponseMessage["payload"];
219
+ export interface PaseoProviderListOptions {
220
+ cwd?: string;
221
+ requestId?: string;
222
+ }
223
+ export interface PaseoProviderRefreshOptions {
224
+ cwd?: string;
225
+ providers?: PaseoAgentProvider[];
226
+ requestId?: string;
227
+ }
228
+ export interface PaseoProviderActions {
229
+ codex(input?: PaseoProviderConfigInput): PaseoProviderConfig;
230
+ claude(input?: PaseoProviderConfigInput): PaseoProviderConfig;
231
+ opencode(input?: PaseoProviderConfigInput): PaseoProviderConfig;
232
+ copilot(input?: PaseoProviderConfigInput): PaseoProviderConfig;
233
+ config(provider: PaseoAgentProvider, input?: PaseoProviderConfigInput): PaseoProviderConfig;
234
+ listModels(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModelsResult>;
235
+ listModes(provider: PaseoAgentProvider, options?: PaseoProviderListOptions): Promise<PaseoProviderModesResult>;
236
+ listFeatures(draftConfig: PaseoProviderFeaturesInput, options?: {
237
+ requestId?: string;
238
+ }): Promise<PaseoProviderFeaturesResult>;
239
+ listAvailable(options?: {
240
+ requestId?: string;
241
+ }): Promise<PaseoProviderAvailabilityResult>;
242
+ snapshot(options?: PaseoProviderListOptions): Promise<PaseoProviderSnapshotResult>;
243
+ refresh(options?: PaseoProviderRefreshOptions): Promise<PaseoProviderRefreshResult>;
244
+ diagnostic(provider: PaseoAgentProvider, options?: {
245
+ requestId?: string;
246
+ }): Promise<PaseoProviderDiagnosticResult>;
247
+ subscribe(handler: (update: PaseoProviderSnapshotUpdate) => void): () => void;
248
+ }
249
+ export interface PaseoConfigActions {
250
+ /**
251
+ * Reads daemon config through the existing config RPC. Provider profiles,
252
+ * custom provider entries, keys/env, custom binaries, and provider enablement
253
+ * are currently config-file-shaped daemon state, so the SDK exposes this raw
254
+ * typed surface instead of pretending there are higher-level provider-settings
255
+ * RPCs.
256
+ */
257
+ get(requestId?: string): Promise<{
258
+ requestId: string;
259
+ config: MutableDaemonConfig;
260
+ }>;
261
+ /**
262
+ * Patches daemon config through the existing config RPC. The daemon validates
263
+ * and persists supported fields; unsupported provider/settings workflows remain
264
+ * daemon gaps until first-class RPCs exist.
265
+ */
266
+ patch(config: MutableDaemonConfigPatch, requestId?: string): Promise<{
267
+ requestId: string;
268
+ config: MutableDaemonConfig;
269
+ }>;
270
+ }
271
+ export interface PaseoClient {
272
+ readonly workspaces: PaseoWorkspaceActions;
273
+ readonly agents: PaseoAgentActions;
274
+ readonly providers: PaseoProviderActions;
275
+ readonly config: PaseoConfigActions;
276
+ connect(): Promise<void>;
277
+ close(): Promise<void>;
278
+ ensureConnected(): void;
279
+ getConnectionState(): ConnectionState;
280
+ }
281
+ export declare function createPaseoClient(config: PaseoClientConfig): PaseoClient;
282
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,177 @@
1
+ import { DaemonClient } from "./daemon-client.js";
2
+ export { DaemonClient };
3
+ export { UploadCancelledError, UploadCapExceededError, UploadFailedError, } from "./daemon-client.js";
4
+ export function createPaseoClient(config) {
5
+ const daemonClient = new DaemonClient({
6
+ ...config,
7
+ clientId: config.clientId ?? createGeneratedClientId(),
8
+ clientType: "cli",
9
+ });
10
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
11
+ const createAgentHandle = createAgentHandleFactory(daemonClient);
12
+ return {
13
+ workspaces: {
14
+ list: (options) => daemonClient.fetchWorkspaces(options),
15
+ ref: (workspace) => createWorkspaceHandle(workspace),
16
+ open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
17
+ create: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
18
+ archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
19
+ subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
20
+ handler(message.payload);
21
+ }),
22
+ },
23
+ agents: {
24
+ ref: (agent) => createAgentHandle(agent),
25
+ create: async (options) => {
26
+ const agent = await daemonClient.createAgent(options);
27
+ return createAgentHandle(agent);
28
+ },
29
+ subscribe: (handler) => daemonClient.on("agent_update", (message) => {
30
+ handler(message.payload);
31
+ }),
32
+ },
33
+ providers: {
34
+ codex: (input) => providerConfig("codex", input),
35
+ claude: (input) => providerConfig("claude", input),
36
+ opencode: (input) => providerConfig("opencode", input),
37
+ copilot: (input) => providerConfig("copilot", input),
38
+ config: (provider, input) => providerConfig(provider, input),
39
+ listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
40
+ listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
41
+ listFeatures: (draftConfig, options) => daemonClient.listProviderFeatures(draftConfig, options),
42
+ listAvailable: (options) => daemonClient.listAvailableProviders(options),
43
+ snapshot: (options) => daemonClient.getProvidersSnapshot(options),
44
+ refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
45
+ diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
46
+ subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
47
+ handler(message.payload);
48
+ }),
49
+ },
50
+ config: {
51
+ get: (requestId) => daemonClient.getDaemonConfig(requestId),
52
+ patch: (patch, requestId) => daemonClient.patchDaemonConfig(patch, requestId),
53
+ },
54
+ connect: () => daemonClient.connect(),
55
+ close: () => daemonClient.close(),
56
+ ensureConnected: () => daemonClient.ensureConnected(),
57
+ getConnectionState: () => daemonClient.getConnectionState(),
58
+ };
59
+ }
60
+ function createWorkspaceHandleFactory(daemonClient) {
61
+ return (workspace) => {
62
+ const id = typeof workspace === "string" ? workspace : workspace.id;
63
+ let latest = typeof workspace === "string" ? null : workspace;
64
+ return {
65
+ id,
66
+ latest: () => latest,
67
+ refetch: async (options) => {
68
+ // Best-effort: fetches one page and matches by id client-side, so a workspace beyond
69
+ // the first page won't be found. TODO: add a "get workspace by id" lookup and resolve
70
+ // by exact id instead of paging.
71
+ const result = await daemonClient.fetchWorkspaces({
72
+ requestId: options?.requestId,
73
+ page: { limit: 25 },
74
+ });
75
+ latest = result.entries.find((entry) => entry.id === id) ?? null;
76
+ return latest;
77
+ },
78
+ archive: async (requestId) => {
79
+ const result = await daemonClient.archiveWorkspace(id, requestId);
80
+ if (latest) {
81
+ latest = { ...latest, archivingAt: result.archivedAt };
82
+ }
83
+ return result;
84
+ },
85
+ subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
86
+ const update = message.payload;
87
+ if (update.kind === "upsert" && update.workspace.id === id) {
88
+ latest = update.workspace;
89
+ handler(update);
90
+ }
91
+ if (update.kind === "remove" && update.id === id) {
92
+ latest = null;
93
+ handler(update);
94
+ }
95
+ }),
96
+ };
97
+ };
98
+ }
99
+ function createAgentHandleFactory(daemonClient) {
100
+ return (agent) => {
101
+ const id = typeof agent === "string" ? agent : agent.id;
102
+ let latest = typeof agent === "string" ? null : agent;
103
+ const handle = {
104
+ id,
105
+ timeline: {
106
+ refetch: async (options) => {
107
+ const result = await daemonClient.fetchAgentTimeline(id, options);
108
+ if (result.agent) {
109
+ latest = result.agent;
110
+ }
111
+ return result;
112
+ },
113
+ subscribe: (handler) => daemonClient.on("agent_stream", (message) => {
114
+ if (message.payload.agentId === id) {
115
+ handler(message.payload);
116
+ }
117
+ }),
118
+ },
119
+ latest: () => latest,
120
+ refetch: async (requestId) => {
121
+ const result = await daemonClient.fetchAgent({ agentId: id, requestId });
122
+ latest = result?.agent ?? null;
123
+ return result;
124
+ },
125
+ send: (text, options) => daemonClient.sendAgentMessage(id, text, options),
126
+ archive: async () => {
127
+ const result = await daemonClient.archiveAgent(id);
128
+ if (latest) {
129
+ latest = { ...latest, archivedAt: result.archivedAt };
130
+ }
131
+ return result;
132
+ },
133
+ detach: async () => {
134
+ await daemonClient.detachAgent(id);
135
+ },
136
+ subscribe: (handler) => daemonClient.on("agent_update", (message) => {
137
+ const update = message.payload;
138
+ if (update.kind === "upsert" && update.agent.id === id) {
139
+ latest = update.agent;
140
+ handler(update);
141
+ }
142
+ if (update.kind === "remove" && update.agentId === id) {
143
+ latest = null;
144
+ handler(update);
145
+ }
146
+ }),
147
+ };
148
+ return handle;
149
+ };
150
+ }
151
+ async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
152
+ const options = typeof input === "string" ? { cwd: input, requestId } : input;
153
+ const result = await daemonClient.openProject(options.cwd, options.requestId);
154
+ return {
155
+ ...result,
156
+ workspace: result.workspace ? createWorkspaceHandle(result.workspace) : null,
157
+ };
158
+ }
159
+ function resolveWorkspaceId(workspace) {
160
+ return typeof workspace === "string" ? workspace : workspace.id;
161
+ }
162
+ function providerConfig(provider, input = {}) {
163
+ return {
164
+ provider,
165
+ ...(input.model !== undefined ? { model: input.model } : {}),
166
+ ...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
167
+ ...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
168
+ ...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
169
+ };
170
+ }
171
+ function createGeneratedClientId() {
172
+ const randomId = typeof globalThis.crypto?.randomUUID === "function"
173
+ ? globalThis.crypto.randomUUID()
174
+ : Math.random().toString(36).slice(2);
175
+ return `paseo-sdk-${randomId}`;
176
+ }
177
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import { type TerminalStreamFrame } from "@hyperdrive.bot/paseo-protocol/binary-frames/index";
2
+ import type { TerminalInput, TerminalState } from "@hyperdrive.bot/paseo-protocol/messages";
3
+ export type TerminalStreamEvent = {
4
+ terminalId: string;
5
+ type: "output";
6
+ data: Uint8Array;
7
+ } | {
8
+ terminalId: string;
9
+ type: "snapshot";
10
+ state: TerminalState;
11
+ } | {
12
+ terminalId: string;
13
+ type: "restore";
14
+ data: Uint8Array;
15
+ };
16
+ export declare class TerminalStreamRouter {
17
+ private readonly terminalSlots;
18
+ private readonly slotTerminals;
19
+ private readonly listeners;
20
+ onEvent(handler: (event: TerminalStreamEvent) => void): () => void;
21
+ setSlot(terminalId: string, slot: number): void;
22
+ removeTerminal(terminalId: string): void;
23
+ clearSlots(): void;
24
+ encodeInput(terminalId: string, message: TerminalInput["message"]): Uint8Array | null;
25
+ handleFrame(frame: TerminalStreamFrame): void;
26
+ private emit;
27
+ }
28
+ //# sourceMappingURL=terminal-stream-router.d.ts.map
@@ -0,0 +1,108 @@
1
+ import { decodeTerminalSnapshotPayload, encodeTerminalResizePayload, encodeTerminalStreamFrame, TerminalStreamOpcode, } from "@hyperdrive.bot/paseo-protocol/binary-frames/index";
2
+ export class TerminalStreamRouter {
3
+ constructor() {
4
+ this.terminalSlots = new Map();
5
+ this.slotTerminals = new Map();
6
+ this.listeners = new Set();
7
+ }
8
+ onEvent(handler) {
9
+ this.listeners.add(handler);
10
+ return () => {
11
+ this.listeners.delete(handler);
12
+ };
13
+ }
14
+ setSlot(terminalId, slot) {
15
+ const existingTerminalId = this.slotTerminals.get(slot);
16
+ if (existingTerminalId && existingTerminalId !== terminalId) {
17
+ this.terminalSlots.delete(existingTerminalId);
18
+ }
19
+ const existingSlot = this.terminalSlots.get(terminalId);
20
+ if (typeof existingSlot === "number" && existingSlot !== slot) {
21
+ this.slotTerminals.delete(existingSlot);
22
+ }
23
+ this.terminalSlots.set(terminalId, slot);
24
+ this.slotTerminals.set(slot, terminalId);
25
+ }
26
+ removeTerminal(terminalId) {
27
+ const slot = this.terminalSlots.get(terminalId);
28
+ if (typeof slot !== "number") {
29
+ return;
30
+ }
31
+ this.terminalSlots.delete(terminalId);
32
+ if (this.slotTerminals.get(slot) === terminalId) {
33
+ this.slotTerminals.delete(slot);
34
+ }
35
+ }
36
+ clearSlots() {
37
+ this.terminalSlots.clear();
38
+ this.slotTerminals.clear();
39
+ }
40
+ encodeInput(terminalId, message) {
41
+ const slot = this.terminalSlots.get(terminalId);
42
+ if (typeof slot !== "number") {
43
+ return null;
44
+ }
45
+ if (message.type === "input") {
46
+ return encodeTerminalStreamFrame({
47
+ opcode: TerminalStreamOpcode.Input,
48
+ slot,
49
+ payload: message.data,
50
+ });
51
+ }
52
+ if (message.type === "resize") {
53
+ return encodeTerminalStreamFrame({
54
+ opcode: TerminalStreamOpcode.Resize,
55
+ slot,
56
+ payload: encodeTerminalResizePayload({
57
+ rows: message.rows,
58
+ cols: message.cols,
59
+ }),
60
+ });
61
+ }
62
+ return null;
63
+ }
64
+ handleFrame(frame) {
65
+ const terminalId = this.slotTerminals.get(frame.slot);
66
+ if (!terminalId) {
67
+ return;
68
+ }
69
+ if (frame.opcode === TerminalStreamOpcode.Output) {
70
+ this.emit({
71
+ terminalId,
72
+ type: "output",
73
+ data: frame.payload,
74
+ });
75
+ return;
76
+ }
77
+ if (frame.opcode === TerminalStreamOpcode.Restore) {
78
+ this.emit({
79
+ terminalId,
80
+ type: "restore",
81
+ data: frame.payload,
82
+ });
83
+ return;
84
+ }
85
+ if (frame.opcode === TerminalStreamOpcode.Snapshot) {
86
+ const state = decodeTerminalSnapshotPayload(frame.payload);
87
+ if (!state) {
88
+ return;
89
+ }
90
+ this.emit({
91
+ terminalId,
92
+ type: "snapshot",
93
+ state,
94
+ });
95
+ }
96
+ }
97
+ emit(event) {
98
+ for (const listener of this.listeners) {
99
+ try {
100
+ listener(event);
101
+ }
102
+ catch {
103
+ // no-op
104
+ }
105
+ }
106
+ }
107
+ }
108
+ //# sourceMappingURL=terminal-stream-router.js.map
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@hyperdrive.bot/paseo-client",
3
+ "version": "0.2.5",
4
+ "description": "Paseo client SDK package",
5
+ "files": [
6
+ "dist",
7
+ "!dist/**/*.map",
8
+ "README.md"
9
+ ],
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./internal/daemon-client": {
17
+ "types": "./dist/daemon-client.d.ts",
18
+ "default": "./dist/daemon-client.js"
19
+ },
20
+ "./internal/daemon-client-transport-types": {
21
+ "types": "./dist/daemon-client-transport-types.d.ts",
22
+ "default": "./dist/daemon-client-transport-types.js"
23
+ }
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "clean": "node ../../scripts/clean-package-dist.mjs",
30
+ "build": "npm run build --workspace=@hyperdrive.bot/paseo-protocol && tsc -p tsconfig.json --incremental false",
31
+ "build:clean": "npm run clean && npm run build",
32
+ "prepack": "npm run build:clean",
33
+ "typecheck": "tsgo --noEmit",
34
+ "typecheck:examples": "tsgo -p tsconfig.examples.json --noEmit",
35
+ "test": "vitest run"
36
+ },
37
+ "dependencies": {
38
+ "@hyperdrive.bot/paseo-protocol": "0.2.5",
39
+ "@hyperdrive.bot/paseo-relay": "0.2.5",
40
+ "zod": "^4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.9.0",
44
+ "typescript": "^5.2.2",
45
+ "vitest": "^4.1.6"
46
+ }
47
+ }