@lzdi/pty-remote-protocol 0.1.3
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.
- package/package.json +24 -0
- package/src/protocol.ts +267 -0
- package/src/runtime-types.ts +87 -0
- package/src/terminal-frame.ts +295 -0
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lzdi/pty-remote-protocol",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/234687552/pty-remote"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=23.0.0"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
"./protocol": "./src/protocol.ts",
|
|
15
|
+
"./protocol.ts": "./src/protocol.ts",
|
|
16
|
+
"./terminal-frame": "./src/terminal-frame.ts",
|
|
17
|
+
"./terminal-frame.ts": "./src/terminal-frame.ts",
|
|
18
|
+
"./runtime-types": "./src/runtime-types.ts",
|
|
19
|
+
"./runtime-types.ts": "./src/runtime-types.ts"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src"
|
|
23
|
+
]
|
|
24
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import type { ChatMessage, CliDescriptor, ProviderId, RuntimeSnapshot } from './runtime-types.ts';
|
|
2
|
+
import type { TerminalFramePatch, TerminalFrameSnapshot } from './terminal-frame.ts';
|
|
3
|
+
|
|
4
|
+
export interface CliRegisterPayload {
|
|
5
|
+
cliId?: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
cwd: string;
|
|
8
|
+
supportedProviders: ProviderId[];
|
|
9
|
+
runtimes: Partial<Record<ProviderId, ProviderRuntimeRegistration>>;
|
|
10
|
+
runtimeBackend: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ProviderRuntimeRegistration {
|
|
14
|
+
cwd: string;
|
|
15
|
+
sessionId: string | null;
|
|
16
|
+
conversationKey: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CliRegisterResult {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
cliId: string;
|
|
22
|
+
error?: string;
|
|
23
|
+
errorCode?: 'conflict';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ProjectSessionSummary {
|
|
27
|
+
providerId: ProviderId;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
cwd: string;
|
|
30
|
+
title: string;
|
|
31
|
+
preview: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
messageCount: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ManagedPtyLifecycle = 'attached' | 'detached' | 'exited' | 'error';
|
|
37
|
+
|
|
38
|
+
export interface ManagedPtyHandleSummary {
|
|
39
|
+
conversationKey: string;
|
|
40
|
+
sessionId: string | null;
|
|
41
|
+
cwd: string;
|
|
42
|
+
label: string;
|
|
43
|
+
lifecycle: ManagedPtyLifecycle;
|
|
44
|
+
hasPty: boolean;
|
|
45
|
+
lastActivityAt: number | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type CliCommandName =
|
|
49
|
+
| 'send-message'
|
|
50
|
+
| 'list-slash-commands'
|
|
51
|
+
| 'upload-attachment'
|
|
52
|
+
| 'delete-attachment'
|
|
53
|
+
| 'stop-message'
|
|
54
|
+
| 'reset-session'
|
|
55
|
+
| 'get-runtime-snapshot'
|
|
56
|
+
| 'get-older-messages'
|
|
57
|
+
| 'pick-project-directory'
|
|
58
|
+
| 'list-project-conversations'
|
|
59
|
+
| 'list-managed-pty-handles'
|
|
60
|
+
| 'select-conversation'
|
|
61
|
+
| 'cleanup-project'
|
|
62
|
+
| 'cleanup-conversation';
|
|
63
|
+
|
|
64
|
+
export interface CliCommandPayloadMap {
|
|
65
|
+
'send-message': { content: string };
|
|
66
|
+
'list-slash-commands': Record<string, never>;
|
|
67
|
+
'upload-attachment': {
|
|
68
|
+
contentBase64: string;
|
|
69
|
+
conversationKey: string | null;
|
|
70
|
+
cwd: string;
|
|
71
|
+
filename: string;
|
|
72
|
+
mimeType: string;
|
|
73
|
+
sessionId: string | null;
|
|
74
|
+
size: number;
|
|
75
|
+
};
|
|
76
|
+
'delete-attachment': {
|
|
77
|
+
attachmentId: string;
|
|
78
|
+
};
|
|
79
|
+
'stop-message': Record<string, never>;
|
|
80
|
+
'reset-session': Record<string, never>;
|
|
81
|
+
'get-runtime-snapshot': Record<string, never>;
|
|
82
|
+
'get-older-messages': {
|
|
83
|
+
beforeMessageId?: string;
|
|
84
|
+
maxMessages?: number;
|
|
85
|
+
};
|
|
86
|
+
'pick-project-directory': Record<string, never>;
|
|
87
|
+
'list-project-conversations': {
|
|
88
|
+
cwd: string;
|
|
89
|
+
maxSessions?: number;
|
|
90
|
+
};
|
|
91
|
+
'list-managed-pty-handles': Record<string, never>;
|
|
92
|
+
'select-conversation': {
|
|
93
|
+
cwd: string;
|
|
94
|
+
conversationKey: string;
|
|
95
|
+
sessionId: string | null;
|
|
96
|
+
clientRequestId?: string | null;
|
|
97
|
+
};
|
|
98
|
+
'cleanup-project': {
|
|
99
|
+
cwd: string;
|
|
100
|
+
};
|
|
101
|
+
'cleanup-conversation': {
|
|
102
|
+
cwd: string;
|
|
103
|
+
conversationKey: string;
|
|
104
|
+
sessionId: string | null;
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface GetRuntimeSnapshotResultPayload {
|
|
109
|
+
snapshot: RuntimeSnapshot;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface GetOlderMessagesResultPayload {
|
|
113
|
+
messages: ChatMessage[];
|
|
114
|
+
providerId: ProviderId | null;
|
|
115
|
+
conversationKey: string | null;
|
|
116
|
+
sessionId: string | null;
|
|
117
|
+
hasOlderMessages: boolean;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface PickProjectDirectoryResultPayload {
|
|
121
|
+
cwd: string;
|
|
122
|
+
label: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ListProjectSessionsResultPayload {
|
|
126
|
+
providerId: ProviderId;
|
|
127
|
+
cwd: string;
|
|
128
|
+
label: string;
|
|
129
|
+
sessions: ProjectSessionSummary[];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface ListManagedPtyHandlesResultPayload {
|
|
133
|
+
providerId: ProviderId;
|
|
134
|
+
handles: ManagedPtyHandleSummary[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface SelectConversationResultPayload {
|
|
138
|
+
providerId: ProviderId;
|
|
139
|
+
cwd: string;
|
|
140
|
+
label: string;
|
|
141
|
+
conversationKey: string;
|
|
142
|
+
sessionId: string | null;
|
|
143
|
+
clientRequestId?: string | null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface UploadAttachmentResultPayload {
|
|
147
|
+
attachmentId: string;
|
|
148
|
+
filename: string;
|
|
149
|
+
mimeType: string;
|
|
150
|
+
path: string;
|
|
151
|
+
size: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ListSlashCommandsResultPayload {
|
|
155
|
+
providerId: ProviderId;
|
|
156
|
+
commands: string[];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CliCommandResultPayloadMap {
|
|
160
|
+
'send-message': null;
|
|
161
|
+
'list-slash-commands': ListSlashCommandsResultPayload;
|
|
162
|
+
'upload-attachment': UploadAttachmentResultPayload;
|
|
163
|
+
'delete-attachment': null;
|
|
164
|
+
'stop-message': null;
|
|
165
|
+
'reset-session': null;
|
|
166
|
+
'get-runtime-snapshot': GetRuntimeSnapshotResultPayload;
|
|
167
|
+
'get-older-messages': GetOlderMessagesResultPayload;
|
|
168
|
+
'pick-project-directory': PickProjectDirectoryResultPayload;
|
|
169
|
+
'list-project-conversations': ListProjectSessionsResultPayload;
|
|
170
|
+
'list-managed-pty-handles': ListManagedPtyHandlesResultPayload;
|
|
171
|
+
'select-conversation': SelectConversationResultPayload;
|
|
172
|
+
'cleanup-project': null;
|
|
173
|
+
'cleanup-conversation': null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface CliCommandEnvelope<TName extends CliCommandName = CliCommandName> {
|
|
177
|
+
requestId: string;
|
|
178
|
+
targetProviderId?: ProviderId | null;
|
|
179
|
+
name: TName;
|
|
180
|
+
payload: CliCommandPayloadMap[TName];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface CliCommandResult<TName extends CliCommandName = CliCommandName> {
|
|
184
|
+
ok: boolean;
|
|
185
|
+
error?: string;
|
|
186
|
+
payload?: CliCommandResultPayloadMap[TName];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface TerminalFramePatchPayload {
|
|
190
|
+
cliId: string;
|
|
191
|
+
providerId: ProviderId;
|
|
192
|
+
conversationKey: string | null;
|
|
193
|
+
patch: TerminalFramePatch;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface TerminalSessionEvictedPayload {
|
|
197
|
+
cliId: string;
|
|
198
|
+
providerId: ProviderId;
|
|
199
|
+
conversationKey: string | null;
|
|
200
|
+
reason: string;
|
|
201
|
+
sessionId: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface RuntimeSubscriptionPayload {
|
|
205
|
+
targetCliId: string | null;
|
|
206
|
+
targetProviderId: ProviderId | null;
|
|
207
|
+
conversationKey: string | null;
|
|
208
|
+
sessionId: string | null;
|
|
209
|
+
lastSeq?: number | null;
|
|
210
|
+
terminalEnabled?: boolean | null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export interface TerminalFrameSyncRequestPayload {
|
|
214
|
+
targetCliId: string | null;
|
|
215
|
+
targetProviderId: ProviderId | null;
|
|
216
|
+
lastRevision: number | null;
|
|
217
|
+
sessionId: string | null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export interface TerminalResizePayload {
|
|
221
|
+
targetCliId: string | null;
|
|
222
|
+
targetProviderId: ProviderId | null;
|
|
223
|
+
cols: number;
|
|
224
|
+
rows: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface TerminalFrameSyncResultPayload {
|
|
228
|
+
ok: boolean;
|
|
229
|
+
error?: string;
|
|
230
|
+
providerId: ProviderId | null;
|
|
231
|
+
sessionId: string | null;
|
|
232
|
+
mode?: 'patches' | 'snapshot';
|
|
233
|
+
snapshot?: TerminalFrameSnapshot;
|
|
234
|
+
patches?: TerminalFramePatch[];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface RuntimeSnapshotPayload {
|
|
238
|
+
cliId: string;
|
|
239
|
+
providerId: ProviderId;
|
|
240
|
+
snapshot: RuntimeSnapshot;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface MessagesUpsertPayload {
|
|
244
|
+
cliId: string;
|
|
245
|
+
providerId: ProviderId | null;
|
|
246
|
+
conversationKey: string | null;
|
|
247
|
+
sessionId: string | null;
|
|
248
|
+
upserts: ChatMessage[];
|
|
249
|
+
recentMessageIds: string[];
|
|
250
|
+
hasOlderMessages: boolean;
|
|
251
|
+
seq?: number;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface WebInitPayload {
|
|
255
|
+
clis: CliDescriptor[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface CliStatusPayload {
|
|
259
|
+
clis: CliDescriptor[];
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface WebCommandEnvelope<TName extends CliCommandName = CliCommandName> {
|
|
263
|
+
targetCliId: string | null;
|
|
264
|
+
targetProviderId?: ProviderId | null;
|
|
265
|
+
name: TName;
|
|
266
|
+
payload: CliCommandPayloadMap[TName];
|
|
267
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export type Role = 'user' | 'assistant';
|
|
2
|
+
export type MessageStatus = 'complete' | 'streaming' | 'error';
|
|
3
|
+
export type RuntimeStatus = 'idle' | 'starting' | 'running' | 'error';
|
|
4
|
+
export type ProviderId = 'claude' | 'codex';
|
|
5
|
+
|
|
6
|
+
export const PROVIDER_LABELS: Record<ProviderId, string> = {
|
|
7
|
+
claude: 'claude',
|
|
8
|
+
codex: 'codex'
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const PROVIDER_ORDER: ProviderId[] = ['claude', 'codex'];
|
|
12
|
+
|
|
13
|
+
export const BUILTIN_SLASH_COMMANDS: Record<ProviderId, string[]> = {
|
|
14
|
+
claude: ['clear', 'compact', 'context', 'cost', 'doctor', 'help', 'plan', 'stats', 'status'],
|
|
15
|
+
codex: ['review', 'new', 'compat', 'undo', 'diff', 'status']
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export interface TextChatMessageBlock {
|
|
19
|
+
id: string;
|
|
20
|
+
type: 'text';
|
|
21
|
+
text: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ToolUseChatMessageBlock {
|
|
25
|
+
id: string;
|
|
26
|
+
type: 'tool_use';
|
|
27
|
+
toolCallId?: string;
|
|
28
|
+
toolName: string;
|
|
29
|
+
input: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface ToolResultChatMessageBlock {
|
|
33
|
+
id: string;
|
|
34
|
+
type: 'tool_result';
|
|
35
|
+
toolCallId?: string;
|
|
36
|
+
content: string;
|
|
37
|
+
isError: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ChatMessageBlock = TextChatMessageBlock | ToolUseChatMessageBlock | ToolResultChatMessageBlock;
|
|
41
|
+
|
|
42
|
+
export interface ChatAttachment {
|
|
43
|
+
attachmentId: string;
|
|
44
|
+
filename: string;
|
|
45
|
+
mimeType: string;
|
|
46
|
+
path: string;
|
|
47
|
+
size: number;
|
|
48
|
+
previewUrl?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ChatMessage {
|
|
52
|
+
id: string;
|
|
53
|
+
role: Role;
|
|
54
|
+
blocks: ChatMessageBlock[];
|
|
55
|
+
attachments?: ChatAttachment[];
|
|
56
|
+
status: MessageStatus;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RuntimeSnapshot {
|
|
61
|
+
providerId: ProviderId | null;
|
|
62
|
+
conversationKey: string | null;
|
|
63
|
+
status: RuntimeStatus;
|
|
64
|
+
sessionId: string | null;
|
|
65
|
+
messages: ChatMessage[];
|
|
66
|
+
hasOlderMessages: boolean;
|
|
67
|
+
lastError: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CliProviderRuntimeDescriptor {
|
|
71
|
+
cwd: string;
|
|
72
|
+
conversationKey: string | null;
|
|
73
|
+
status: RuntimeStatus;
|
|
74
|
+
sessionId: string | null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CliDescriptor {
|
|
78
|
+
cliId: string;
|
|
79
|
+
label: string;
|
|
80
|
+
cwd: string;
|
|
81
|
+
supportedProviders: ProviderId[];
|
|
82
|
+
runtimes: Partial<Record<ProviderId, CliProviderRuntimeDescriptor>>;
|
|
83
|
+
runtimeBackend: string;
|
|
84
|
+
connected: boolean;
|
|
85
|
+
connectedAt: string | null;
|
|
86
|
+
lastSeenAt: string | null;
|
|
87
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
export interface TerminalFrameRun {
|
|
2
|
+
style: string;
|
|
3
|
+
text: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface TerminalFrameLine {
|
|
7
|
+
wrapped: boolean;
|
|
8
|
+
runs: TerminalFrameRun[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TerminalFrameSnapshot {
|
|
12
|
+
sessionId: string | null;
|
|
13
|
+
revision: number;
|
|
14
|
+
cols: number;
|
|
15
|
+
rows: number;
|
|
16
|
+
tailStart: number;
|
|
17
|
+
cursorX: number;
|
|
18
|
+
cursorY: number;
|
|
19
|
+
viewportY: number;
|
|
20
|
+
baseY: number;
|
|
21
|
+
totalLines: number;
|
|
22
|
+
lines: TerminalFrameLine[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type TerminalColorMode = 'default' | 'palette' | 'rgb';
|
|
26
|
+
|
|
27
|
+
export interface TerminalFrameStyle {
|
|
28
|
+
flags: number;
|
|
29
|
+
fgMode: TerminalColorMode;
|
|
30
|
+
fg: number;
|
|
31
|
+
bgMode: TerminalColorMode;
|
|
32
|
+
bg: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type TerminalFramePatchOp =
|
|
36
|
+
| {
|
|
37
|
+
type: 'reset';
|
|
38
|
+
snapshot: TerminalFrameSnapshot;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
type: 'trimHead';
|
|
42
|
+
count: number;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: 'spliceLines';
|
|
46
|
+
start: number;
|
|
47
|
+
deleteCount: number;
|
|
48
|
+
lines: TerminalFrameLine[];
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
type: 'meta';
|
|
52
|
+
cols: number;
|
|
53
|
+
rows: number;
|
|
54
|
+
tailStart: number;
|
|
55
|
+
cursorX: number;
|
|
56
|
+
cursorY: number;
|
|
57
|
+
viewportY: number;
|
|
58
|
+
baseY: number;
|
|
59
|
+
totalLines: number;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export interface TerminalFramePatch {
|
|
63
|
+
sessionId: string | null;
|
|
64
|
+
baseRevision: number;
|
|
65
|
+
revision: number;
|
|
66
|
+
ops: TerminalFramePatchOp[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DEFAULT_STYLE: TerminalFrameStyle = {
|
|
70
|
+
flags: 0,
|
|
71
|
+
fgMode: 'default',
|
|
72
|
+
fg: 0,
|
|
73
|
+
bgMode: 'default',
|
|
74
|
+
bg: 0
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const ANSI_16_COLOR_PALETTE = [
|
|
78
|
+
'#000000',
|
|
79
|
+
'#cd3131',
|
|
80
|
+
'#0dbc79',
|
|
81
|
+
'#949800',
|
|
82
|
+
'#2472c8',
|
|
83
|
+
'#bc3fbc',
|
|
84
|
+
'#11a8cd',
|
|
85
|
+
'#e5e5e5',
|
|
86
|
+
'#666666',
|
|
87
|
+
'#f14c4c',
|
|
88
|
+
'#23d18b',
|
|
89
|
+
'#f5f543',
|
|
90
|
+
'#3b8eea',
|
|
91
|
+
'#d670d6',
|
|
92
|
+
'#29b8db',
|
|
93
|
+
'#ffffff'
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
function cloneTerminalFrameRun(run: TerminalFrameRun): TerminalFrameRun {
|
|
97
|
+
return {
|
|
98
|
+
style: run.style,
|
|
99
|
+
text: run.text
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cloneTerminalFrameLine(line: TerminalFrameLine): TerminalFrameLine {
|
|
104
|
+
return {
|
|
105
|
+
wrapped: line.wrapped,
|
|
106
|
+
runs: line.runs.map(cloneTerminalFrameRun)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function cloneTerminalFrameSnapshot(snapshot: TerminalFrameSnapshot): TerminalFrameSnapshot {
|
|
111
|
+
return {
|
|
112
|
+
sessionId: snapshot.sessionId,
|
|
113
|
+
revision: snapshot.revision,
|
|
114
|
+
cols: snapshot.cols,
|
|
115
|
+
rows: snapshot.rows,
|
|
116
|
+
tailStart: snapshot.tailStart,
|
|
117
|
+
cursorX: snapshot.cursorX,
|
|
118
|
+
cursorY: snapshot.cursorY,
|
|
119
|
+
viewportY: snapshot.viewportY,
|
|
120
|
+
baseY: snapshot.baseY,
|
|
121
|
+
totalLines: snapshot.totalLines,
|
|
122
|
+
lines: snapshot.lines.map(cloneTerminalFrameLine)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function createEmptyTerminalFrameSnapshot(
|
|
127
|
+
sessionId: string | null = null,
|
|
128
|
+
revision = 0,
|
|
129
|
+
cols = 0,
|
|
130
|
+
rows = 0
|
|
131
|
+
): TerminalFrameSnapshot {
|
|
132
|
+
return {
|
|
133
|
+
sessionId,
|
|
134
|
+
revision,
|
|
135
|
+
cols,
|
|
136
|
+
rows,
|
|
137
|
+
tailStart: 0,
|
|
138
|
+
cursorX: 0,
|
|
139
|
+
cursorY: 0,
|
|
140
|
+
viewportY: 0,
|
|
141
|
+
baseY: 0,
|
|
142
|
+
totalLines: 0,
|
|
143
|
+
lines: []
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function terminalFrameLineEquals(left: TerminalFrameLine | undefined, right: TerminalFrameLine | undefined): boolean {
|
|
148
|
+
if (!left || !right) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
if (left.wrapped !== right.wrapped || left.runs.length !== right.runs.length) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
for (let index = 0; index < left.runs.length; index += 1) {
|
|
155
|
+
const leftRun = left.runs[index];
|
|
156
|
+
const rightRun = right.runs[index];
|
|
157
|
+
if (!leftRun || !rightRun || leftRun.style !== rightRun.style || leftRun.text !== rightRun.text) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function encodeTerminalFrameStyle(style: TerminalFrameStyle): string {
|
|
165
|
+
if (
|
|
166
|
+
style.flags === DEFAULT_STYLE.flags &&
|
|
167
|
+
style.fgMode === DEFAULT_STYLE.fgMode &&
|
|
168
|
+
style.fg === DEFAULT_STYLE.fg &&
|
|
169
|
+
style.bgMode === DEFAULT_STYLE.bgMode &&
|
|
170
|
+
style.bg === DEFAULT_STYLE.bg
|
|
171
|
+
) {
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return `${style.flags};${style.fgMode}:${style.fg};${style.bgMode}:${style.bg}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function decodeTerminalFrameStyle(value: string): TerminalFrameStyle {
|
|
179
|
+
if (!value) {
|
|
180
|
+
return DEFAULT_STYLE;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const [flagsPart = '0', fgPart = 'default:0', bgPart = 'default:0'] = value.split(';');
|
|
184
|
+
const [fgModePart = 'default', fgValuePart = '0'] = fgPart.split(':');
|
|
185
|
+
const [bgModePart = 'default', bgValuePart = '0'] = bgPart.split(':');
|
|
186
|
+
const fgMode = normalizeColorMode(fgModePart);
|
|
187
|
+
const bgMode = normalizeColorMode(bgModePart);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
flags: Number.parseInt(flagsPart, 10) || 0,
|
|
191
|
+
fgMode,
|
|
192
|
+
fg: Number.parseInt(fgValuePart, 10) || 0,
|
|
193
|
+
bgMode,
|
|
194
|
+
bg: Number.parseInt(bgValuePart, 10) || 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeColorMode(value: string): TerminalColorMode {
|
|
199
|
+
if (value === 'palette' || value === 'rgb') {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
return 'default';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function rgbNumberToHex(value: number): string {
|
|
206
|
+
const normalized = Math.max(0, Math.min(value, 0xffffff));
|
|
207
|
+
return `#${normalized.toString(16).padStart(6, '0')}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function terminalPaletteIndexToCss(index: number): string {
|
|
211
|
+
if (index >= 0 && index < ANSI_16_COLOR_PALETTE.length) {
|
|
212
|
+
return ANSI_16_COLOR_PALETTE[index] ?? ANSI_16_COLOR_PALETTE[0];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (index >= 16 && index <= 231) {
|
|
216
|
+
const cubeIndex = index - 16;
|
|
217
|
+
const redIndex = Math.floor(cubeIndex / 36);
|
|
218
|
+
const greenIndex = Math.floor((cubeIndex % 36) / 6);
|
|
219
|
+
const blueIndex = cubeIndex % 6;
|
|
220
|
+
const component = [0, 95, 135, 175, 215, 255];
|
|
221
|
+
return rgbNumberToHex(
|
|
222
|
+
((component[redIndex] ?? 0) << 16) |
|
|
223
|
+
((component[greenIndex] ?? 0) << 8) |
|
|
224
|
+
(component[blueIndex] ?? 0)
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (index >= 232 && index <= 255) {
|
|
229
|
+
const value = 8 + (index - 232) * 10;
|
|
230
|
+
return rgbNumberToHex((value << 16) | (value << 8) | value);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return ANSI_16_COLOR_PALETTE[0];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function resolveTerminalFrameColor(mode: TerminalColorMode, value: number, fallback: string): string {
|
|
237
|
+
if (mode === 'rgb') {
|
|
238
|
+
return rgbNumberToHex(value);
|
|
239
|
+
}
|
|
240
|
+
if (mode === 'palette') {
|
|
241
|
+
return terminalPaletteIndexToCss(value);
|
|
242
|
+
}
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function applyTerminalFramePatch(
|
|
247
|
+
currentSnapshot: TerminalFrameSnapshot | null,
|
|
248
|
+
patch: TerminalFramePatch
|
|
249
|
+
): TerminalFrameSnapshot {
|
|
250
|
+
let nextSnapshot = currentSnapshot ? cloneTerminalFrameSnapshot(currentSnapshot) : createEmptyTerminalFrameSnapshot();
|
|
251
|
+
|
|
252
|
+
for (const op of patch.ops) {
|
|
253
|
+
if (op.type === 'reset') {
|
|
254
|
+
nextSnapshot = cloneTerminalFrameSnapshot(op.snapshot);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (op.type === 'spliceLines') {
|
|
259
|
+
const nextLines = nextSnapshot.lines.slice();
|
|
260
|
+
nextLines.splice(op.start, op.deleteCount, ...op.lines.map(cloneTerminalFrameLine));
|
|
261
|
+
nextSnapshot = {
|
|
262
|
+
...nextSnapshot,
|
|
263
|
+
lines: nextLines
|
|
264
|
+
};
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (op.type === 'trimHead') {
|
|
269
|
+
nextSnapshot = {
|
|
270
|
+
...nextSnapshot,
|
|
271
|
+
lines: nextSnapshot.lines.slice(op.count)
|
|
272
|
+
};
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
nextSnapshot = {
|
|
277
|
+
...nextSnapshot,
|
|
278
|
+
cols: op.cols,
|
|
279
|
+
rows: op.rows,
|
|
280
|
+
tailStart: op.tailStart,
|
|
281
|
+
cursorX: op.cursorX,
|
|
282
|
+
cursorY: op.cursorY,
|
|
283
|
+
viewportY: op.viewportY,
|
|
284
|
+
baseY: op.baseY,
|
|
285
|
+
totalLines: op.totalLines
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
...nextSnapshot,
|
|
291
|
+
sessionId: patch.sessionId,
|
|
292
|
+
revision: patch.revision,
|
|
293
|
+
totalLines: nextSnapshot.lines.length
|
|
294
|
+
};
|
|
295
|
+
}
|