@ellipsis-dev/sdk 0.14.0 → 0.15.1

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.
@@ -116,32 +116,6 @@ function lifecycleText(recordType, payload) {
116
116
  }
117
117
 
118
118
  // src/store/transcript.ts
119
- var LineBuffer = class {
120
- buf = "";
121
- push(chunk) {
122
- this.buf += chunk;
123
- const parts = this.buf.split("\n");
124
- this.buf = parts.pop() ?? "";
125
- return parts;
126
- }
127
- flush() {
128
- const rest = this.buf.trim();
129
- this.buf = "";
130
- return rest ? [rest] : [];
131
- }
132
- };
133
- function parseEventLine(line) {
134
- const trimmed = line.trim();
135
- if (!trimmed) return null;
136
- try {
137
- const value = JSON.parse(trimmed);
138
- if (value && typeof value === "object" && !Array.isArray(value)) {
139
- return value;
140
- }
141
- } catch {
142
- }
143
- return null;
144
- }
145
119
  function oneLine(text, max) {
146
120
  const collapsed = text.replace(/\s+/g, " ").trim();
147
121
  return collapsed.length <= max ? collapsed : `${collapsed.slice(0, max - 3)}...`;
@@ -176,35 +150,31 @@ function formatDuration(seconds) {
176
150
  }
177
151
  function blocksOf(content) {
178
152
  if (typeof content === "string") return [{ type: "text", text: content }];
179
- if (Array.isArray(content)) return content;
180
- return [];
153
+ return content;
181
154
  }
182
- function toolResultText(block) {
183
- const c = block.content;
184
- if (typeof c === "string") return c.trim();
185
- if (Array.isArray(c)) {
155
+ function toolResultText(content) {
156
+ if (typeof content === "string") return content.trim();
157
+ if (Array.isArray(content)) {
186
158
  const parts = [];
187
- for (const inner of c) {
159
+ for (const inner of content) {
188
160
  if (typeof inner.text === "string") parts.push(inner.text);
189
161
  else parts.push(JSON.stringify(inner));
190
162
  }
191
163
  return parts.join("\n").trim();
192
164
  }
193
- if (c === void 0 || c === null) return "";
194
- return JSON.stringify(c);
165
+ if (content === void 0 || content === null) return "";
166
+ return JSON.stringify(content);
195
167
  }
196
168
  function eventToItems(event, keyBase, options = {}) {
197
169
  const items = [];
198
170
  const push = (item) => {
199
171
  items.push({ ...item, key: `${keyBase}:${items.length}` });
200
172
  };
201
- const type = event.type;
202
- if (type === "result") {
173
+ if (event.kind === "result") {
203
174
  const bits = [];
204
- if (typeof event.duration_ms === "number")
205
- bits.push(formatDuration(event.duration_ms / 1e3));
206
- if (typeof event.total_cost_usd === "number")
207
- bits.push(`$${event.total_cost_usd.toFixed(2)}`);
175
+ bits.push(formatDuration(event.duration_ms / 1e3));
176
+ if (typeof event.cost_usd === "number")
177
+ bits.push(`$${event.cost_usd.toFixed(2)}`);
208
178
  const label = event.is_error ? "turn ended with an error" : "turn complete";
209
179
  push({
210
180
  kind: "summary",
@@ -214,11 +184,12 @@ function eventToItems(event, keyBase, options = {}) {
214
184
  });
215
185
  return items;
216
186
  }
217
- if (type === "system") {
187
+ if (event.kind === "system") {
218
188
  if (!options.systemInitLine || event.subtype !== "init") return items;
189
+ const data = event.data ?? {};
219
190
  const bits = [];
220
- if (typeof event.model === "string") bits.push(event.model);
221
- if (typeof event.cwd === "string") bits.push(event.cwd);
191
+ if (typeof data.model === "string") bits.push(data.model);
192
+ if (typeof data.cwd === "string") bits.push(data.cwd);
222
193
  push({
223
194
  kind: "system",
224
195
  text: bits.length ? `session started \xB7 ${bits.join(" \xB7 ")}` : "session started",
@@ -226,19 +197,20 @@ function eventToItems(event, keyBase, options = {}) {
226
197
  });
227
198
  return items;
228
199
  }
229
- const blocks = blocksOf(event.message?.content);
230
- if (type === "user") {
200
+ if (event.kind === "rate_limit") return items;
201
+ const blocks = blocksOf(event.content);
202
+ if (event.kind === "user") {
231
203
  for (const block of blocks) {
232
204
  if (block.type === "tool_result") {
233
- const body = toolResultText(block);
205
+ const body = toolResultText(block.content);
234
206
  push({
235
207
  kind: "tool_result",
236
208
  gutter: "\u23BF",
237
209
  text: body || "(no output)",
238
210
  spaceBefore: false,
239
- isError: block.is_error
211
+ isError: block.is_error ?? void 0
240
212
  });
241
- } else if (typeof block.text === "string" && block.text.trim()) {
213
+ } else if (block.type === "text" && block.text.trim()) {
242
214
  push({
243
215
  kind: "user",
244
216
  gutter: "\u203A",
@@ -250,37 +222,153 @@ function eventToItems(event, keyBase, options = {}) {
250
222
  return items;
251
223
  }
252
224
  for (const block of blocks) {
253
- if (block.type === "thinking" && typeof block.thinking === "string" && block.thinking.trim()) {
225
+ if (block.type === "thinking" && block.thinking.trim()) {
254
226
  push({
255
227
  kind: "thinking",
256
228
  gutter: "\u273B",
257
229
  text: block.thinking.trim(),
258
230
  spaceBefore: true
259
231
  });
260
- } else if (block.type === "tool_use") {
261
- const name = block.name ?? "tool";
262
- const summary = summarizeToolInput(name, block.input);
232
+ } else if (block.type === "tool_use" || block.type === "server_tool_use") {
233
+ const summary = summarizeToolInput(block.name, block.input);
263
234
  push({
264
235
  kind: "tool",
265
236
  gutter: "\u25CF",
266
- text: name,
237
+ text: block.name,
267
238
  detail: summary ? `(${summary})` : void 0,
268
239
  spaceBefore: true,
269
- tool: { name, input: block.input }
240
+ tool: { name: block.name, input: block.input }
270
241
  });
271
- } else if (typeof block.text === "string" && block.text.trim()) {
242
+ } else if (block.type === "text" && block.text.trim()) {
272
243
  push({ kind: "assistant", text: block.text.trim(), spaceBefore: true });
273
244
  }
274
245
  }
275
246
  return items;
276
247
  }
248
+ function codexEventToItems(event, keyBase) {
249
+ const items = [];
250
+ const push = (item2) => {
251
+ items.push({ ...item2, key: `${keyBase}:${items.length}` });
252
+ };
253
+ if (event.type === "turn.failed") {
254
+ push({
255
+ kind: "summary",
256
+ text: "turn ended with an error",
257
+ spaceBefore: true,
258
+ isError: true
259
+ });
260
+ return items;
261
+ }
262
+ if (event.type === "error") {
263
+ push({
264
+ kind: "error",
265
+ text: event.message?.trim() || "error",
266
+ spaceBefore: true,
267
+ isError: true
268
+ });
269
+ return items;
270
+ }
271
+ if (event.type !== "item.completed") return items;
272
+ const item = event.item;
273
+ switch (item.type) {
274
+ case "agent_message": {
275
+ const text = item.text.trim();
276
+ if (text) push({ kind: "assistant", text, spaceBefore: true });
277
+ break;
278
+ }
279
+ case "reasoning": {
280
+ const r = item;
281
+ const text = (r.text ?? r.summary ?? "").trim();
282
+ if (text)
283
+ push({ kind: "thinking", gutter: "\u273B", text, spaceBefore: true });
284
+ break;
285
+ }
286
+ case "command_execution": {
287
+ const c = item;
288
+ const summary = oneLine(c.command, 100);
289
+ push({
290
+ kind: "tool",
291
+ gutter: "\u25CF",
292
+ text: "Bash",
293
+ detail: summary ? `(${summary})` : void 0,
294
+ spaceBefore: true,
295
+ tool: { name: "Bash", input: { command: c.command } }
296
+ });
297
+ const output = (c.aggregated_output ?? "").trim();
298
+ push({
299
+ kind: "tool_result",
300
+ gutter: "\u23BF",
301
+ text: output || "(no output)",
302
+ spaceBefore: false,
303
+ isError: typeof c.exit_code === "number" && c.exit_code !== 0
304
+ });
305
+ break;
306
+ }
307
+ case "file_change": {
308
+ const paths = codexChangedPaths(item);
309
+ push({
310
+ kind: "tool",
311
+ gutter: "\u25CF",
312
+ text: "Edit",
313
+ detail: paths.length ? `(${oneLine(paths.join(", "), 100)})` : void 0,
314
+ spaceBefore: true,
315
+ tool: { name: "Edit" }
316
+ });
317
+ break;
318
+ }
319
+ case "mcp_tool_call": {
320
+ const name = codexMcpToolName(item);
321
+ push({
322
+ kind: "tool",
323
+ gutter: "\u25CF",
324
+ text: name,
325
+ spaceBefore: true,
326
+ tool: { name }
327
+ });
328
+ break;
329
+ }
330
+ case "web_search": {
331
+ const query = item.query;
332
+ push({
333
+ kind: "tool",
334
+ gutter: "\u25CF",
335
+ text: "WebSearch",
336
+ detail: query ? `(${oneLine(query, 100)})` : void 0,
337
+ spaceBefore: true,
338
+ tool: { name: "WebSearch" }
339
+ });
340
+ break;
341
+ }
342
+ case "error":
343
+ push({
344
+ kind: "error",
345
+ text: item.message?.trim() || "error",
346
+ spaceBefore: true,
347
+ isError: true
348
+ });
349
+ break;
350
+ }
351
+ return items;
352
+ }
353
+ function codexChangedPaths(item) {
354
+ return (item.changes ?? []).map((c) => typeof c.path === "string" ? c.path : null).filter((p) => p !== null);
355
+ }
356
+ function codexMcpToolName(item) {
357
+ return item.server && item.tool ? `mcp__${item.server}__${item.tool}` : item.tool ?? "mcp_tool_call";
358
+ }
277
359
  function recordToItems(record, keyBase, options = {}) {
278
- if (record.source === "lifecycle") {
279
- const text = lifecycleText(record.record_type, record.payload);
280
- return text ? [{ key: keyBase, kind: "notice", text, spaceBefore: true }] : [];
360
+ switch (record.record_format) {
361
+ case "claude_sdk@1":
362
+ return eventToItems(record.payload, keyBase, options);
363
+ case "codex_jsonl@1":
364
+ return codexEventToItems(record.payload, keyBase);
365
+ case "ellipsis_lifecycle@1": {
366
+ const text = lifecycleText(record.record_type, record.payload);
367
+ return text ? [{ key: keyBase, kind: "notice", text, spaceBefore: true }] : [];
368
+ }
369
+ default:
370
+ return [];
281
371
  }
282
- if (record.source !== "claude_code") return [];
283
- return eventToItems(record.payload, keyBase, options);
284
372
  }
285
373
  function isConnectVisibleRecord(record) {
286
374
  return record.source !== "lifecycle" || record.record_type === "sandbox_ready";
@@ -341,19 +429,18 @@ function clampLines(text, maxLines) {
341
429
  };
342
430
  }
343
431
  function resultCostUsd(event) {
344
- if (event.type !== "result") return null;
345
- return typeof event.total_cost_usd === "number" ? event.total_cost_usd : null;
432
+ if (event.kind !== "result") return null;
433
+ return typeof event.cost_usd === "number" ? event.cost_usd : null;
346
434
  }
347
435
  function foldCosts(events) {
348
- let prev = null;
349
436
  let total = null;
437
+ let lastStep = null;
350
438
  for (const event of events) {
351
439
  const cost = resultCostUsd(event);
352
440
  if (cost == null) continue;
353
- prev = total;
354
- total = cost;
441
+ total = (total ?? 0) + cost;
442
+ lastStep = cost;
355
443
  }
356
- const lastStep = total != null && prev != null ? Math.max(0, total - prev) : total;
357
444
  return { total, lastStep };
358
445
  }
359
446
  function statusActivityText(status) {
@@ -370,36 +457,19 @@ function statusActivityText(status) {
370
457
  }
371
458
 
372
459
  // src/store/chatTurns.ts
373
- var isInitEvent = (data) => data.type === "system" && data.subtype === "init";
374
- var TOOL_USE_BLOCK_TYPES = /* @__PURE__ */ new Set([
375
- "tool_use",
376
- "server_tool_use",
377
- "mcp_tool_use"
378
- ]);
460
+ var isInitEvent = (data) => data.kind === "system" && data.subtype === "init";
379
461
  var SANDBOX_RECORD_TYPES = /* @__PURE__ */ new Set([
380
462
  "sandbox_starting",
381
463
  "sandbox_phase",
382
464
  "sandbox_output",
383
465
  "sandbox_ready"
384
466
  ]);
385
- function toolResultText2(block) {
386
- const c = block.content;
387
- if (typeof c === "string") return c.trim();
388
- if (Array.isArray(c)) {
389
- return c.map(
390
- (inner) => typeof inner.text === "string" ? inner.text : JSON.stringify(inner)
391
- ).join("\n").trim();
392
- }
393
- if (c === void 0 || c === null) return "";
394
- return JSON.stringify(c);
395
- }
396
467
  function groupRecordsToChatTurns(records) {
397
468
  const turns = [];
398
469
  const toolNodeByUseId = /* @__PURE__ */ new Map();
399
470
  let seq = 0;
400
471
  const nextKey = (base) => `${base}:${seq++}`;
401
472
  let openIdx = -1;
402
- let prevCumulativeCostUsd = 0;
403
473
  let initCount = 0;
404
474
  let pendingResume = false;
405
475
  const openAgentTurn = (record) => {
@@ -421,7 +491,7 @@ function groupRecordsToChatTurns(records) {
421
491
  return turns[openIdx];
422
492
  };
423
493
  for (const record of records) {
424
- if (record.source === "lifecycle") {
494
+ if (record.record_format === "ellipsis_lifecycle@1") {
425
495
  const text = lifecycleText(record.record_type, record.payload);
426
496
  if (!text) continue;
427
497
  const isSandbox = SANDBOX_RECORD_TYPES.has(record.record_type);
@@ -483,43 +553,49 @@ function groupRecordsToChatTurns(records) {
483
553
  });
484
554
  continue;
485
555
  }
486
- if (record.source !== "claude_code") continue;
556
+ if (record.record_format === "codex_jsonl@1") {
557
+ codexEventIntoTurns(record, record.payload, {
558
+ openAgentTurn,
559
+ closeTurn: (completedAt) => {
560
+ if (openIdx >= 0) {
561
+ turns[openIdx].completedAt = completedAt;
562
+ openIdx = -1;
563
+ }
564
+ },
565
+ nextKey
566
+ });
567
+ continue;
568
+ }
487
569
  const data = record.payload;
488
- if (data.type === "system") {
570
+ if (data.kind === "system") {
489
571
  if (isInitEvent(data)) {
490
572
  initCount += 1;
491
573
  if (initCount > 1) pendingResume = true;
492
574
  }
493
575
  continue;
494
576
  }
495
- if (data.type === "result") {
577
+ if (data.kind === "result") {
496
578
  if (openIdx >= 0) {
497
579
  turns[openIdx].completedAt = record.created_at;
498
580
  turns[openIdx].durationMs = typeof data.duration_ms === "number" ? data.duration_ms : null;
499
- if (typeof data.total_cost_usd === "number") {
500
- turns[openIdx].costUsd = Math.max(
501
- 0,
502
- data.total_cost_usd - prevCumulativeCostUsd
503
- );
504
- prevCumulativeCostUsd = data.total_cost_usd;
581
+ if (typeof data.cost_usd === "number") {
582
+ turns[openIdx].costUsd = data.cost_usd;
505
583
  }
506
584
  openIdx = -1;
507
585
  }
508
586
  continue;
509
587
  }
510
- const message = data.message;
511
- if (!message) continue;
512
- if (data.type === "user") {
513
- const content2 = message.content;
514
- if (typeof content2 === "string") {
515
- if (content2.trim()) {
588
+ if (data.kind === "user") {
589
+ const content = data.content;
590
+ if (typeof content === "string") {
591
+ if (content.trim()) {
516
592
  openIdx = -1;
517
593
  turns.push({
518
594
  resumed: false,
519
595
  key: nextKey(record.id),
520
596
  role: "user",
521
597
  nodes: [
522
- { key: nextKey(record.id), kind: "user", text: content2.trim() }
598
+ { key: nextKey(record.id), kind: "user", text: content.trim() }
523
599
  ],
524
600
  startedAt: record.created_at,
525
601
  completedAt: record.created_at,
@@ -530,12 +606,11 @@ function groupRecordsToChatTurns(records) {
530
606
  }
531
607
  continue;
532
608
  }
533
- if (!Array.isArray(content2)) continue;
534
- for (const block of content2) {
609
+ for (const block of content) {
535
610
  if (block.type !== "tool_result") continue;
536
- const body = toolResultText2(block) || "(no output)";
537
- const useId = block.tool_use_id ?? void 0;
538
- const node = useId != null ? toolNodeByUseId.get(useId) : void 0;
611
+ const body = toolResultText(block.content) || "(no output)";
612
+ const useId = block.tool_use_id;
613
+ const node = toolNodeByUseId.get(useId);
539
614
  if (node) {
540
615
  node.result = body;
541
616
  node.isError = !!block.is_error;
@@ -553,41 +628,38 @@ function groupRecordsToChatTurns(records) {
553
628
  completedAt: record.created_at
554
629
  };
555
630
  openAgentTurn(record).nodes.push(orphan);
556
- if (useId != null) toolNodeByUseId.set(useId, orphan);
631
+ toolNodeByUseId.set(useId, orphan);
557
632
  }
558
633
  }
559
634
  continue;
560
635
  }
561
- const content = message.content;
562
- if (!Array.isArray(content)) continue;
636
+ if (data.kind !== "assistant") continue;
563
637
  const turn = openAgentTurn(record);
564
- const usage = message.usage;
565
- if (usage && typeof usage.output_tokens === "number") {
566
- turn.tokens = (turn.tokens ?? 0) + usage.output_tokens;
638
+ if (data.usage && typeof data.usage.output_tokens === "number") {
639
+ turn.tokens = (turn.tokens ?? 0) + data.usage.output_tokens;
567
640
  }
568
- for (const block of content) {
569
- if ((block.type === "thinking" || block.type === "redacted_thinking") && typeof block.thinking === "string" && block.thinking.trim()) {
641
+ for (const block of data.content) {
642
+ if (block.type === "thinking" && block.thinking.trim()) {
570
643
  turn.nodes.push({
571
644
  key: nextKey(record.id),
572
645
  kind: "thinking",
573
646
  text: block.thinking.trim()
574
647
  });
575
- } else if (block.type != null && TOOL_USE_BLOCK_TYPES.has(block.type)) {
576
- const name = block.name ?? "tool";
648
+ } else if (block.type === "tool_use" || block.type === "server_tool_use") {
577
649
  const node = {
578
650
  key: nextKey(record.id),
579
651
  kind: "tool",
580
- name,
581
- input: block.input ?? null,
582
- summary: summarizeToolInput(name, block.input ?? void 0),
652
+ name: block.name,
653
+ input: block.input,
654
+ summary: summarizeToolInput(block.name, block.input),
583
655
  result: null,
584
656
  isError: false,
585
657
  startedAt: record.created_at,
586
658
  completedAt: null
587
659
  };
588
660
  turn.nodes.push(node);
589
- if (block.id) toolNodeByUseId.set(block.id, node);
590
- } else if (block.type === "text" && typeof block.text === "string" && block.text.trim()) {
661
+ toolNodeByUseId.set(block.id, node);
662
+ } else if (block.type === "text" && block.text.trim()) {
591
663
  turn.nodes.push({
592
664
  key: nextKey(record.id),
593
665
  kind: "assistant",
@@ -598,6 +670,105 @@ function groupRecordsToChatTurns(records) {
598
670
  }
599
671
  return turns;
600
672
  }
673
+ function codexEventIntoTurns(record, event, ops) {
674
+ if (event.type === "turn.completed" || event.type === "turn.failed") {
675
+ ops.closeTurn(record.created_at);
676
+ return;
677
+ }
678
+ if (event.type === "error") {
679
+ const turn2 = ops.openAgentTurn(record);
680
+ turn2.nodes.push({
681
+ key: ops.nextKey(record.id),
682
+ kind: "assistant",
683
+ text: event.message?.trim() || "error"
684
+ });
685
+ return;
686
+ }
687
+ if (event.type !== "item.completed") return;
688
+ const item = event.item;
689
+ const turn = () => ops.openAgentTurn(record);
690
+ switch (item.type) {
691
+ case "agent_message": {
692
+ const text = item.text.trim();
693
+ if (text)
694
+ turn().nodes.push({
695
+ key: ops.nextKey(record.id),
696
+ kind: "assistant",
697
+ text
698
+ });
699
+ break;
700
+ }
701
+ case "reasoning": {
702
+ const r = item;
703
+ const text = (r.text ?? r.summary ?? "").trim();
704
+ if (text)
705
+ turn().nodes.push({
706
+ key: ops.nextKey(record.id),
707
+ kind: "thinking",
708
+ text
709
+ });
710
+ break;
711
+ }
712
+ case "command_execution": {
713
+ const c = item;
714
+ turn().nodes.push({
715
+ key: ops.nextKey(record.id),
716
+ kind: "tool",
717
+ name: "Bash",
718
+ input: { command: c.command },
719
+ summary: oneLine(c.command, 100),
720
+ result: (c.aggregated_output ?? "").trim() || "(no output)",
721
+ isError: typeof c.exit_code === "number" && c.exit_code !== 0,
722
+ startedAt: record.created_at,
723
+ completedAt: record.created_at
724
+ });
725
+ break;
726
+ }
727
+ case "file_change": {
728
+ const paths = codexChangedPaths(item);
729
+ turn().nodes.push({
730
+ key: ops.nextKey(record.id),
731
+ kind: "tool",
732
+ name: "Edit",
733
+ input: null,
734
+ summary: paths.length ? oneLine(paths.join(", "), 100) : "",
735
+ result: null,
736
+ isError: false,
737
+ startedAt: record.created_at,
738
+ completedAt: record.created_at
739
+ });
740
+ break;
741
+ }
742
+ case "mcp_tool_call":
743
+ turn().nodes.push({
744
+ key: ops.nextKey(record.id),
745
+ kind: "tool",
746
+ name: codexMcpToolName(item),
747
+ input: null,
748
+ summary: "",
749
+ result: null,
750
+ isError: false,
751
+ startedAt: record.created_at,
752
+ completedAt: record.created_at
753
+ });
754
+ break;
755
+ case "web_search": {
756
+ const query = item.query;
757
+ turn().nodes.push({
758
+ key: ops.nextKey(record.id),
759
+ kind: "tool",
760
+ name: "WebSearch",
761
+ input: query ? { query } : null,
762
+ summary: query ? oneLine(query, 100) : "",
763
+ result: null,
764
+ isError: false,
765
+ startedAt: record.created_at,
766
+ completedAt: record.created_at
767
+ });
768
+ break;
769
+ }
770
+ }
771
+ }
601
772
 
602
773
  // src/store/index.ts
603
774
  var TERMINAL_SESSION_STATUSES = /* @__PURE__ */ new Set([
@@ -791,10 +962,12 @@ var SessionTranscriptStore = class {
791
962
  };
792
963
  };
793
964
  export {
794
- LineBuffer,
795
965
  SessionTranscriptStore,
796
966
  cacheTierLabel,
797
967
  clampLines,
968
+ codexChangedPaths,
969
+ codexEventToItems,
970
+ codexMcpToolName,
798
971
  collapseToolRuns,
799
972
  emptySessionTranscriptSnapshot,
800
973
  eventToItems,
@@ -805,7 +978,6 @@ export {
805
978
  isConversationOver,
806
979
  lifecycleText,
807
980
  oneLine,
808
- parseEventLine,
809
981
  pendingToolCalls,
810
982
  recordToItems,
811
983
  resultCostUsd,
@@ -814,5 +986,6 @@ export {
814
986
  sandboxOutputStep,
815
987
  sandboxPhaseLabel,
816
988
  statusActivityText,
817
- summarizeToolInput
989
+ summarizeToolInput,
990
+ toolResultText
818
991
  };
@@ -1,4 +1,4 @@
1
- import { X as StreamFrame, r as Session } from '../types-egqnpL5C.js';
1
+ import { a7 as StreamFrame, K as Session } from '../types-D9g-eZui.js';
2
2
 
3
3
  declare const SESSION_STREAM_PROTOCOL_VERSION = 3;
4
4
  declare const WS_CLOSE_NORMAL = 1000;