@somewhatintelligent/cc-ws-client 0.1.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.
- package/README.md +16 -0
- package/package.json +24 -0
- package/src/controls.ts +95 -0
- package/src/index.ts +109 -0
- package/src/messages.ts +312 -0
- package/src/modes.ts +68 -0
- package/src/permissions.ts +105 -0
- package/src/persistence.ts +121 -0
- package/src/protocol.ts +525 -0
- package/src/session.ts +662 -0
- package/src/shell.ts +272 -0
- package/src/tasks.ts +246 -0
- package/src/usage.ts +30 -0
- package/src/ws.ts +98 -0
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
// Wire protocol — every JSON shape exchanged between the browser and the
|
|
2
|
+
// bridge. Two channels share the same WS:
|
|
3
|
+
// 1. Claude Code stream-json frames (forwarded verbatim to/from the child).
|
|
4
|
+
// 2. _local frames (intercepted by the bridge, never reach the child).
|
|
5
|
+
//
|
|
6
|
+
// Field sets are pulled from the binary's own runtime Zod schemas. Run
|
|
7
|
+
// `bun packages/client/scripts/extract-claude-schemas.ts extract` against
|
|
8
|
+
// the active `claude` binary to reproduce — see scripts/README.md for the
|
|
9
|
+
// runbook + drift-detection workflow. Variants we don't actively consume
|
|
10
|
+
// still ride the same discriminated union so consumers can narrow
|
|
11
|
+
// without `as any` escape hatches. Unknown system subtypes fall through
|
|
12
|
+
// to UnknownSystemFrame. Pinned to v2.1.129 (2026-05-05).
|
|
13
|
+
|
|
14
|
+
// ---------- shared ----------
|
|
15
|
+
|
|
16
|
+
export type Json = unknown;
|
|
17
|
+
|
|
18
|
+
export type SessionMode =
|
|
19
|
+
| { kind: "new" }
|
|
20
|
+
| { kind: "continue" }
|
|
21
|
+
| { kind: "resume"; sessionId: string };
|
|
22
|
+
|
|
23
|
+
// ---------- content blocks (inside message.content arrays) ----------
|
|
24
|
+
|
|
25
|
+
export type TextContentBlock = { type: "text"; text: string };
|
|
26
|
+
export type ThinkingContentBlock = { type: "thinking"; thinking: string };
|
|
27
|
+
export type ToolUseContentBlock = {
|
|
28
|
+
type: "tool_use";
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
input: unknown;
|
|
32
|
+
};
|
|
33
|
+
export type ToolResultContentBlock = {
|
|
34
|
+
type: "tool_result";
|
|
35
|
+
tool_use_id: string;
|
|
36
|
+
content: string | Array<TextContentBlock | { type: "image"; source: unknown }>;
|
|
37
|
+
is_error?: boolean;
|
|
38
|
+
};
|
|
39
|
+
export type ImageContentBlock = { type: "image"; source: unknown };
|
|
40
|
+
|
|
41
|
+
export type AssistantContentBlock =
|
|
42
|
+
| TextContentBlock
|
|
43
|
+
| ThinkingContentBlock
|
|
44
|
+
| ToolUseContentBlock;
|
|
45
|
+
|
|
46
|
+
export type UserContentBlock =
|
|
47
|
+
| TextContentBlock
|
|
48
|
+
| ToolResultContentBlock
|
|
49
|
+
| ImageContentBlock;
|
|
50
|
+
|
|
51
|
+
// ---------- assistant / user message envelopes ----------
|
|
52
|
+
|
|
53
|
+
export type AssistantMessage = {
|
|
54
|
+
id: string;
|
|
55
|
+
role: "assistant";
|
|
56
|
+
model?: string;
|
|
57
|
+
content: AssistantContentBlock[];
|
|
58
|
+
stop_reason?: string | null;
|
|
59
|
+
stop_sequence?: string | null;
|
|
60
|
+
usage?: UsageBlock;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type UserMessage = {
|
|
64
|
+
role: "user";
|
|
65
|
+
content: string | UserContentBlock[];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type UsageBlock = {
|
|
69
|
+
input_tokens?: number;
|
|
70
|
+
output_tokens?: number;
|
|
71
|
+
cache_creation_input_tokens?: number;
|
|
72
|
+
cache_read_input_tokens?: number;
|
|
73
|
+
service_tier?: string;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// ---------- streaming events (carried on `stream_event` frames) ----------
|
|
77
|
+
|
|
78
|
+
export type StreamEvent =
|
|
79
|
+
| { type: "message_start"; message: AssistantMessage }
|
|
80
|
+
| {
|
|
81
|
+
type: "content_block_start";
|
|
82
|
+
index: number;
|
|
83
|
+
content_block: AssistantContentBlock;
|
|
84
|
+
}
|
|
85
|
+
| {
|
|
86
|
+
type: "content_block_delta";
|
|
87
|
+
index: number;
|
|
88
|
+
delta:
|
|
89
|
+
| { type: "text_delta"; text: string }
|
|
90
|
+
| { type: "thinking_delta"; thinking: string }
|
|
91
|
+
| { type: "input_json_delta"; partial_json: string }
|
|
92
|
+
| { type: "signature_delta"; signature: string };
|
|
93
|
+
}
|
|
94
|
+
| { type: "content_block_stop"; index: number }
|
|
95
|
+
| { type: "message_delta"; delta: { stop_reason?: string }; usage?: UsageBlock }
|
|
96
|
+
| { type: "message_stop" };
|
|
97
|
+
|
|
98
|
+
// ---------- inbound: system frames ----------
|
|
99
|
+
|
|
100
|
+
export type SessionStateValue = "idle" | "running" | "requires_action";
|
|
101
|
+
|
|
102
|
+
export type SystemInit = {
|
|
103
|
+
type: "system";
|
|
104
|
+
subtype: "init";
|
|
105
|
+
agents?: string[];
|
|
106
|
+
apiKeySource?: string;
|
|
107
|
+
betas?: string[];
|
|
108
|
+
claude_code_version?: string;
|
|
109
|
+
cwd?: string;
|
|
110
|
+
tools?: string[];
|
|
111
|
+
mcp_servers?: unknown[];
|
|
112
|
+
model?: string;
|
|
113
|
+
permissionMode?: string;
|
|
114
|
+
permission_mode?: string;
|
|
115
|
+
slash_commands?: string[];
|
|
116
|
+
output_style?: string;
|
|
117
|
+
skills?: string[];
|
|
118
|
+
plugins?: unknown[];
|
|
119
|
+
plugin_errors?: unknown;
|
|
120
|
+
fast_mode_state?: unknown;
|
|
121
|
+
analytics_disabled?: boolean;
|
|
122
|
+
memory_paths?: string[];
|
|
123
|
+
uuid?: string;
|
|
124
|
+
session_id?: string;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export type SystemStatus = {
|
|
128
|
+
type: "system";
|
|
129
|
+
subtype: "status";
|
|
130
|
+
status?: string | null;
|
|
131
|
+
permissionMode?: string;
|
|
132
|
+
compact_result?: unknown;
|
|
133
|
+
compact_error?: unknown;
|
|
134
|
+
uuid?: string;
|
|
135
|
+
session_id?: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type SystemSessionStateChanged = {
|
|
139
|
+
type: "system";
|
|
140
|
+
subtype: "session_state_changed";
|
|
141
|
+
state: SessionStateValue;
|
|
142
|
+
uuid?: string;
|
|
143
|
+
session_id?: string;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export type SystemTaskStarted = {
|
|
147
|
+
type: "system";
|
|
148
|
+
subtype: "task_started";
|
|
149
|
+
task_id: string;
|
|
150
|
+
tool_use_id?: string;
|
|
151
|
+
description: string;
|
|
152
|
+
task_type?: string;
|
|
153
|
+
workflow_name?: string;
|
|
154
|
+
prompt?: string;
|
|
155
|
+
skip_transcript?: boolean;
|
|
156
|
+
uuid?: string;
|
|
157
|
+
session_id?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export type SystemTaskProgress = {
|
|
161
|
+
type: "system";
|
|
162
|
+
subtype: "task_progress";
|
|
163
|
+
task_id: string;
|
|
164
|
+
tool_use_id?: string;
|
|
165
|
+
description?: string;
|
|
166
|
+
usage?: TaskUsageBlock;
|
|
167
|
+
last_tool_name?: string;
|
|
168
|
+
summary?: string;
|
|
169
|
+
uuid?: string;
|
|
170
|
+
session_id?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type SystemTaskNotification = {
|
|
174
|
+
type: "system";
|
|
175
|
+
subtype: "task_notification";
|
|
176
|
+
task_id: string;
|
|
177
|
+
tool_use_id?: string;
|
|
178
|
+
status?: "completed" | "failed" | "stopped" | string;
|
|
179
|
+
output_file?: string;
|
|
180
|
+
summary?: string;
|
|
181
|
+
usage?: TaskUsageBlock;
|
|
182
|
+
skip_transcript?: boolean;
|
|
183
|
+
uuid?: string;
|
|
184
|
+
session_id?: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// task_updated.patch is a wire-safe subset of TaskState fields that changed.
|
|
188
|
+
// Mergeable into the local task map. Excludes abortController/messages/result.
|
|
189
|
+
export type TaskUpdatedPatch = {
|
|
190
|
+
status?: "running" | "completed" | "failed" | "killed" | "stopped" | string;
|
|
191
|
+
description?: string;
|
|
192
|
+
summary?: string;
|
|
193
|
+
last_tool_name?: string;
|
|
194
|
+
output_file?: string;
|
|
195
|
+
usage?: TaskUsageBlock;
|
|
196
|
+
tool_use_id?: string;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export type SystemTaskUpdated = {
|
|
200
|
+
type: "system";
|
|
201
|
+
subtype: "task_updated";
|
|
202
|
+
task_id: string;
|
|
203
|
+
patch: TaskUpdatedPatch;
|
|
204
|
+
uuid?: string;
|
|
205
|
+
session_id?: string;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export type TaskUsageBlock = {
|
|
209
|
+
total_tokens?: number;
|
|
210
|
+
tool_uses?: number;
|
|
211
|
+
duration_ms?: number;
|
|
212
|
+
input_tokens?: number;
|
|
213
|
+
output_tokens?: number;
|
|
214
|
+
cache_creation_input_tokens?: number;
|
|
215
|
+
cache_read_input_tokens?: number;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export type HookSubtype = "hook_started" | "hook_progress" | "hook_response";
|
|
219
|
+
|
|
220
|
+
export type SystemHook = {
|
|
221
|
+
type: "system";
|
|
222
|
+
subtype: HookSubtype;
|
|
223
|
+
hook_event_name?: string;
|
|
224
|
+
hookEventName?: string;
|
|
225
|
+
hook_id?: string;
|
|
226
|
+
status?: string;
|
|
227
|
+
output?: unknown;
|
|
228
|
+
error?: string;
|
|
229
|
+
uuid?: string;
|
|
230
|
+
session_id?: string;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export type SystemLocalCommandOutput = {
|
|
234
|
+
type: "system";
|
|
235
|
+
subtype: "local_command_output";
|
|
236
|
+
content?: string;
|
|
237
|
+
uuid?: string;
|
|
238
|
+
session_id?: string;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export type SystemCompactBoundary = {
|
|
242
|
+
type: "system";
|
|
243
|
+
subtype: "compact_boundary";
|
|
244
|
+
compact_metadata?: unknown;
|
|
245
|
+
uuid?: string;
|
|
246
|
+
session_id?: string;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
export type SystemFrame =
|
|
250
|
+
| SystemInit
|
|
251
|
+
| SystemStatus
|
|
252
|
+
| SystemSessionStateChanged
|
|
253
|
+
| SystemTaskStarted
|
|
254
|
+
| SystemTaskProgress
|
|
255
|
+
| SystemTaskNotification
|
|
256
|
+
| SystemTaskUpdated
|
|
257
|
+
| SystemHook
|
|
258
|
+
| SystemLocalCommandOutput
|
|
259
|
+
| SystemCompactBoundary;
|
|
260
|
+
|
|
261
|
+
// Catch-all for system subtypes the binary may emit but the lib doesn't
|
|
262
|
+
// model. Sits OUTSIDE SystemFrame so narrowing on a known subtype yields
|
|
263
|
+
// a single specific variant; consumers that need the broad case (e.g.
|
|
264
|
+
// renderers showing every system frame) accept SystemFrame | UnknownSystemFrame.
|
|
265
|
+
export type UnknownSystemFrame = {
|
|
266
|
+
type: "system";
|
|
267
|
+
subtype: string;
|
|
268
|
+
uuid?: string;
|
|
269
|
+
session_id?: string;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// ---------- inbound: non-system frames ----------
|
|
273
|
+
|
|
274
|
+
export type AssistantFrame = {
|
|
275
|
+
type: "assistant";
|
|
276
|
+
message: AssistantMessage;
|
|
277
|
+
parent_tool_use_id?: string | null;
|
|
278
|
+
error?: unknown;
|
|
279
|
+
uuid?: string;
|
|
280
|
+
session_id?: string;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export type UserFrame = {
|
|
284
|
+
type: "user";
|
|
285
|
+
message: UserMessage;
|
|
286
|
+
parent_tool_use_id?: string | null;
|
|
287
|
+
isSynthetic?: boolean;
|
|
288
|
+
isReplay?: boolean;
|
|
289
|
+
tool_use_result?: unknown;
|
|
290
|
+
priority?: string;
|
|
291
|
+
origin?: string;
|
|
292
|
+
client_platform?: string;
|
|
293
|
+
shouldQuery?: boolean;
|
|
294
|
+
timestamp?: number;
|
|
295
|
+
uuid?: string;
|
|
296
|
+
session_id?: string;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
export type ResultFrame = {
|
|
300
|
+
type: "result";
|
|
301
|
+
subtype?: string;
|
|
302
|
+
duration_ms?: number;
|
|
303
|
+
duration_api_ms?: number;
|
|
304
|
+
is_error?: boolean;
|
|
305
|
+
num_turns?: number;
|
|
306
|
+
stop_reason?: string | null;
|
|
307
|
+
total_cost_usd?: number;
|
|
308
|
+
usage?: UsageBlock;
|
|
309
|
+
modelUsage?: Record<string, UsageBlock>;
|
|
310
|
+
permission_denials?: unknown[];
|
|
311
|
+
errors?: unknown[];
|
|
312
|
+
terminal_reason?: string;
|
|
313
|
+
fast_mode_state?: unknown;
|
|
314
|
+
origin?: string;
|
|
315
|
+
uuid?: string;
|
|
316
|
+
session_id?: string;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export type StreamEventFrame = {
|
|
320
|
+
type: "stream_event";
|
|
321
|
+
event: StreamEvent;
|
|
322
|
+
parent_tool_use_id?: string | null;
|
|
323
|
+
uuid?: string;
|
|
324
|
+
session_id?: string;
|
|
325
|
+
ttft_ms?: number;
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
export type RateLimitEventFrame = {
|
|
329
|
+
type: "rate_limit_event";
|
|
330
|
+
resetsAt?: number;
|
|
331
|
+
resetsIn?: number;
|
|
332
|
+
retryAfter?: number;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// ---------- inbound: control_request (binary asks us) ----------
|
|
336
|
+
|
|
337
|
+
export type CanUseToolControlRequest = {
|
|
338
|
+
subtype: "can_use_tool";
|
|
339
|
+
tool_name?: string;
|
|
340
|
+
input?: unknown;
|
|
341
|
+
permission_suggestions?: unknown[];
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
// Other inbound control_request subtypes the lib may receive but doesn't
|
|
345
|
+
// drive UI off of. We accept them with subtype + an opaque payload.
|
|
346
|
+
export type UnknownControlRequest = {
|
|
347
|
+
subtype: string;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export type ControlRequestPayload = CanUseToolControlRequest | UnknownControlRequest;
|
|
351
|
+
|
|
352
|
+
export type ControlRequestFrame = {
|
|
353
|
+
type: "control_request";
|
|
354
|
+
request_id: string;
|
|
355
|
+
request: ControlRequestPayload;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// ---------- inbound: control_response ----------
|
|
359
|
+
|
|
360
|
+
export type ControlResponseSuccess = {
|
|
361
|
+
subtype: "success";
|
|
362
|
+
request_id: string;
|
|
363
|
+
response?: unknown;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export type ControlResponseError = {
|
|
367
|
+
subtype: "error";
|
|
368
|
+
request_id: string;
|
|
369
|
+
error?: string;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export type ControlResponseFrame = {
|
|
373
|
+
type: "control_response";
|
|
374
|
+
response: ControlResponseSuccess | ControlResponseError;
|
|
375
|
+
// Some bridge variants put request_id at the outer level. Keep optional
|
|
376
|
+
// for forward compatibility; consumers should prefer response.request_id.
|
|
377
|
+
request_id?: string;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// ---------- inbound: _local frames (bridge-injected) ----------
|
|
381
|
+
|
|
382
|
+
export type LocalRespawnResultFrame = {
|
|
383
|
+
_local: "respawnResult";
|
|
384
|
+
requestId: string;
|
|
385
|
+
ok: boolean;
|
|
386
|
+
error?: string;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
// ---------- inbound union ----------
|
|
390
|
+
|
|
391
|
+
export type InboundFrame =
|
|
392
|
+
| AssistantFrame
|
|
393
|
+
| UserFrame
|
|
394
|
+
| ResultFrame
|
|
395
|
+
| StreamEventFrame
|
|
396
|
+
| SystemFrame
|
|
397
|
+
| UnknownSystemFrame
|
|
398
|
+
| ControlRequestFrame
|
|
399
|
+
| ControlResponseFrame
|
|
400
|
+
| RateLimitEventFrame
|
|
401
|
+
| LocalRespawnResultFrame;
|
|
402
|
+
|
|
403
|
+
// ---------- outbound: frames the client sends to the bridge ----------
|
|
404
|
+
|
|
405
|
+
export type UserMessageFrame = {
|
|
406
|
+
type: "user";
|
|
407
|
+
message: { role: "user"; content: string };
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export type BashCommandFrame = {
|
|
411
|
+
type: "bash_command";
|
|
412
|
+
command: string;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Outbound control_request subtypes we use. Nested envelope.
|
|
416
|
+
export type OutboundControlRequestSubtype =
|
|
417
|
+
| { subtype: "interrupt" }
|
|
418
|
+
| { subtype: "set_permission_mode"; mode: string }
|
|
419
|
+
| { subtype: "set_model"; model: string }
|
|
420
|
+
| { subtype: "set_max_thinking_tokens"; max_tokens: number }
|
|
421
|
+
| { subtype: "end_session" }
|
|
422
|
+
| { subtype: "get_settings" }
|
|
423
|
+
| { subtype: "file_suggestions"; query: string }
|
|
424
|
+
| { subtype: "stop_task"; task_id: string };
|
|
425
|
+
|
|
426
|
+
export type OutboundControlRequestFrame = {
|
|
427
|
+
type: "control_request";
|
|
428
|
+
request_id: string;
|
|
429
|
+
request: OutboundControlRequestSubtype;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// Reply to a can_use_tool control_request. Nested envelope.
|
|
433
|
+
export type CanUseToolResponseFrame = {
|
|
434
|
+
type: "control_response";
|
|
435
|
+
response: {
|
|
436
|
+
subtype: "success";
|
|
437
|
+
request_id: string;
|
|
438
|
+
response:
|
|
439
|
+
| { behavior: "allow"; updatedInput: unknown }
|
|
440
|
+
| { behavior: "deny"; message: string };
|
|
441
|
+
};
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
export type LocalRespawnFrame = {
|
|
445
|
+
_local: "respawn";
|
|
446
|
+
args: string[];
|
|
447
|
+
requestId: string;
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export type OutboundFrame =
|
|
451
|
+
| UserMessageFrame
|
|
452
|
+
| BashCommandFrame
|
|
453
|
+
| OutboundControlRequestFrame
|
|
454
|
+
| CanUseToolResponseFrame
|
|
455
|
+
| LocalRespawnFrame;
|
|
456
|
+
|
|
457
|
+
// ---------- helpers ----------
|
|
458
|
+
|
|
459
|
+
export function makeRequestId(): string {
|
|
460
|
+
return crypto.randomUUID();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Translate a SessionMode + extras into the CLI flag list for the spawn
|
|
464
|
+
// (consumed by the bridge via _local:respawn). Effort and model are spawn-
|
|
465
|
+
// time only at present (no set_effort control_request exists; --model picks
|
|
466
|
+
// the launch model and set_model can change it later).
|
|
467
|
+
export function buildSpawnArgs(opts: {
|
|
468
|
+
mode: SessionMode;
|
|
469
|
+
effort?: string;
|
|
470
|
+
model?: string;
|
|
471
|
+
permissionMode?: string;
|
|
472
|
+
includePartialMessages?: boolean;
|
|
473
|
+
includeHookEvents?: boolean;
|
|
474
|
+
permissionPromptTool?: string | null;
|
|
475
|
+
}): string[] {
|
|
476
|
+
const args: string[] = [];
|
|
477
|
+
if (opts.mode.kind === "continue") args.push("--continue");
|
|
478
|
+
else if (opts.mode.kind === "resume") args.push("--resume", opts.mode.sessionId);
|
|
479
|
+
if (opts.permissionMode) args.push("--permission-mode", opts.permissionMode);
|
|
480
|
+
if (opts.permissionPromptTool) args.push("--permission-prompt-tool", opts.permissionPromptTool);
|
|
481
|
+
if (opts.includePartialMessages) args.push("--include-partial-messages");
|
|
482
|
+
if (opts.includeHookEvents) args.push("--include-hook-events");
|
|
483
|
+
if (opts.effort) args.push("--effort", opts.effort);
|
|
484
|
+
if (opts.model) args.push("--model", opts.model);
|
|
485
|
+
return args;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Discriminator helpers — narrow once, reuse the type guard.
|
|
489
|
+
|
|
490
|
+
export function isSystemFrame(f: InboundFrame): f is SystemFrame | UnknownSystemFrame {
|
|
491
|
+
return (f as { type?: unknown }).type === "system";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function isUserFrame(f: InboundFrame): f is UserFrame {
|
|
495
|
+
return (f as { type?: unknown }).type === "user";
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function isAssistantFrame(f: InboundFrame): f is AssistantFrame {
|
|
499
|
+
return (f as { type?: unknown }).type === "assistant";
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export function isStreamEventFrame(f: InboundFrame): f is StreamEventFrame {
|
|
503
|
+
return (f as { type?: unknown }).type === "stream_event";
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function isResultFrame(f: InboundFrame): f is ResultFrame {
|
|
507
|
+
return (f as { type?: unknown }).type === "result";
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function isControlRequest(f: InboundFrame): f is ControlRequestFrame {
|
|
511
|
+
return (f as { type?: unknown }).type === "control_request";
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export function isControlResponse(f: InboundFrame): f is ControlResponseFrame {
|
|
515
|
+
return (f as { type?: unknown }).type === "control_response";
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function isLocalRespawnResult(f: InboundFrame): f is LocalRespawnResultFrame {
|
|
519
|
+
return (f as { _local?: unknown })._local === "respawnResult";
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Backwards-compat: existing call sites expect this name.
|
|
523
|
+
export type CanUseToolRequest = ControlRequestFrame & {
|
|
524
|
+
request: CanUseToolControlRequest;
|
|
525
|
+
};
|