@psnext/slingcli 2.4.20260522-1 → 2.4.20260522-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/bin/sling.js CHANGED
@@ -124,7 +124,10 @@ function syncBundledSkills() {
124
124
  for (const entry of readdirSync(srcSkillsDir, { withFileTypes: true })) {
125
125
  if (!entry.isDirectory()) continue;
126
126
  const dest = path.join(destSkillsDir, entry.name);
127
+ try {
127
128
  cpSync(path.join(srcSkillsDir, entry.name), dest, { recursive: true, force: true });
129
+ } catch (err) {
130
+ }
128
131
  }
129
132
  }
130
133
 
@@ -135,6 +138,7 @@ async function checkAndInstallPackages() {
135
138
  if (!existsSync(configPath)) return;
136
139
 
137
140
  let installed = new Set();
141
+ let unsupported = new Set();
138
142
  const settingsPath = path.resolve(homedir(), ".sling/agent/settings.json");
139
143
  if (existsSync(settingsPath)) {
140
144
  try {
@@ -143,10 +147,30 @@ async function checkAndInstallPackages() {
143
147
  const source = typeof p === "object" && p !== null ? p.source : p;
144
148
  installed.add(source);
145
149
  }
150
+ for (const p of settings.unsupportedPackages || []) {
151
+ const source = typeof p === "object" && p !== null ? p.source : p;
152
+ unsupported.add(source);
153
+ }
146
154
  } catch {}
147
155
  }
148
156
 
149
157
  try {
158
+
159
+ //uninstall unsupported packages
160
+ for (const u of unsupported) {
161
+ console.log(`Removing unsupported package ${u}...`);
162
+ try {
163
+ await runCommand(process.execPath, [__filename, "remove", "-g", u]);
164
+ console.log(`✓ Removed ${u}`);
165
+ } catch (err) {
166
+ console.error(`✗ Failed to remove ${u}:`, err.message);
167
+ console.error(` You may need to manually remove it with: sling remove ${u}`);
168
+ }
169
+ }
170
+
171
+ //remove unsupported packages from installed set
172
+ for (const u of unsupported) installed.delete(u);
173
+
150
174
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
151
175
  const packages = (config.packages || []).filter((p) => {
152
176
  const source = typeof p === "object" && p !== null ? p.source : p;
@@ -3515,42 +3515,6 @@ export const MODELS = {
3515
3515
  },
3516
3516
  },
3517
3517
  "fireworks": {
3518
- "accounts/fireworks/models/deepseek-v3p1": {
3519
- id: "accounts/fireworks/models/deepseek-v3p1",
3520
- name: "DeepSeek V3.1",
3521
- api: "anthropic-messages",
3522
- provider: "fireworks",
3523
- baseUrl: "https://api.fireworks.ai/inference",
3524
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3525
- reasoning: true,
3526
- input: ["text"],
3527
- cost: {
3528
- input: 0.56,
3529
- output: 1.68,
3530
- cacheRead: 0,
3531
- cacheWrite: 0,
3532
- },
3533
- contextWindow: 163840,
3534
- maxTokens: 163840,
3535
- },
3536
- "accounts/fireworks/models/deepseek-v3p2": {
3537
- id: "accounts/fireworks/models/deepseek-v3p2",
3538
- name: "DeepSeek V3.2",
3539
- api: "anthropic-messages",
3540
- provider: "fireworks",
3541
- baseUrl: "https://api.fireworks.ai/inference",
3542
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3543
- reasoning: true,
3544
- input: ["text"],
3545
- cost: {
3546
- input: 0.56,
3547
- output: 1.68,
3548
- cacheRead: 0.28,
3549
- cacheWrite: 0,
3550
- },
3551
- contextWindow: 160000,
3552
- maxTokens: 160000,
3553
- },
3554
3518
  "accounts/fireworks/models/deepseek-v4-flash": {
3555
3519
  id: "accounts/fireworks/models/deepseek-v4-flash",
3556
3520
  name: "DeepSeek V4 Flash",
@@ -3581,84 +3545,12 @@ export const MODELS = {
3581
3545
  cost: {
3582
3546
  input: 1.74,
3583
3547
  output: 3.48,
3584
- cacheRead: 0.15,
3548
+ cacheRead: 0.145,
3585
3549
  cacheWrite: 0,
3586
3550
  },
3587
3551
  contextWindow: 1000000,
3588
3552
  maxTokens: 384000,
3589
3553
  },
3590
- "accounts/fireworks/models/glm-4p5": {
3591
- id: "accounts/fireworks/models/glm-4p5",
3592
- name: "GLM 4.5",
3593
- api: "anthropic-messages",
3594
- provider: "fireworks",
3595
- baseUrl: "https://api.fireworks.ai/inference",
3596
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3597
- reasoning: true,
3598
- input: ["text"],
3599
- cost: {
3600
- input: 0.55,
3601
- output: 2.19,
3602
- cacheRead: 0,
3603
- cacheWrite: 0,
3604
- },
3605
- contextWindow: 131072,
3606
- maxTokens: 131072,
3607
- },
3608
- "accounts/fireworks/models/glm-4p5-air": {
3609
- id: "accounts/fireworks/models/glm-4p5-air",
3610
- name: "GLM 4.5 Air",
3611
- api: "anthropic-messages",
3612
- provider: "fireworks",
3613
- baseUrl: "https://api.fireworks.ai/inference",
3614
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3615
- reasoning: true,
3616
- input: ["text"],
3617
- cost: {
3618
- input: 0.22,
3619
- output: 0.88,
3620
- cacheRead: 0,
3621
- cacheWrite: 0,
3622
- },
3623
- contextWindow: 131072,
3624
- maxTokens: 131072,
3625
- },
3626
- "accounts/fireworks/models/glm-4p7": {
3627
- id: "accounts/fireworks/models/glm-4p7",
3628
- name: "GLM 4.7",
3629
- api: "anthropic-messages",
3630
- provider: "fireworks",
3631
- baseUrl: "https://api.fireworks.ai/inference",
3632
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3633
- reasoning: true,
3634
- input: ["text"],
3635
- cost: {
3636
- input: 0.6,
3637
- output: 2.2,
3638
- cacheRead: 0.3,
3639
- cacheWrite: 0,
3640
- },
3641
- contextWindow: 198000,
3642
- maxTokens: 198000,
3643
- },
3644
- "accounts/fireworks/models/glm-5": {
3645
- id: "accounts/fireworks/models/glm-5",
3646
- name: "GLM 5",
3647
- api: "anthropic-messages",
3648
- provider: "fireworks",
3649
- baseUrl: "https://api.fireworks.ai/inference",
3650
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3651
- reasoning: true,
3652
- input: ["text"],
3653
- cost: {
3654
- input: 1,
3655
- output: 3.2,
3656
- cacheRead: 0.5,
3657
- cacheWrite: 0,
3658
- },
3659
- contextWindow: 202752,
3660
- maxTokens: 131072,
3661
- },
3662
3554
  "accounts/fireworks/models/glm-5p1": {
3663
3555
  id: "accounts/fireworks/models/glm-5p1",
3664
3556
  name: "GLM 5.1",
@@ -3689,7 +3581,7 @@ export const MODELS = {
3689
3581
  cost: {
3690
3582
  input: 0.15,
3691
3583
  output: 0.6,
3692
- cacheRead: 0,
3584
+ cacheRead: 0.015,
3693
3585
  cacheWrite: 0,
3694
3586
  },
3695
3587
  contextWindow: 131072,
@@ -3705,50 +3597,14 @@ export const MODELS = {
3705
3597
  reasoning: true,
3706
3598
  input: ["text"],
3707
3599
  cost: {
3708
- input: 0.05,
3709
- output: 0.2,
3710
- cacheRead: 0,
3600
+ input: 0.07,
3601
+ output: 0.3,
3602
+ cacheRead: 0.035,
3711
3603
  cacheWrite: 0,
3712
3604
  },
3713
3605
  contextWindow: 131072,
3714
3606
  maxTokens: 32768,
3715
3607
  },
3716
- "accounts/fireworks/models/kimi-k2-instruct": {
3717
- id: "accounts/fireworks/models/kimi-k2-instruct",
3718
- name: "Kimi K2 Instruct",
3719
- api: "anthropic-messages",
3720
- provider: "fireworks",
3721
- baseUrl: "https://api.fireworks.ai/inference",
3722
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3723
- reasoning: false,
3724
- input: ["text"],
3725
- cost: {
3726
- input: 1,
3727
- output: 3,
3728
- cacheRead: 0,
3729
- cacheWrite: 0,
3730
- },
3731
- contextWindow: 128000,
3732
- maxTokens: 16384,
3733
- },
3734
- "accounts/fireworks/models/kimi-k2-thinking": {
3735
- id: "accounts/fireworks/models/kimi-k2-thinking",
3736
- name: "Kimi K2 Thinking",
3737
- api: "anthropic-messages",
3738
- provider: "fireworks",
3739
- baseUrl: "https://api.fireworks.ai/inference",
3740
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3741
- reasoning: true,
3742
- input: ["text"],
3743
- cost: {
3744
- input: 0.6,
3745
- output: 2.5,
3746
- cacheRead: 0.3,
3747
- cacheWrite: 0,
3748
- },
3749
- contextWindow: 256000,
3750
- maxTokens: 256000,
3751
- },
3752
3608
  "accounts/fireworks/models/kimi-k2p5": {
3753
3609
  id: "accounts/fireworks/models/kimi-k2p5",
3754
3610
  name: "Kimi K2.5",
@@ -3785,24 +3641,6 @@ export const MODELS = {
3785
3641
  contextWindow: 262000,
3786
3642
  maxTokens: 262000,
3787
3643
  },
3788
- "accounts/fireworks/models/minimax-m2p1": {
3789
- id: "accounts/fireworks/models/minimax-m2p1",
3790
- name: "MiniMax-M2.1",
3791
- api: "anthropic-messages",
3792
- provider: "fireworks",
3793
- baseUrl: "https://api.fireworks.ai/inference",
3794
- compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3795
- reasoning: true,
3796
- input: ["text"],
3797
- cost: {
3798
- input: 0.3,
3799
- output: 1.2,
3800
- cacheRead: 0.03,
3801
- cacheWrite: 0,
3802
- },
3803
- contextWindow: 200000,
3804
- maxTokens: 200000,
3805
- },
3806
3644
  "accounts/fireworks/models/minimax-m2p5": {
3807
3645
  id: "accounts/fireworks/models/minimax-m2p5",
3808
3646
  name: "MiniMax-M2.5",
@@ -3833,7 +3671,7 @@ export const MODELS = {
3833
3671
  cost: {
3834
3672
  input: 0.3,
3835
3673
  output: 1.2,
3836
- cacheRead: 0.03,
3674
+ cacheRead: 0.06,
3837
3675
  cacheWrite: 0,
3838
3676
  },
3839
3677
  contextWindow: 196608,
@@ -3857,9 +3695,27 @@ export const MODELS = {
3857
3695
  contextWindow: 128000,
3858
3696
  maxTokens: 8192,
3859
3697
  },
3860
- "accounts/fireworks/routers/kimi-k2p5-turbo": {
3861
- id: "accounts/fireworks/routers/kimi-k2p5-turbo",
3862
- name: "Kimi K2.5 Turbo",
3698
+ "accounts/fireworks/routers/glm-5p1-fast": {
3699
+ id: "accounts/fireworks/routers/glm-5p1-fast",
3700
+ name: "GLM 5.1 Fast",
3701
+ api: "anthropic-messages",
3702
+ provider: "fireworks",
3703
+ baseUrl: "https://api.fireworks.ai/inference",
3704
+ compat: { "sendSessionAffinityHeaders": true, "supportsEagerToolInputStreaming": false, "supportsCacheControlOnTools": false, "supportsLongCacheRetention": false },
3705
+ reasoning: true,
3706
+ input: ["text"],
3707
+ cost: {
3708
+ input: 2.8,
3709
+ output: 8.8,
3710
+ cacheRead: 0.52,
3711
+ cacheWrite: 0,
3712
+ },
3713
+ contextWindow: 202800,
3714
+ maxTokens: 131072,
3715
+ },
3716
+ "accounts/fireworks/routers/kimi-k2p6-turbo": {
3717
+ id: "accounts/fireworks/routers/kimi-k2p6-turbo",
3718
+ name: "Kimi K2.6 Turbo",
3863
3719
  api: "anthropic-messages",
3864
3720
  provider: "fireworks",
3865
3721
  baseUrl: "https://api.fireworks.ai/inference",
@@ -3867,13 +3723,13 @@ export const MODELS = {
3867
3723
  reasoning: true,
3868
3724
  input: ["text", "image"],
3869
3725
  cost: {
3870
- input: 0,
3871
- output: 0,
3872
- cacheRead: 0,
3726
+ input: 2,
3727
+ output: 8,
3728
+ cacheRead: 0.3,
3873
3729
  cacheWrite: 0,
3874
3730
  },
3875
- contextWindow: 256000,
3876
- maxTokens: 256000,
3731
+ contextWindow: 262000,
3732
+ maxTokens: 262000,
3877
3733
  },
3878
3734
  },
3879
3735
  "github-copilot": {
@@ -4046,6 +3902,25 @@ export const MODELS = {
4046
3902
  contextWindow: 128000,
4047
3903
  maxTokens: 64000,
4048
3904
  },
3905
+ "gemini-3.5-flash": {
3906
+ id: "gemini-3.5-flash",
3907
+ name: "Gemini 3.5 Flash",
3908
+ api: "openai-completions",
3909
+ provider: "github-copilot",
3910
+ baseUrl: "https://api.individual.githubcopilot.com",
3911
+ headers: { "User-Agent": "GitHubCopilotChat/0.35.0", "Editor-Version": "vscode/1.107.0", "Editor-Plugin-Version": "copilot-chat/0.35.0", "Copilot-Integration-Id": "vscode-chat" },
3912
+ compat: { "supportsStore": false, "supportsDeveloperRole": false, "supportsReasoningEffort": false },
3913
+ reasoning: true,
3914
+ input: ["text", "image"],
3915
+ cost: {
3916
+ input: 0,
3917
+ output: 0,
3918
+ cacheRead: 0,
3919
+ cacheWrite: 0,
3920
+ },
3921
+ contextWindow: 128000,
3922
+ maxTokens: 64000,
3923
+ },
4049
3924
  "gpt-4.1": {
4050
3925
  id: "gpt-4.1",
4051
3926
  name: "GPT-4.1",
@@ -8778,13 +8653,13 @@ export const MODELS = {
8778
8653
  thinkingLevelMap: { "minimal": null, "low": null, "medium": null, "high": "high", "xhigh": "max" },
8779
8654
  input: ["text"],
8780
8655
  cost: {
8781
- input: 0.112,
8782
- output: 0.224,
8783
- cacheRead: 0.022,
8656
+ input: 0.09999999999999999,
8657
+ output: 0.19999999999999998,
8658
+ cacheRead: 0.02,
8784
8659
  cacheWrite: 0,
8785
8660
  },
8786
8661
  contextWindow: 1048576,
8787
- maxTokens: 4096,
8662
+ maxTokens: 16384,
8788
8663
  },
8789
8664
  "deepseek/deepseek-v4-flash:free": {
8790
8665
  id: "deepseek/deepseek-v4-flash:free",
@@ -11874,11 +11749,11 @@ export const MODELS = {
11874
11749
  cost: {
11875
11750
  input: 0.15,
11876
11751
  output: 1,
11877
- cacheRead: 0.049999999999999996,
11752
+ cacheRead: 0,
11878
11753
  cacheWrite: 0,
11879
11754
  },
11880
11755
  contextWindow: 262144,
11881
- maxTokens: 262144,
11756
+ maxTokens: 262140,
11882
11757
  },
11883
11758
  "qwen/qwen3.6-flash": {
11884
11759
  id: "qwen/qwen3.6-flash",
@@ -11889,10 +11764,10 @@ export const MODELS = {
11889
11764
  reasoning: true,
11890
11765
  input: ["text", "image"],
11891
11766
  cost: {
11892
- input: 0.12375000000000001,
11893
- output: 0.7424999999999999,
11767
+ input: 0.1875,
11768
+ output: 1.125,
11894
11769
  cacheRead: 0,
11895
- cacheWrite: 0.1545,
11770
+ cacheWrite: 0.234375,
11896
11771
  },
11897
11772
  contextWindow: 1000000,
11898
11773
  maxTokens: 65536,
@@ -11923,10 +11798,10 @@ export const MODELS = {
11923
11798
  reasoning: true,
11924
11799
  input: ["text", "image"],
11925
11800
  cost: {
11926
- input: 0.1794,
11927
- output: 1.07315,
11801
+ input: 0.325,
11802
+ output: 1.95,
11928
11803
  cacheRead: 0,
11929
- cacheWrite: 0.2236,
11804
+ cacheWrite: 0.40625,
11930
11805
  },
11931
11806
  contextWindow: 1000000,
11932
11807
  maxTokens: 65536,
@@ -31,7 +31,7 @@ const DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api";
31
31
  const JWT_CLAIM_PATH = "https://api.openai.com/auth";
32
32
  const MAX_RETRIES = 3;
33
33
  const BASE_DELAY_MS = 1000;
34
- const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
34
+ const CODEX_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode", "slingshot"]);
35
35
  const WEBSOCKET_MESSAGE_TOO_BIG_CLOSE_CODE = 1009;
36
36
  const CODEX_RESPONSE_STATUSES = new Set([
37
37
  "completed",
@@ -91,7 +91,7 @@ export const streamOpenAICodexResponses = (model, context, options) => {
91
91
  if (!apiKey) {
92
92
  throw new Error(`No API key for provider: ${model.provider}`);
93
93
  }
94
- const accountId = extractAccountId(apiKey);
94
+ const accountId = extractAccountId(apiKey) || "";
95
95
  let body = buildRequestBody(model, context, options);
96
96
  const nextBody = await options?.onPayload?.(body, model);
97
97
  if (nextBody !== undefined) {
@@ -265,7 +265,7 @@ function buildRequestBody(model, context, options) {
265
265
  stream: true,
266
266
  instructions: context.systemPrompt || "You are a helpful assistant.",
267
267
  input: messages,
268
- text: { verbosity: options?.textVerbosity || "low" },
268
+ text: { verbosity: options?.textVerbosity || "medium" },
269
269
  include: ["reasoning.encrypted_content"],
270
270
  prompt_cache_key: clampOpenAIPromptCacheKey(options?.sessionId),
271
271
  tool_choice: "auto",
@@ -322,11 +322,7 @@ function resolveCodexServiceTier(responseServiceTier, requestServiceTier) {
322
322
  function resolveCodexUrl(baseUrl) {
323
323
  const raw = baseUrl && baseUrl.trim().length > 0 ? baseUrl : DEFAULT_CODEX_BASE_URL;
324
324
  const normalized = raw.replace(/\/+$/, "");
325
- if (normalized.endsWith("/codex/responses"))
326
- return normalized;
327
- if (normalized.endsWith("/codex"))
328
- return `${normalized}/responses`;
329
- return `${normalized}/codex/responses`;
325
+ return `${normalized}/responses`;
330
326
  }
331
327
  function resolveCodexWebSocketUrl(baseUrl) {
332
328
  const url = new URL(resolveCodexUrl(baseUrl));
@@ -1035,15 +1031,13 @@ function extractAccountId(token) {
1035
1031
  try {
1036
1032
  const parts = token.split(".");
1037
1033
  if (parts.length !== 3)
1038
- throw new Error("Invalid token");
1034
+ return null;
1039
1035
  const payload = JSON.parse(atob(parts[1]));
1040
1036
  const accountId = payload?.[JWT_CLAIM_PATH]?.chatgpt_account_id;
1041
- if (!accountId)
1042
- throw new Error("No account ID in token");
1043
- return accountId;
1037
+ return accountId || null;
1044
1038
  }
1045
1039
  catch {
1046
- throw new Error("Failed to extract accountId from token");
1040
+ return null;
1047
1041
  }
1048
1042
  }
1049
1043
  function createCodexRequestId() {
@@ -1058,7 +1052,10 @@ function buildBaseCodexHeaders(initHeaders, additionalHeaders, accountId, token)
1058
1052
  headers.set(key, value);
1059
1053
  }
1060
1054
  headers.set("Authorization", `Bearer ${token}`);
1061
- headers.set("chatgpt-account-id", accountId);
1055
+ // Only set chatgpt-account-id if we have one (OpenAI-specific)
1056
+ if (accountId) {
1057
+ headers.set("chatgpt-account-id", accountId);
1058
+ }
1062
1059
  headers.set("originator", "pi");
1063
1060
  const userAgent = _os ? `pi (${_os.platform()} ${_os.release()}; ${_os.arch()})` : "pi (browser)";
1064
1061
  headers.set("User-Agent", userAgent);
@@ -0,0 +1,3 @@
1
+ # `@mariozechner/clipboard-linux-x64-gnu`
2
+
3
+ This is the **x86_64-unknown-linux-gnu** binary for `@mariozechner/clipboard`
@@ -1,20 +1,23 @@
1
1
  {
2
- "name": "@mariozechner/clipboard-darwin-arm64",
2
+ "name": "@mariozechner/clipboard-linux-x64-gnu",
3
3
  "version": "0.3.6",
4
4
  "os": [
5
- "darwin"
5
+ "linux"
6
6
  ],
7
7
  "cpu": [
8
- "arm64"
8
+ "x64"
9
9
  ],
10
- "main": "clipboard.darwin-arm64.node",
10
+ "main": "clipboard.linux-x64-gnu.node",
11
11
  "files": [
12
- "clipboard.darwin-arm64.node"
12
+ "clipboard.linux-x64-gnu.node"
13
13
  ],
14
14
  "license": "MIT",
15
15
  "engines": {
16
16
  "node": ">= 10"
17
17
  },
18
+ "libc": [
19
+ "glibc"
20
+ ],
18
21
  "repository": {
19
22
  "type": "git",
20
23
  "url": "https://github.com/badlogic/clipboard.git"
@@ -40,6 +40,10 @@ class Receiver extends Writable {
40
40
  * extensions
41
41
  * @param {Boolean} [options.isServer=false] Specifies whether to operate in
42
42
  * client or server mode
43
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
44
+ * buffered data chunks
45
+ * @param {Number} [options.maxFragments=0] The maximum number of message
46
+ * fragments
43
47
  * @param {Number} [options.maxPayload=0] The maximum allowed message length
44
48
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
45
49
  * not to skip UTF-8 validation for text and close messages
@@ -54,6 +58,8 @@ class Receiver extends Writable {
54
58
  this._binaryType = options.binaryType || BINARY_TYPES[0];
55
59
  this._extensions = options.extensions || {};
56
60
  this._isServer = !!options.isServer;
61
+ this._maxBufferedChunks = options.maxBufferedChunks | 0;
62
+ this._maxFragments = options.maxFragments | 0;
57
63
  this._maxPayload = options.maxPayload | 0;
58
64
  this._skipUTF8Validation = !!options.skipUTF8Validation;
59
65
  this[kWebSocket] = undefined;
@@ -89,6 +95,22 @@ class Receiver extends Writable {
89
95
  _write(chunk, encoding, cb) {
90
96
  if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
91
97
 
98
+ if (
99
+ this._maxBufferedChunks > 0 &&
100
+ this._buffers.length >= this._maxBufferedChunks
101
+ ) {
102
+ cb(
103
+ this.createError(
104
+ RangeError,
105
+ 'Too many buffered chunks',
106
+ false,
107
+ 1008,
108
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
109
+ )
110
+ );
111
+ return;
112
+ }
113
+
92
114
  this._bufferedBytes += chunk.length;
93
115
  this._buffers.push(chunk);
94
116
  this.startLoop(cb);
@@ -485,6 +507,22 @@ class Receiver extends Writable {
485
507
  }
486
508
 
487
509
  if (data.length) {
510
+ if (
511
+ this._maxFragments > 0 &&
512
+ this._fragments.length >= this._maxFragments
513
+ ) {
514
+ const error = this.createError(
515
+ RangeError,
516
+ 'Too many message fragments',
517
+ false,
518
+ 1008,
519
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
520
+ );
521
+
522
+ cb(error);
523
+ return;
524
+ }
525
+
488
526
  //
489
527
  // This message is not compressed so its length is the sum of the payload
490
528
  // length of all fragments.
@@ -524,6 +562,22 @@ class Receiver extends Writable {
524
562
  return;
525
563
  }
526
564
 
565
+ if (
566
+ this._maxFragments > 0 &&
567
+ this._fragments.length >= this._maxFragments
568
+ ) {
569
+ const error = this.createError(
570
+ RangeError,
571
+ 'Too many message fragments',
572
+ false,
573
+ 1008,
574
+ 'WS_ERR_TOO_MANY_BUFFERED_PARTS'
575
+ );
576
+
577
+ cb(error);
578
+ return;
579
+ }
580
+
527
581
  this._fragments.push(buf);
528
582
  }
529
583
 
@@ -43,6 +43,10 @@ class WebSocketServer extends EventEmitter {
43
43
  * called
44
44
  * @param {Function} [options.handleProtocols] A hook to handle protocols
45
45
  * @param {String} [options.host] The hostname where to bind the server
46
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
47
+ * buffered data chunks
48
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
49
+ * fragments
46
50
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
47
51
  * size
48
52
  * @param {Boolean} [options.noServer=false] Enable no server mode
@@ -65,6 +69,8 @@ class WebSocketServer extends EventEmitter {
65
69
  options = {
66
70
  allowSynchronousEvents: true,
67
71
  autoPong: true,
72
+ maxBufferedChunks: 1024 * 1024,
73
+ maxFragments: 128 * 1024,
68
74
  maxPayload: 100 * 1024 * 1024,
69
75
  skipUTF8Validation: false,
70
76
  perMessageDeflate: false,
@@ -424,6 +430,8 @@ class WebSocketServer extends EventEmitter {
424
430
 
425
431
  ws.setSocket(socket, head, {
426
432
  allowSynchronousEvents: this.options.allowSynchronousEvents,
433
+ maxBufferedChunks: this.options.maxBufferedChunks,
434
+ maxFragments: this.options.maxFragments,
427
435
  maxPayload: this.options.maxPayload,
428
436
  skipUTF8Validation: this.options.skipUTF8Validation
429
437
  });
@@ -201,6 +201,10 @@ class WebSocket extends EventEmitter {
201
201
  * multiple times in the same tick
202
202
  * @param {Function} [options.generateMask] The function used to generate the
203
203
  * masking key
204
+ * @param {Number} [options.maxBufferedChunks=0] The maximum number of
205
+ * buffered data chunks
206
+ * @param {Number} [options.maxFragments=0] The maximum number of message
207
+ * fragments
204
208
  * @param {Number} [options.maxPayload=0] The maximum allowed message size
205
209
  * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
206
210
  * not to skip UTF-8 validation for text and close messages
@@ -212,6 +216,8 @@ class WebSocket extends EventEmitter {
212
216
  binaryType: this.binaryType,
213
217
  extensions: this._extensions,
214
218
  isServer: this._isServer,
219
+ maxBufferedChunks: options.maxBufferedChunks,
220
+ maxFragments: options.maxFragments,
215
221
  maxPayload: options.maxPayload,
216
222
  skipUTF8Validation: options.skipUTF8Validation
217
223
  });
@@ -640,6 +646,10 @@ module.exports = WebSocket;
640
646
  * masking key
641
647
  * @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
642
648
  * handshake request
649
+ * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
650
+ * buffered data chunks
651
+ * @param {Number} [options.maxFragments=131072] The maximum number of message
652
+ * fragments
643
653
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
644
654
  * size
645
655
  * @param {Number} [options.maxRedirects=10] The maximum number of redirects
@@ -660,6 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
660
670
  autoPong: true,
661
671
  closeTimeout: CLOSE_TIMEOUT,
662
672
  protocolVersion: protocolVersions[1],
673
+ maxBufferedChunks: 1024 * 1024,
674
+ maxFragments: 128 * 1024,
663
675
  maxPayload: 100 * 1024 * 1024,
664
676
  skipUTF8Validation: false,
665
677
  perMessageDeflate: true,
@@ -1017,6 +1029,8 @@ function initAsClient(websocket, address, protocols, options) {
1017
1029
  websocket.setSocket(socket, head, {
1018
1030
  allowSynchronousEvents: opts.allowSynchronousEvents,
1019
1031
  generateMask: opts.generateMask,
1032
+ maxBufferedChunks: opts.maxBufferedChunks,
1033
+ maxFragments: opts.maxFragments,
1020
1034
  maxPayload: opts.maxPayload,
1021
1035
  skipUTF8Validation: opts.skipUTF8Validation
1022
1036
  });