@otto-code/client 0.5.0

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,281 @@
1
+ import type { AgentSnapshotPayload, CreateAgentRequestMessage, FetchWorkspacesRequestMessage, FetchWorkspacesResponseMessage, GetProvidersSnapshotResponseMessage, ListAvailableProvidersResponse, ListProviderFeaturesRequestMessage, ListProviderFeaturesResponseMessage, ListProviderModelsResponseMessage, ListProviderModesResponseMessage, MutableDaemonConfig, MutableDaemonConfigPatch, ProviderDiagnosticResponseMessage, ProjectPlacementPayload, RefreshProvidersSnapshotResponseMessage, SendAgentMessageRequest, SessionOutboundMessage, WorkspaceDescriptorPayload } from "@otto-code/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 type { DaemonClientConfig, DaemonEvent, BrowserAutomationExecuteRequestMessage, BrowserAutomationExecuteResponseMessage, HostingAuthStatusPayload, HostingSearchPayload, WebSocketFactory, WebSocketLike, } from "./daemon-client.js";
6
+ export type ConnectionState = {
7
+ status: "idle";
8
+ } | {
9
+ status: "connecting";
10
+ attempt: number;
11
+ } | {
12
+ status: "connected";
13
+ } | {
14
+ status: "disconnected";
15
+ reason?: string;
16
+ } | {
17
+ status: "disposed";
18
+ };
19
+ export interface OttoLogger {
20
+ debug(obj: object, msg?: string): void;
21
+ info(obj: object, msg?: string): void;
22
+ warn(obj: object, msg?: string): void;
23
+ error(obj: object, msg?: string): void;
24
+ }
25
+ export interface OttoClientConfig {
26
+ url: string;
27
+ clientId?: string;
28
+ appVersion?: string;
29
+ runtimeGeneration?: number | null;
30
+ password?: string;
31
+ authHeader?: string;
32
+ suppressSendErrors?: boolean;
33
+ logger?: OttoLogger;
34
+ connectTimeoutMs?: number;
35
+ e2ee?: {
36
+ enabled?: boolean;
37
+ daemonPublicKeyB64?: string;
38
+ };
39
+ reconnect?: {
40
+ enabled?: boolean;
41
+ baseDelayMs?: number;
42
+ maxDelayMs?: number;
43
+ };
44
+ runtimeMetricsIntervalMs?: number;
45
+ runtimeMetricsWindowMs?: number;
46
+ }
47
+ export type OttoWorkspace = WorkspaceDescriptorPayload;
48
+ export type OttoAgent = AgentSnapshotPayload;
49
+ export type OttoWorkspaceListOptions = Omit<FetchWorkspacesRequestMessage, "type" | "requestId"> & {
50
+ requestId?: string;
51
+ };
52
+ export interface OttoWorkspaceListResult {
53
+ requestId: string;
54
+ subscriptionId?: string | null;
55
+ entries: OttoWorkspace[];
56
+ pageInfo: FetchWorkspacesResponseMessage["payload"]["pageInfo"];
57
+ }
58
+ export interface OttoWorkspaceOpenOptions {
59
+ cwd: string;
60
+ requestId?: string;
61
+ }
62
+ export interface OttoWorkspaceOpenResult {
63
+ requestId: string;
64
+ workspace: OttoWorkspaceHandle | null;
65
+ error: string | null;
66
+ }
67
+ export interface OttoWorkspaceArchiveResult {
68
+ requestId: string;
69
+ workspaceId: string;
70
+ archivedAt: string | null;
71
+ error: string | null;
72
+ }
73
+ export type OttoWorkspaceUpdate = Extract<SessionOutboundMessage, {
74
+ type: "workspace_update";
75
+ }>["payload"];
76
+ export type OttoWorkspaceUpdateHandler = (update: OttoWorkspaceUpdate) => void;
77
+ /**
78
+ * A handle is a stable typed reference to a daemon resource. Its identity is the
79
+ * daemon id, and `latest()` only returns the most recent snapshot this handle has
80
+ * seen through construction, `refetch()`, or this handle's local subscription.
81
+ */
82
+ export interface OttoWorkspaceHandle {
83
+ readonly id: string;
84
+ latest(): OttoWorkspace | null;
85
+ /**
86
+ * Fetches a fresh workspace snapshot through the existing workspace list RPC,
87
+ * exact-matches this handle id from the result, and updates `latest()`.
88
+ */
89
+ refetch(options?: {
90
+ requestId?: string;
91
+ }): Promise<OttoWorkspace | null>;
92
+ archive(requestId?: string): Promise<OttoWorkspaceArchiveResult>;
93
+ /**
94
+ * Subscribes to already-emitted daemon workspace_update events for this id.
95
+ * This returns a local unsubscribe function; it does not own app cache state or
96
+ * send a daemon unsubscribe RPC. Call `workspaces.list({ subscribe: {} })` when
97
+ * the daemon should start streaming workspace directory updates.
98
+ */
99
+ subscribe(handler: (update: OttoWorkspaceUpdate) => void): () => void;
100
+ }
101
+ export interface OttoWorkspaceActions {
102
+ list(options?: OttoWorkspaceListOptions): Promise<OttoWorkspaceListResult>;
103
+ ref(workspace: string | OttoWorkspace): OttoWorkspaceHandle;
104
+ open(input: string | OttoWorkspaceOpenOptions, requestId?: string): Promise<OttoWorkspaceOpenResult>;
105
+ create(input: string | OttoWorkspaceOpenOptions, requestId?: string): Promise<OttoWorkspaceOpenResult>;
106
+ archive(workspace: string | OttoWorkspaceHandle, requestId?: string): Promise<OttoWorkspaceArchiveResult>;
107
+ /**
108
+ * Local event subscription over the low-level driver's workspace_update stream.
109
+ * The returned function only removes this SDK listener.
110
+ */
111
+ subscribe(handler: OttoWorkspaceUpdateHandler): () => void;
112
+ }
113
+ type OttoAgentSessionConfig = CreateAgentRequestMessage["config"];
114
+ type OttoAgentProvider = OttoAgentSessionConfig["provider"];
115
+ type OttoAgentConfigOverrides = Partial<Omit<OttoAgentSessionConfig, "provider" | "cwd">>;
116
+ export interface OttoAgentCreateOptions extends OttoAgentConfigOverrides {
117
+ config?: OttoAgentSessionConfig;
118
+ provider?: CreateAgentRequestMessage["config"]["provider"];
119
+ cwd?: string;
120
+ workspaceId?: string;
121
+ initialPrompt?: string;
122
+ clientMessageId?: string;
123
+ outputSchema?: Record<string, unknown>;
124
+ images?: CreateAgentRequestMessage["images"];
125
+ attachments?: CreateAgentRequestMessage["attachments"];
126
+ git?: CreateAgentRequestMessage["git"];
127
+ worktreeName?: string;
128
+ requestId?: string;
129
+ labels?: Record<string, string>;
130
+ }
131
+ export interface OttoAgentRefetchResult {
132
+ agent: OttoAgent;
133
+ project: ProjectPlacementPayload | null;
134
+ }
135
+ export interface OttoAgentTimelineRefetchOptions {
136
+ direction?: FetchAgentTimelineDirection;
137
+ cursor?: FetchAgentTimelineCursor;
138
+ limit?: number;
139
+ projection?: FetchAgentTimelineProjection;
140
+ requestId?: string;
141
+ }
142
+ export interface OttoAgentSendOptions {
143
+ messageId?: string;
144
+ images?: Array<{
145
+ data: string;
146
+ mimeType: string;
147
+ }>;
148
+ attachments?: SendAgentMessageRequest["attachments"];
149
+ }
150
+ export type OttoAgentUpdate = Extract<SessionOutboundMessage, {
151
+ type: "agent_update";
152
+ }>["payload"];
153
+ export type OttoAgentStream = Extract<SessionOutboundMessage, {
154
+ type: "agent_stream";
155
+ }>["payload"];
156
+ export type OttoAgentUpdateHandler = (update: OttoAgentUpdate) => void;
157
+ export interface OttoAgentTimelineHandle {
158
+ /**
159
+ * Fetches a fresh timeline page through the existing daemon RPC. If the daemon
160
+ * includes an agent snapshot in the response, the parent handle's `latest()`
161
+ * is updated to that snapshot.
162
+ */
163
+ refetch(options?: OttoAgentTimelineRefetchOptions): Promise<FetchAgentTimelinePayload>;
164
+ /**
165
+ * Local listener for agent_stream events matching this handle id. It does not
166
+ * retain timeline entries or own application cache state.
167
+ */
168
+ subscribe(handler: (event: OttoAgentStream) => void): () => void;
169
+ }
170
+ /**
171
+ * Agent handles follow the same identity/snapshot rule as workspace handles:
172
+ * `id` is stable, while `latest()` is only the newest snapshot observed by this
173
+ * handle through construction, `refetch()`, timeline refetch, archive, or local
174
+ * agent_update subscription.
175
+ */
176
+ export interface OttoAgentHandle {
177
+ readonly id: string;
178
+ readonly timeline: OttoAgentTimelineHandle;
179
+ latest(): OttoAgent | null;
180
+ refetch(requestId?: string): Promise<OttoAgentRefetchResult | null>;
181
+ send(text: string, options?: OttoAgentSendOptions): Promise<void>;
182
+ archive(): Promise<{
183
+ archivedAt: string;
184
+ }>;
185
+ detach(): Promise<void>;
186
+ subscribe(handler: (update: OttoAgentUpdate) => void): () => void;
187
+ }
188
+ export interface OttoAgentActions {
189
+ ref(agent: string | OttoAgent): OttoAgentHandle;
190
+ create(options: OttoAgentCreateOptions): Promise<OttoAgentHandle>;
191
+ /**
192
+ * Local event subscription over the low-level driver's agent_update stream.
193
+ * The returned function only removes this SDK listener.
194
+ */
195
+ subscribe(handler: OttoAgentUpdateHandler): () => void;
196
+ }
197
+ export interface OttoProviderConfig extends OttoProviderConfigInput {
198
+ provider: OttoAgentProvider;
199
+ }
200
+ export type OttoProviderFeatureValues = Record<string, unknown>;
201
+ export interface OttoProviderConfigInput {
202
+ model?: string;
203
+ modeId?: string;
204
+ thinkingOptionId?: string;
205
+ featureValues?: OttoProviderFeatureValues;
206
+ }
207
+ export type OttoProviderModelsResult = ListProviderModelsResponseMessage["payload"];
208
+ export type OttoProviderModesResult = ListProviderModesResponseMessage["payload"];
209
+ export type OttoProviderFeaturesInput = ListProviderFeaturesRequestMessage["draftConfig"];
210
+ export type OttoProviderFeaturesResult = ListProviderFeaturesResponseMessage["payload"];
211
+ export type OttoProviderAvailabilityResult = ListAvailableProvidersResponse["payload"];
212
+ export type OttoProviderSnapshotResult = GetProvidersSnapshotResponseMessage["payload"];
213
+ export type OttoProviderSnapshotUpdate = Extract<SessionOutboundMessage, {
214
+ type: "providers_snapshot_update";
215
+ }>["payload"];
216
+ export type OttoProviderRefreshResult = RefreshProvidersSnapshotResponseMessage["payload"];
217
+ export type OttoProviderDiagnosticResult = ProviderDiagnosticResponseMessage["payload"];
218
+ export interface OttoProviderListOptions {
219
+ cwd?: string;
220
+ requestId?: string;
221
+ }
222
+ export interface OttoProviderRefreshOptions {
223
+ cwd?: string;
224
+ providers?: OttoAgentProvider[];
225
+ requestId?: string;
226
+ }
227
+ export interface OttoProviderActions {
228
+ codex(input?: OttoProviderConfigInput): OttoProviderConfig;
229
+ claude(input?: OttoProviderConfigInput): OttoProviderConfig;
230
+ opencode(input?: OttoProviderConfigInput): OttoProviderConfig;
231
+ copilot(input?: OttoProviderConfigInput): OttoProviderConfig;
232
+ config(provider: OttoAgentProvider, input?: OttoProviderConfigInput): OttoProviderConfig;
233
+ listModels(provider: OttoAgentProvider, options?: OttoProviderListOptions): Promise<OttoProviderModelsResult>;
234
+ listModes(provider: OttoAgentProvider, options?: OttoProviderListOptions): Promise<OttoProviderModesResult>;
235
+ listFeatures(draftConfig: OttoProviderFeaturesInput, options?: {
236
+ requestId?: string;
237
+ }): Promise<OttoProviderFeaturesResult>;
238
+ listAvailable(options?: {
239
+ requestId?: string;
240
+ }): Promise<OttoProviderAvailabilityResult>;
241
+ snapshot(options?: OttoProviderListOptions): Promise<OttoProviderSnapshotResult>;
242
+ refresh(options?: OttoProviderRefreshOptions): Promise<OttoProviderRefreshResult>;
243
+ diagnostic(provider: OttoAgentProvider, options?: {
244
+ requestId?: string;
245
+ }): Promise<OttoProviderDiagnosticResult>;
246
+ subscribe(handler: (update: OttoProviderSnapshotUpdate) => void): () => void;
247
+ }
248
+ export interface OttoConfigActions {
249
+ /**
250
+ * Reads daemon config through the existing config RPC. Provider profiles,
251
+ * custom provider entries, keys/env, custom binaries, and provider enablement
252
+ * are currently config-file-shaped daemon state, so the SDK exposes this raw
253
+ * typed surface instead of pretending there are higher-level provider-settings
254
+ * RPCs.
255
+ */
256
+ get(requestId?: string): Promise<{
257
+ requestId: string;
258
+ config: MutableDaemonConfig;
259
+ }>;
260
+ /**
261
+ * Patches daemon config through the existing config RPC. The daemon validates
262
+ * and persists supported fields; unsupported provider/settings workflows remain
263
+ * daemon gaps until first-class RPCs exist.
264
+ */
265
+ patch(config: MutableDaemonConfigPatch, requestId?: string): Promise<{
266
+ requestId: string;
267
+ config: MutableDaemonConfig;
268
+ }>;
269
+ }
270
+ export interface OttoClient {
271
+ readonly workspaces: OttoWorkspaceActions;
272
+ readonly agents: OttoAgentActions;
273
+ readonly providers: OttoProviderActions;
274
+ readonly config: OttoConfigActions;
275
+ connect(): Promise<void>;
276
+ close(): Promise<void>;
277
+ ensureConnected(): void;
278
+ getConnectionState(): ConnectionState;
279
+ }
280
+ export declare function createOttoClient(config: OttoClientConfig): OttoClient;
281
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,176 @@
1
+ import { DaemonClient } from "./daemon-client.js";
2
+ export { DaemonClient };
3
+ export function createOttoClient(config) {
4
+ const daemonClient = new DaemonClient({
5
+ ...config,
6
+ clientId: config.clientId ?? createGeneratedClientId(),
7
+ clientType: "cli",
8
+ });
9
+ const createWorkspaceHandle = createWorkspaceHandleFactory(daemonClient);
10
+ const createAgentHandle = createAgentHandleFactory(daemonClient);
11
+ return {
12
+ workspaces: {
13
+ list: (options) => daemonClient.fetchWorkspaces(options),
14
+ ref: (workspace) => createWorkspaceHandle(workspace),
15
+ open: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
16
+ create: (input, requestId) => openWorkspace(daemonClient, createWorkspaceHandle, input, requestId),
17
+ archive: (workspace, requestId) => daemonClient.archiveWorkspace(resolveWorkspaceId(workspace), requestId),
18
+ subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
19
+ handler(message.payload);
20
+ }),
21
+ },
22
+ agents: {
23
+ ref: (agent) => createAgentHandle(agent),
24
+ create: async (options) => {
25
+ const agent = await daemonClient.createAgent(options);
26
+ return createAgentHandle(agent);
27
+ },
28
+ subscribe: (handler) => daemonClient.on("agent_update", (message) => {
29
+ handler(message.payload);
30
+ }),
31
+ },
32
+ providers: {
33
+ codex: (input) => providerConfig("codex", input),
34
+ claude: (input) => providerConfig("claude", input),
35
+ opencode: (input) => providerConfig("opencode", input),
36
+ copilot: (input) => providerConfig("copilot", input),
37
+ config: (provider, input) => providerConfig(provider, input),
38
+ listModels: (provider, options) => daemonClient.listProviderModels(provider, options),
39
+ listModes: (provider, options) => daemonClient.listProviderModes(provider, options),
40
+ listFeatures: (draftConfig, options) => daemonClient.listProviderFeatures(draftConfig, options),
41
+ listAvailable: (options) => daemonClient.listAvailableProviders(options),
42
+ snapshot: (options) => daemonClient.getProvidersSnapshot(options),
43
+ refresh: (options) => daemonClient.refreshProvidersSnapshot(options),
44
+ diagnostic: (provider, options) => daemonClient.getProviderDiagnostic(provider, options),
45
+ subscribe: (handler) => daemonClient.on("providers_snapshot_update", (message) => {
46
+ handler(message.payload);
47
+ }),
48
+ },
49
+ config: {
50
+ get: (requestId) => daemonClient.getDaemonConfig(requestId),
51
+ patch: (patch, requestId) => daemonClient.patchDaemonConfig(patch, requestId),
52
+ },
53
+ connect: () => daemonClient.connect(),
54
+ close: () => daemonClient.close(),
55
+ ensureConnected: () => daemonClient.ensureConnected(),
56
+ getConnectionState: () => daemonClient.getConnectionState(),
57
+ };
58
+ }
59
+ function createWorkspaceHandleFactory(daemonClient) {
60
+ return (workspace) => {
61
+ const id = typeof workspace === "string" ? workspace : workspace.id;
62
+ let latest = typeof workspace === "string" ? null : workspace;
63
+ return {
64
+ id,
65
+ latest: () => latest,
66
+ refetch: async (options) => {
67
+ // Best-effort: fetches one page and matches by id client-side, so a workspace beyond
68
+ // the first page won't be found. TODO: add a "get workspace by id" lookup and resolve
69
+ // by exact id instead of paging.
70
+ const result = await daemonClient.fetchWorkspaces({
71
+ requestId: options?.requestId,
72
+ page: { limit: 25 },
73
+ });
74
+ latest = result.entries.find((entry) => entry.id === id) ?? null;
75
+ return latest;
76
+ },
77
+ archive: async (requestId) => {
78
+ const result = await daemonClient.archiveWorkspace(id, requestId);
79
+ if (latest) {
80
+ latest = { ...latest, archivingAt: result.archivedAt };
81
+ }
82
+ return result;
83
+ },
84
+ subscribe: (handler) => daemonClient.on("workspace_update", (message) => {
85
+ const update = message.payload;
86
+ if (update.kind === "upsert" && update.workspace.id === id) {
87
+ latest = update.workspace;
88
+ handler(update);
89
+ }
90
+ if (update.kind === "remove" && update.id === id) {
91
+ latest = null;
92
+ handler(update);
93
+ }
94
+ }),
95
+ };
96
+ };
97
+ }
98
+ function createAgentHandleFactory(daemonClient) {
99
+ return (agent) => {
100
+ const id = typeof agent === "string" ? agent : agent.id;
101
+ let latest = typeof agent === "string" ? null : agent;
102
+ const handle = {
103
+ id,
104
+ timeline: {
105
+ refetch: async (options) => {
106
+ const result = await daemonClient.fetchAgentTimeline(id, options);
107
+ if (result.agent) {
108
+ latest = result.agent;
109
+ }
110
+ return result;
111
+ },
112
+ subscribe: (handler) => daemonClient.on("agent_stream", (message) => {
113
+ if (message.payload.agentId === id) {
114
+ handler(message.payload);
115
+ }
116
+ }),
117
+ },
118
+ latest: () => latest,
119
+ refetch: async (requestId) => {
120
+ const result = await daemonClient.fetchAgent({ agentId: id, requestId });
121
+ latest = result?.agent ?? null;
122
+ return result;
123
+ },
124
+ send: (text, options) => daemonClient.sendAgentMessage(id, text, options),
125
+ archive: async () => {
126
+ const result = await daemonClient.archiveAgent(id);
127
+ if (latest) {
128
+ latest = { ...latest, archivedAt: result.archivedAt };
129
+ }
130
+ return result;
131
+ },
132
+ detach: async () => {
133
+ await daemonClient.detachAgent(id);
134
+ },
135
+ subscribe: (handler) => daemonClient.on("agent_update", (message) => {
136
+ const update = message.payload;
137
+ if (update.kind === "upsert" && update.agent.id === id) {
138
+ latest = update.agent;
139
+ handler(update);
140
+ }
141
+ if (update.kind === "remove" && update.agentId === id) {
142
+ latest = null;
143
+ handler(update);
144
+ }
145
+ }),
146
+ };
147
+ return handle;
148
+ };
149
+ }
150
+ async function openWorkspace(daemonClient, createWorkspaceHandle, input, requestId) {
151
+ const options = typeof input === "string" ? { cwd: input, requestId } : input;
152
+ const result = await daemonClient.openProject(options.cwd, options.requestId);
153
+ return {
154
+ ...result,
155
+ workspace: result.workspace ? createWorkspaceHandle(result.workspace) : null,
156
+ };
157
+ }
158
+ function resolveWorkspaceId(workspace) {
159
+ return typeof workspace === "string" ? workspace : workspace.id;
160
+ }
161
+ function providerConfig(provider, input = {}) {
162
+ return {
163
+ provider,
164
+ ...(input.model !== undefined ? { model: input.model } : {}),
165
+ ...(input.modeId !== undefined ? { modeId: input.modeId } : {}),
166
+ ...(input.thinkingOptionId !== undefined ? { thinkingOptionId: input.thinkingOptionId } : {}),
167
+ ...(input.featureValues !== undefined ? { featureValues: input.featureValues } : {}),
168
+ };
169
+ }
170
+ function createGeneratedClientId() {
171
+ const randomId = typeof globalThis.crypto?.randomUUID === "function"
172
+ ? globalThis.crypto.randomUUID()
173
+ : Math.random().toString(36).slice(2);
174
+ return `otto-sdk-${randomId}`;
175
+ }
176
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,28 @@
1
+ import { type TerminalStreamFrame } from "@otto-code/protocol/binary-frames/index";
2
+ import type { TerminalInput, TerminalState } from "@otto-code/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 "@otto-code/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": "@otto-code/client",
3
+ "version": "0.5.0",
4
+ "description": "Otto 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=@otto-code/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
+ "@otto-code/protocol": "0.5.0",
39
+ "@otto-code/relay": "0.5.0",
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
+ }