@entelligentsia/forgecli 0.7.10 → 0.8.4

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.
Files changed (227) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/CHANGELOG-forge-plugin.md +70 -0
  3. package/dist/CHANGELOG-pi.md +63 -0
  4. package/dist/bin/argv.d.ts +2 -2
  5. package/dist/bin/argv.js +10 -0
  6. package/dist/bin/argv.js.map +1 -1
  7. package/dist/bin/env-defaults.d.ts +1 -0
  8. package/dist/bin/env-defaults.js +13 -0
  9. package/dist/bin/env-defaults.js.map +1 -0
  10. package/dist/bin/forge.js +9 -0
  11. package/dist/bin/forge.js.map +1 -1
  12. package/dist/bin/update-cli.d.ts +9 -0
  13. package/dist/bin/update-cli.js +120 -0
  14. package/dist/bin/update-cli.js.map +1 -0
  15. package/dist/extensions/forgecli/index.js +3 -3
  16. package/dist/extensions/forgecli/index.js.map +1 -1
  17. package/dist/extensions/forgecli/update-check.js +1 -1
  18. package/dist/extensions/forgecli/update-check.js.map +1 -1
  19. package/dist/extensions/forgecli/whats-new-widget.d.ts +5 -5
  20. package/dist/extensions/forgecli/whats-new-widget.js +11 -11
  21. package/dist/extensions/forgecli/whats-new-widget.js.map +1 -1
  22. package/dist/extensions/forgecli/whats-new.js +6 -5
  23. package/dist/extensions/forgecli/whats-new.js.map +1 -1
  24. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  25. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +27 -98
  26. package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
  27. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +62 -132
  28. package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
  29. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.d.ts.map +1 -1
  30. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js +25 -15
  31. package/node_modules/@earendil-works/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  32. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  33. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js +1 -0
  34. package/node_modules/@earendil-works/pi-ai/dist/providers/anthropic.js.map +1 -1
  35. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  36. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js +17 -1
  37. package/node_modules/@earendil-works/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  38. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  39. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js +8 -2
  40. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-completions.js.map +1 -1
  41. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  42. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js +17 -1
  43. package/node_modules/@earendil-works/pi-ai/dist/providers/openai-responses.js.map +1 -1
  44. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  45. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js +8 -1
  46. package/node_modules/@earendil-works/pi-ai/dist/providers/simple-options.js.map +1 -1
  47. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  48. package/node_modules/@earendil-works/pi-coding-agent/CHANGELOG.md +63 -0
  49. package/node_modules/@earendil-works/pi-coding-agent/README.md +1 -1
  50. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.d.ts.map +1 -1
  51. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.js +1 -1
  52. package/node_modules/@earendil-works/pi-coding-agent/dist/cli/config-selector.js.map +1 -1
  53. package/node_modules/@earendil-works/pi-coding-agent/dist/cli.d.ts.map +1 -1
  54. package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js +6 -10
  55. package/node_modules/@earendil-works/pi-coding-agent/dist/cli.js.map +1 -1
  56. package/node_modules/@earendil-works/pi-coding-agent/dist/config.d.ts.map +1 -1
  57. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js +12 -3
  58. package/node_modules/@earendil-works/pi-coding-agent/dist/config.js.map +1 -1
  59. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts +1 -0
  60. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  61. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js +30 -15
  62. package/node_modules/@earendil-works/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  63. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.d.ts +3 -3
  64. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  65. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js +23 -13
  66. package/node_modules/@earendil-works/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  67. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts +4 -0
  68. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  69. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js +58 -38
  70. package/node_modules/@earendil-works/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  71. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  72. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js +0 -1
  73. package/node_modules/@earendil-works/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  74. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  75. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js +3 -2
  76. package/node_modules/@earendil-works/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  77. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts +2 -2
  78. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  79. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js +7 -4
  80. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/components/config-selector.js.map +1 -1
  81. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  82. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js +6 -2
  83. package/node_modules/@earendil-works/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  84. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.d.ts.map +1 -1
  85. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js +3 -4
  86. package/node_modules/@earendil-works/pi-coding-agent/dist/package-manager-cli.js.map +1 -1
  87. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.d.ts.map +1 -1
  88. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js +2 -2
  89. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/changelog.js.map +1 -1
  90. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.d.ts +7 -1
  91. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.d.ts.map +1 -1
  92. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.js +60 -7
  93. package/node_modules/@earendil-works/pi-coding-agent/dist/utils/child-process.js.map +1 -1
  94. package/node_modules/@earendil-works/pi-coding-agent/docs/packages.md +2 -2
  95. package/node_modules/@earendil-works/pi-coding-agent/docs/settings.md +1 -3
  96. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package.json +1 -1
  97. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  98. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package.json +1 -1
  99. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package.json +1 -1
  100. package/node_modules/@earendil-works/pi-coding-agent/package.json +6 -6
  101. package/node_modules/@earendil-works/pi-tui/package.json +2 -2
  102. package/node_modules/@protobufjs/fetch/CHANGELOG.md +8 -0
  103. package/node_modules/@protobufjs/fetch/index.d.ts +7 -7
  104. package/node_modules/@protobufjs/fetch/index.js +4 -7
  105. package/node_modules/@protobufjs/fetch/package.json +7 -5
  106. package/node_modules/@protobufjs/fetch/tests/data/file.txt +1 -0
  107. package/node_modules/@protobufjs/fetch/tests/index.js +150 -8
  108. package/node_modules/@protobufjs/fetch/util/fs.js +11 -0
  109. package/node_modules/@protobufjs/inquire/CHANGELOG.md +8 -0
  110. package/node_modules/@protobufjs/inquire/index.d.ts +1 -0
  111. package/node_modules/@protobufjs/inquire/index.js +1 -0
  112. package/node_modules/@protobufjs/inquire/package.json +1 -1
  113. package/node_modules/protobufjs/dist/light/protobuf.js +187 -153
  114. package/node_modules/protobufjs/dist/light/protobuf.js.map +1 -1
  115. package/node_modules/protobufjs/dist/light/protobuf.min.js +3 -3
  116. package/node_modules/protobufjs/dist/light/protobuf.min.js.map +1 -1
  117. package/node_modules/protobufjs/dist/minimal/protobuf.js +14 -5
  118. package/node_modules/protobufjs/dist/minimal/protobuf.js.map +1 -1
  119. package/node_modules/protobufjs/dist/minimal/protobuf.min.js +3 -3
  120. package/node_modules/protobufjs/dist/minimal/protobuf.min.js.map +1 -1
  121. package/node_modules/protobufjs/dist/protobuf.js +207 -173
  122. package/node_modules/protobufjs/dist/protobuf.js.map +1 -1
  123. package/node_modules/protobufjs/dist/protobuf.min.js +3 -3
  124. package/node_modules/protobufjs/dist/protobuf.min.js.map +1 -1
  125. package/node_modules/protobufjs/package.json +6 -3
  126. package/node_modules/protobufjs/src/util/fs.js +11 -0
  127. package/node_modules/protobufjs/src/util/minimal.js +10 -2
  128. package/node_modules/protobufjs/src/util.js +1 -1
  129. package/node_modules/undici/README.md +14 -5
  130. package/node_modules/undici/docs/docs/api/Client.md +4 -2
  131. package/node_modules/undici/docs/docs/api/Dispatcher.md +62 -27
  132. package/node_modules/undici/docs/docs/api/GlobalInstallation.md +7 -5
  133. package/node_modules/undici/docs/docs/api/H2CClient.md +1 -1
  134. package/node_modules/undici/docs/docs/api/RedirectHandler.md +14 -9
  135. package/node_modules/undici/docs/docs/api/RetryAgent.md +0 -1
  136. package/node_modules/undici/docs/docs/api/RetryHandler.md +12 -14
  137. package/node_modules/undici/docs/docs/api/SnapshotAgent.md +23 -0
  138. package/node_modules/undici/docs/docs/best-practices/migrating-from-v7-to-v8.md +231 -0
  139. package/node_modules/undici/index.js +4 -2
  140. package/node_modules/undici/lib/api/api-connect.js +13 -11
  141. package/node_modules/undici/lib/api/api-pipeline.js +26 -13
  142. package/node_modules/undici/lib/api/api-request.js +45 -21
  143. package/node_modules/undici/lib/api/api-stream.js +81 -20
  144. package/node_modules/undici/lib/api/api-upgrade.js +21 -11
  145. package/node_modules/undici/lib/api/readable.js +3 -2
  146. package/node_modules/undici/lib/cache/memory-cache-store.js +1 -1
  147. package/node_modules/undici/lib/cache/sqlite-cache-store.js +6 -4
  148. package/node_modules/undici/lib/core/connect.js +17 -1
  149. package/node_modules/undici/lib/core/constants.js +1 -24
  150. package/node_modules/undici/lib/core/errors.js +2 -2
  151. package/node_modules/undici/lib/core/request.js +115 -18
  152. package/node_modules/undici/lib/core/socks5-client.js +24 -9
  153. package/node_modules/undici/lib/core/socks5-utils.js +32 -23
  154. package/node_modules/undici/lib/core/symbols.js +1 -0
  155. package/node_modules/undici/lib/core/util.js +70 -43
  156. package/node_modules/undici/lib/dispatcher/agent.js +47 -33
  157. package/node_modules/undici/lib/dispatcher/balanced-pool.js +21 -26
  158. package/node_modules/undici/lib/dispatcher/client-h1.js +98 -39
  159. package/node_modules/undici/lib/dispatcher/client-h2.js +603 -272
  160. package/node_modules/undici/lib/dispatcher/client.js +12 -5
  161. package/node_modules/undici/lib/dispatcher/dispatcher-base.js +24 -5
  162. package/node_modules/undici/lib/dispatcher/dispatcher.js +0 -4
  163. package/node_modules/undici/lib/dispatcher/dispatcher1-wrapper.js +107 -0
  164. package/node_modules/undici/lib/dispatcher/h2c-client.js +5 -5
  165. package/node_modules/undici/lib/dispatcher/pool-base.js +28 -10
  166. package/node_modules/undici/lib/dispatcher/pool.js +31 -6
  167. package/node_modules/undici/lib/dispatcher/proxy-agent.js +38 -13
  168. package/node_modules/undici/lib/dispatcher/round-robin-pool.js +31 -9
  169. package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +95 -80
  170. package/node_modules/undici/lib/global.js +13 -1
  171. package/node_modules/undici/lib/handler/cache-handler.js +16 -8
  172. package/node_modules/undici/lib/handler/decorator-handler.js +1 -2
  173. package/node_modules/undici/lib/handler/redirect-handler.js +5 -51
  174. package/node_modules/undici/lib/handler/retry-handler.js +15 -2
  175. package/node_modules/undici/lib/interceptor/cache.js +30 -17
  176. package/node_modules/undici/lib/interceptor/decompress.js +28 -2
  177. package/node_modules/undici/lib/interceptor/dns.js +1 -1
  178. package/node_modules/undici/lib/interceptor/redirect.js +3 -3
  179. package/node_modules/undici/lib/llhttp/llhttp-wasm.js +1 -1
  180. package/node_modules/undici/lib/llhttp/llhttp_simd-wasm.js +1 -1
  181. package/node_modules/undici/lib/mock/mock-agent.js +8 -8
  182. package/node_modules/undici/lib/mock/mock-call-history.js +15 -15
  183. package/node_modules/undici/lib/mock/mock-utils.js +37 -22
  184. package/node_modules/undici/lib/mock/snapshot-agent.js +16 -6
  185. package/node_modules/undici/lib/mock/snapshot-recorder.js +38 -3
  186. package/node_modules/undici/lib/util/cache.js +8 -7
  187. package/node_modules/undici/lib/util/runtime-features.js +3 -34
  188. package/node_modules/undici/lib/web/cache/cache.js +6 -8
  189. package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +245 -150
  190. package/node_modules/undici/lib/web/fetch/body.js +3 -9
  191. package/node_modules/undici/lib/web/fetch/formdata-parser.js +17 -6
  192. package/node_modules/undici/lib/web/fetch/formdata.js +21 -2
  193. package/node_modules/undici/lib/web/fetch/index.js +214 -221
  194. package/node_modules/undici/lib/web/webidl/index.js +7 -9
  195. package/node_modules/undici/lib/web/websocket/frame.js +1 -7
  196. package/node_modules/undici/lib/web/websocket/permessage-deflate.js +13 -31
  197. package/node_modules/undici/lib/web/websocket/receiver.js +62 -22
  198. package/node_modules/undici/lib/web/websocket/stream/websocketstream.js +11 -17
  199. package/node_modules/undici/lib/web/websocket/websocket.js +6 -1
  200. package/node_modules/undici/package.json +9 -9
  201. package/node_modules/undici/types/agent.d.ts +0 -2
  202. package/node_modules/undici/types/client.d.ts +25 -19
  203. package/node_modules/undici/types/dispatcher.d.ts +7 -27
  204. package/node_modules/undici/types/dispatcher1-wrapper.d.ts +7 -0
  205. package/node_modules/undici/types/formdata.d.ts +0 -6
  206. package/node_modules/undici/types/h2c-client.d.ts +6 -6
  207. package/node_modules/undici/types/header.d.ts +5 -0
  208. package/node_modules/undici/types/index.d.ts +3 -1
  209. package/node_modules/undici/types/interceptors.d.ts +1 -1
  210. package/node_modules/undici/types/pool.d.ts +0 -2
  211. package/node_modules/undici/types/proxy-agent.d.ts +2 -2
  212. package/node_modules/undici/types/round-robin-pool.d.ts +0 -2
  213. package/node_modules/undici/types/snapshot-agent.d.ts +4 -0
  214. package/node_modules/undici/types/socks5-proxy-agent.d.ts +2 -2
  215. package/node_modules/undici/types/webidl.d.ts +0 -1
  216. package/package.json +7 -8
  217. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/custom-provider-anthropic/package-lock.json +0 -24
  218. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/sandbox/package-lock.json +0 -92
  219. package/node_modules/@earendil-works/pi-coding-agent/examples/extensions/with-deps/package-lock.json +0 -31
  220. package/node_modules/undici/lib/handler/unwrap-handler.js +0 -100
  221. package/node_modules/undici/lib/handler/wrap-handler.js +0 -105
  222. package/node_modules/undici/lib/llhttp/.gitkeep +0 -0
  223. package/node_modules/undici/lib/util/promise.js +0 -28
  224. package/skills/refresh-kb-links/SKILL.md +0 -217
  225. package/skills/store-custodian/SKILL.md +0 -163
  226. package/skills/store-query-grammar/SKILL.md +0 -145
  227. package/skills/store-query-nlp/SKILL.md +0 -110
@@ -7,6 +7,7 @@ const { debuglog } = require('node:util')
7
7
  const { parseAddress } = require('./socks5-utils')
8
8
 
9
9
  const debug = debuglog('undici:socks5')
10
+ const EMPTY_BUFFER = Buffer.alloc(0)
10
11
 
11
12
  // SOCKS5 constants
12
13
  const SOCKS_VERSION = 0x05
@@ -51,6 +52,7 @@ const STATES = {
51
52
  INITIAL: 'initial',
52
53
  HANDSHAKING: 'handshaking',
53
54
  AUTHENTICATING: 'authenticating',
55
+ AUTHENTICATED: 'authenticated',
54
56
  CONNECTING: 'connecting',
55
57
  CONNECTED: 'connected',
56
58
  ERROR: 'error',
@@ -72,7 +74,10 @@ class Socks5Client extends EventEmitter {
72
74
  this.socket = socket
73
75
  this.options = options
74
76
  this.state = STATES.INITIAL
75
- this.buffer = Buffer.alloc(0)
77
+ this.buffer = EMPTY_BUFFER
78
+ this.onSocketData = this.onData.bind(this)
79
+ this.onSocketError = this.onError.bind(this)
80
+ this.onSocketClose = this.onClose.bind(this)
76
81
 
77
82
  // Authentication settings
78
83
  this.authMethods = []
@@ -82,9 +87,9 @@ class Socks5Client extends EventEmitter {
82
87
  this.authMethods.push(AUTH_METHODS.NO_AUTH)
83
88
 
84
89
  // Socket event handlers
85
- this.socket.on('data', this.onData.bind(this))
86
- this.socket.on('error', this.onError.bind(this))
87
- this.socket.on('close', this.onClose.bind(this))
90
+ this.socket.on('data', this.onSocketData)
91
+ this.socket.on('error', this.onSocketError)
92
+ this.socket.on('close', this.onSocketClose)
88
93
  }
89
94
 
90
95
  /**
@@ -139,6 +144,11 @@ class Socks5Client extends EventEmitter {
139
144
  }
140
145
  }
141
146
 
147
+ markAuthenticated () {
148
+ this.state = STATES.AUTHENTICATED
149
+ this.emit('authenticated')
150
+ }
151
+
142
152
  /**
143
153
  * Start the SOCKS5 handshake
144
154
  */
@@ -189,7 +199,7 @@ class Socks5Client extends EventEmitter {
189
199
  debug('server selected auth method', method)
190
200
 
191
201
  if (method === AUTH_METHODS.NO_AUTH) {
192
- this.emit('authenticated')
202
+ this.markAuthenticated()
193
203
  } else if (method === AUTH_METHODS.USERNAME_PASSWORD) {
194
204
  this.state = STATES.AUTHENTICATING
195
205
  this.sendAuthRequest()
@@ -254,7 +264,7 @@ class Socks5Client extends EventEmitter {
254
264
 
255
265
  this.buffer = this.buffer.subarray(2)
256
266
  debug('authentication successful')
257
- this.emit('authenticated')
267
+ this.markAuthenticated()
258
268
  }
259
269
 
260
270
  /**
@@ -263,8 +273,12 @@ class Socks5Client extends EventEmitter {
263
273
  * @param {number} port - Target port
264
274
  */
265
275
  connect (address, port) {
266
- if (this.state === STATES.CONNECTED) {
267
- throw new InvalidArgumentError('Already connected')
276
+ if (this.state === STATES.CONNECTING || this.state === STATES.CONNECTED) {
277
+ throw new InvalidArgumentError('Connection already in progress')
278
+ }
279
+
280
+ if (this.state !== STATES.AUTHENTICATED) {
281
+ throw new InvalidArgumentError('Client must be authenticated before CONNECT')
268
282
  }
269
283
 
270
284
  debug('connecting to', address, port)
@@ -363,8 +377,9 @@ class Socks5Client extends EventEmitter {
363
377
 
364
378
  const boundPort = this.buffer.readUInt16BE(offset)
365
379
 
366
- this.buffer = this.buffer.subarray(responseLength)
380
+ this.buffer = EMPTY_BUFFER
367
381
  this.state = STATES.CONNECTED
382
+ this.socket.removeListener('data', this.onSocketData)
368
383
 
369
384
  debug('connected, bound address:', boundAddress, 'port:', boundPort)
370
385
  this.emit('connected', { address: boundAddress, port: boundPort })
@@ -46,34 +46,43 @@ function parseAddress (address) {
46
46
  */
47
47
  function parseIPv6 (address) {
48
48
  const buffer = Buffer.alloc(16)
49
- const parts = address.split(':')
50
- let partIndex = 0
51
- let bufferIndex = 0
49
+ let normalizedAddress = address
50
+
51
+ // Expand an embedded IPv4 tail into the last two IPv6 groups.
52
+ if (address.includes('.')) {
53
+ const lastColonIndex = address.lastIndexOf(':')
54
+ const ipv4Part = address.slice(lastColonIndex + 1)
55
+
56
+ if (net.isIPv4(ipv4Part)) {
57
+ const octets = ipv4Part.split('.').map(Number)
58
+ const high = ((octets[0] << 8) | octets[1]).toString(16)
59
+ const low = ((octets[2] << 8) | octets[3]).toString(16)
60
+ normalizedAddress = `${address.slice(0, lastColonIndex)}:${high}:${low}`
61
+ }
62
+ }
52
63
 
53
64
  // Handle compressed notation (::)
54
- const doubleColonIndex = address.indexOf('::')
65
+ const doubleColonIndex = normalizedAddress.indexOf('::')
55
66
  if (doubleColonIndex !== -1) {
56
- // Count non-empty parts
57
- const nonEmptyParts = parts.filter(p => p.length > 0).length
58
- const skipParts = 8 - nonEmptyParts
59
-
60
- for (let i = 0; i < parts.length; i++) {
61
- if (parts[i] === '' && i === doubleColonIndex / 3) {
62
- // Skip empty parts for ::
63
- bufferIndex += skipParts * 2
64
- } else if (parts[i] !== '') {
65
- const value = parseInt(parts[i], 16)
66
- buffer.writeUInt16BE(value, bufferIndex)
67
- bufferIndex += 2
68
- }
67
+ const before = normalizedAddress.slice(0, doubleColonIndex)
68
+ const after = normalizedAddress.slice(doubleColonIndex + 2)
69
+ const beforeParts = before === '' ? [] : before.split(':')
70
+ const afterParts = after === '' ? [] : after.split(':')
71
+
72
+ let bufferIndex = 0
73
+ for (const part of beforeParts) {
74
+ buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
75
+ bufferIndex += 2
76
+ }
77
+ bufferIndex = 16 - afterParts.length * 2
78
+ for (const part of afterParts) {
79
+ buffer.writeUInt16BE(parseInt(part, 16), bufferIndex)
80
+ bufferIndex += 2
69
81
  }
70
82
  } else {
71
- // No compression, parse normally
72
- for (const part of parts) {
73
- if (part === '') continue
74
- const value = parseInt(part, 16)
75
- buffer.writeUInt16BE(value, partIndex * 2)
76
- partIndex++
83
+ const parts = normalizedAddress.split(':')
84
+ for (let i = 0; i < parts.length; i++) {
85
+ buffer.writeUInt16BE(parseInt(parts[i], 16), i * 2)
77
86
  }
78
87
  }
79
88
 
@@ -62,6 +62,7 @@ module.exports = {
62
62
  kListeners: Symbol('listeners'),
63
63
  kHTTPContext: Symbol('http context'),
64
64
  kMaxConcurrentStreams: Symbol('max concurrent streams'),
65
+ kHostAuthority: Symbol('host authority'),
65
66
  kHTTP2InitialWindowSize: Symbol('http2 initial window size'),
66
67
  kHTTP2ConnectionWindowSize: Symbol('http2 connection window size'),
67
68
  kEnableConnectProtocol: Symbol('http2session connect protocol'),
@@ -6,14 +6,12 @@ const { IncomingMessage } = require('node:http')
6
6
  const stream = require('node:stream')
7
7
  const net = require('node:net')
8
8
  const { stringify } = require('node:querystring')
9
- const { EventEmitter: EE } = require('node:events')
9
+ const { EventEmitter: EE, addAbortListener: addAbortListenerNative } = require('node:events')
10
10
  const timers = require('../util/timers')
11
11
  const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
12
12
  const { headerNameLowerCasedRecord } = require('./constants')
13
13
  const { tree } = require('./tree')
14
14
 
15
- const [nodeMajor, nodeMinor] = process.versions.node.split('.', 2).map(v => Number(v))
16
-
17
15
  class BodyAsyncIterable {
18
16
  constructor (body) {
19
17
  this[kBody] = body
@@ -85,23 +83,9 @@ function isStream (obj) {
85
83
  /**
86
84
  * @param {*} object
87
85
  * @returns {object is Blob}
88
- * based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
89
86
  */
90
87
  function isBlobLike (object) {
91
- if (object === null) {
92
- return false
93
- } else if (object instanceof Blob) {
94
- return true
95
- } else if (typeof object !== 'object') {
96
- return false
97
- } else {
98
- const sTag = object[Symbol.toStringTag]
99
-
100
- return (sTag === 'Blob' || sTag === 'File') && (
101
- ('stream' in object && typeof object.stream === 'function') ||
102
- ('arrayBuffer' in object && typeof object.arrayBuffer === 'function')
103
- )
104
- }
88
+ return object instanceof Blob
105
89
  }
106
90
 
107
91
  /**
@@ -337,7 +321,7 @@ function isIterable (obj) {
337
321
  */
338
322
  function hasSafeIterator (obj) {
339
323
  const prototype = Object.getPrototypeOf(obj)
340
- const ownIterator = Object.prototype.hasOwnProperty.call(obj, Symbol.iterator)
324
+ const ownIterator = Object.hasOwn(obj, Symbol.iterator)
341
325
  return ownIterator || (prototype != null && prototype !== Object.prototype && typeof obj[Symbol.iterator] === 'function')
342
326
  }
343
327
 
@@ -480,10 +464,30 @@ function parseHeaders (headers, obj) {
480
464
  }
481
465
 
482
466
  /**
483
- * @param {Buffer[]} headers
467
+ * @param {Buffer[] | string[] | Record<string, string | string[]> | null | undefined} headers
484
468
  * @returns {string[]}
485
469
  */
486
470
  function parseRawHeaders (headers) {
471
+ if (headers == null) {
472
+ return []
473
+ }
474
+
475
+ if (!Array.isArray(headers)) {
476
+ const rawHeaders = []
477
+
478
+ for (const [name, value] of Object.entries(headers)) {
479
+ if (Array.isArray(value)) {
480
+ for (const entry of value) {
481
+ rawHeaders.push(name, `${entry}`)
482
+ }
483
+ } else {
484
+ rawHeaders.push(name, `${value}`)
485
+ }
486
+ }
487
+
488
+ return rawHeaders
489
+ }
490
+
487
491
  const headersLength = headers.length
488
492
  /**
489
493
  * @type {string[]}
@@ -507,6 +511,26 @@ function parseRawHeaders (headers) {
507
511
  return ret
508
512
  }
509
513
 
514
+ /**
515
+ * @param {Record<string, string | string[]>} headers
516
+ * @returns {Buffer[]}
517
+ */
518
+ function toRawHeaders (headers) {
519
+ const rawHeaders = []
520
+
521
+ for (const [name, value] of Object.entries(headers)) {
522
+ if (Array.isArray(value)) {
523
+ for (const entry of value) {
524
+ rawHeaders.push(Buffer.from(name, 'latin1'), Buffer.from(`${entry}`, 'latin1'))
525
+ }
526
+ } else {
527
+ rawHeaders.push(Buffer.from(name, 'latin1'), Buffer.from(`${value}`, 'latin1'))
528
+ }
529
+ }
530
+
531
+ return rawHeaders
532
+ }
533
+
510
534
  /**
511
535
  * @param {string[]} headers
512
536
  * @param {Buffer[]} headers
@@ -540,38 +564,37 @@ function assertRequestHandler (handler, method, upgrade) {
540
564
  throw new InvalidArgumentError('handler must be an object')
541
565
  }
542
566
 
543
- if (typeof handler.onRequestStart === 'function') {
544
- // TODO (fix): More checks...
545
- return
546
- }
547
-
548
- if (typeof handler.onConnect !== 'function') {
549
- throw new InvalidArgumentError('invalid onConnect method')
567
+ if (typeof handler.onRequestStart !== 'function') {
568
+ throw new InvalidArgumentError('invalid onRequestStart method')
550
569
  }
551
570
 
552
- if (typeof handler.onError !== 'function') {
553
- throw new InvalidArgumentError('invalid onError method')
571
+ if (typeof handler.onResponseError !== 'function') {
572
+ throw new InvalidArgumentError('invalid onResponseError method')
554
573
  }
555
574
 
556
575
  if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) {
557
576
  throw new InvalidArgumentError('invalid onBodySent method')
558
577
  }
559
578
 
579
+ if (typeof handler.onRequestSent !== 'function' && handler.onRequestSent !== undefined) {
580
+ throw new InvalidArgumentError('invalid onRequestSent method')
581
+ }
582
+
560
583
  if (upgrade || method === 'CONNECT') {
561
- if (typeof handler.onUpgrade !== 'function') {
562
- throw new InvalidArgumentError('invalid onUpgrade method')
584
+ if (typeof handler.onRequestUpgrade !== 'function') {
585
+ throw new InvalidArgumentError('invalid onRequestUpgrade method')
563
586
  }
564
587
  } else {
565
- if (typeof handler.onHeaders !== 'function') {
566
- throw new InvalidArgumentError('invalid onHeaders method')
588
+ if (typeof handler.onResponseStart !== 'function') {
589
+ throw new InvalidArgumentError('invalid onResponseStart method')
567
590
  }
568
591
 
569
- if (typeof handler.onData !== 'function') {
570
- throw new InvalidArgumentError('invalid onData method')
592
+ if (typeof handler.onResponseData !== 'function') {
593
+ throw new InvalidArgumentError('invalid onResponseData method')
571
594
  }
572
595
 
573
- if (typeof handler.onComplete !== 'function') {
574
- throw new InvalidArgumentError('invalid onComplete method')
596
+ if (typeof handler.onResponseEnd !== 'function') {
597
+ throw new InvalidArgumentError('invalid onResponseEnd method')
575
598
  }
576
599
  }
577
600
  }
@@ -675,7 +698,12 @@ function isFormDataLike (object) {
675
698
  }
676
699
 
677
700
  function addAbortListener (signal, listener) {
678
- if ('addEventListener' in signal) {
701
+ if (signal instanceof AbortSignal) {
702
+ const disposable = addAbortListenerNative(signal, listener)
703
+ return () => disposable[Symbol.dispose]()
704
+ }
705
+
706
+ if (typeof signal.addEventListener === 'function') {
679
707
  signal.addEventListener('abort', listener, { once: true })
680
708
  return () => signal.removeEventListener('abort', listener)
681
709
  }
@@ -748,7 +776,7 @@ function isValidHeaderValue (characters) {
748
776
  return !headerCharRegex.test(characters)
749
777
  }
750
778
 
751
- const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+)?$/
779
+ const rangeHeaderRegex = /^bytes (\d+)-(\d+)\/(\d+|\*)?$/
752
780
 
753
781
  /**
754
782
  * @typedef {object} RangeHeader
@@ -771,7 +799,7 @@ function parseRangeHeader (range) {
771
799
  ? {
772
800
  start: parseInt(m[1]),
773
801
  end: m[2] ? parseInt(m[2]) : null,
774
- size: m[3] ? parseInt(m[3]) : null
802
+ size: m[3] && m[3] !== '*' ? parseInt(m[3]) : null
775
803
  }
776
804
  : null
777
805
  }
@@ -812,7 +840,7 @@ function removeAllListeners (obj) {
812
840
  */
813
841
  function errorRequest (client, request, err) {
814
842
  try {
815
- request.onError(err)
843
+ request.onResponseError(err)
816
844
  assert(request.aborted)
817
845
  } catch (err) {
818
846
  client.emit('error', err)
@@ -961,6 +989,7 @@ module.exports = {
961
989
  removeAllListeners,
962
990
  errorRequest,
963
991
  parseRawHeaders,
992
+ toRawHeaders,
964
993
  encodeRawHeaders,
965
994
  parseHeaders,
966
995
  parseKeepAliveTimeout,
@@ -983,8 +1012,6 @@ module.exports = {
983
1012
  normalizedMethodRecords,
984
1013
  isValidPort,
985
1014
  isHttpOrHttpsPrefixed,
986
- nodeMajor,
987
- nodeMinor,
988
1015
  safeHTTPMethods: Object.freeze(['GET', 'HEAD', 'OPTIONS', 'TRACE']),
989
1016
  wrapRequestBody,
990
1017
  setupConnectTimeout,
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
4
- const { kClients, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
4
+ const { kBusy, kClients, kConnected, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
5
5
  const DispatcherBase = require('./dispatcher-base')
6
6
  const Pool = require('./pool')
7
7
  const Client = require('./client')
@@ -35,7 +35,7 @@ class Agent extends DispatcherBase {
35
35
  throw new InvalidArgumentError('maxOrigins must be a number greater than 0')
36
36
  }
37
37
 
38
- super()
38
+ super(options)
39
39
 
40
40
  if (connect && typeof connect !== 'function') {
41
41
  connect = { ...connect }
@@ -65,60 +65,74 @@ class Agent extends DispatcherBase {
65
65
 
66
66
  get [kRunning] () {
67
67
  let ret = 0
68
- for (const { dispatcher } of this[kClients].values()) {
68
+ for (const dispatcher of this[kClients].values()) {
69
69
  ret += dispatcher[kRunning]
70
70
  }
71
71
  return ret
72
72
  }
73
73
 
74
74
  [kDispatch] (opts, handler) {
75
- let key
75
+ let origin
76
76
  if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) {
77
- key = String(opts.origin)
77
+ origin = String(opts.origin)
78
78
  } else {
79
79
  throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
80
80
  }
81
81
 
82
- if (this[kOrigins].size >= this[kOptions].maxOrigins && !this[kOrigins].has(key)) {
82
+ const allowH2 = opts.allowH2 ?? this[kOptions].allowH2
83
+ const key = allowH2 === false ? `${origin}#http1-only` : origin
84
+
85
+ if (this[kOrigins].size >= this[kOptions].maxOrigins && !this[kOrigins].has(origin)) {
83
86
  throw new MaxOriginsReachedError()
84
87
  }
85
88
 
86
- const result = this[kClients].get(key)
87
- let dispatcher = result && result.dispatcher
89
+ let dispatcher = this[kClients].get(key)
88
90
  if (!dispatcher) {
89
- const closeClientIfUnused = (connected) => {
90
- const result = this[kClients].get(key)
91
- if (result) {
92
- if (connected) result.count -= 1
93
- if (result.count <= 0) {
94
- this[kClients].delete(key)
95
- if (!result.dispatcher.destroyed) {
96
- result.dispatcher.close()
97
- }
91
+ dispatcher = this[kFactory](opts.origin, allowH2 === false
92
+ ? { ...this[kOptions], allowH2: false }
93
+ : this[kOptions])
94
+
95
+ const closeClientIfUnused = () => {
96
+ if (this[kClients].get(key) !== dispatcher) {
97
+ return
98
+ }
99
+
100
+ if (dispatcher[kConnected] > 0 || dispatcher[kBusy]) {
101
+ return
102
+ }
103
+
104
+ this[kClients].delete(key)
105
+ if (!dispatcher.destroyed) {
106
+ dispatcher.close()
107
+ }
108
+
109
+ let hasOrigin = false
110
+ for (const client of this[kClients].values()) {
111
+ if (client[kUrl].origin === dispatcher[kUrl].origin) {
112
+ hasOrigin = true
113
+ break
98
114
  }
99
- this[kOrigins].delete(key)
115
+ }
116
+
117
+ if (!hasOrigin) {
118
+ this[kOrigins].delete(dispatcher[kUrl].origin)
100
119
  }
101
120
  }
102
- dispatcher = this[kFactory](opts.origin, this[kOptions])
121
+
122
+ dispatcher
103
123
  .on('drain', this[kOnDrain])
104
- .on('connect', (origin, targets) => {
105
- const result = this[kClients].get(key)
106
- if (result) {
107
- result.count += 1
108
- }
109
- this[kOnConnect](origin, targets)
110
- })
124
+ .on('connect', this[kOnConnect])
111
125
  .on('disconnect', (origin, targets, err) => {
112
- closeClientIfUnused(true)
126
+ closeClientIfUnused()
113
127
  this[kOnDisconnect](origin, targets, err)
114
128
  })
115
129
  .on('connectionError', (origin, targets, err) => {
116
- closeClientIfUnused(false)
130
+ closeClientIfUnused()
117
131
  this[kOnConnectionError](origin, targets, err)
118
132
  })
119
133
 
120
- this[kClients].set(key, { count: 0, dispatcher })
121
- this[kOrigins].add(key)
134
+ this[kClients].set(key, dispatcher)
135
+ this[kOrigins].add(origin)
122
136
  }
123
137
 
124
138
  return dispatcher.dispatch(opts, handler)
@@ -126,7 +140,7 @@ class Agent extends DispatcherBase {
126
140
 
127
141
  [kClose] () {
128
142
  const closePromises = []
129
- for (const { dispatcher } of this[kClients].values()) {
143
+ for (const dispatcher of this[kClients].values()) {
130
144
  closePromises.push(dispatcher.close())
131
145
  }
132
146
  this[kClients].clear()
@@ -136,7 +150,7 @@ class Agent extends DispatcherBase {
136
150
 
137
151
  [kDestroy] (err) {
138
152
  const destroyPromises = []
139
- for (const { dispatcher } of this[kClients].values()) {
153
+ for (const dispatcher of this[kClients].values()) {
140
154
  destroyPromises.push(dispatcher.destroy(err))
141
155
  }
142
156
  this[kClients].clear()
@@ -146,7 +160,7 @@ class Agent extends DispatcherBase {
146
160
 
147
161
  get stats () {
148
162
  const allClientStats = {}
149
- for (const { dispatcher } of this[kClients].values()) {
163
+ for (const dispatcher of this[kClients].values()) {
150
164
  if (dispatcher.stats) {
151
165
  allClientStats[dispatcher[kUrl].origin] = dispatcher.stats
152
166
  }
@@ -57,9 +57,6 @@ class BalancedPool extends PoolBase {
57
57
  super()
58
58
 
59
59
  this[kOptions] = { ...util.deepClone(opts) }
60
- this[kOptions].interceptors = opts.interceptors
61
- ? { ...opts.interceptors }
62
- : undefined
63
60
  this[kIndex] = -1
64
61
  this[kCurrentWeight] = 0
65
62
 
@@ -167,35 +164,14 @@ class BalancedPool extends PoolBase {
167
164
  throw new BalancedPoolMissingUpstreamError()
168
165
  }
169
166
 
170
- const dispatcher = this[kClients].find(dispatcher => (
171
- !dispatcher[kNeedDrain] &&
172
- dispatcher.closed !== true &&
173
- dispatcher.destroyed !== true
174
- ))
175
-
176
- if (!dispatcher) {
177
- return
178
- }
179
-
180
- const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true)
181
-
182
- if (allClientsBusy) {
183
- return
184
- }
185
-
186
167
  let counter = 0
187
168
 
188
- let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain])
169
+ let maxWeightIndex = -1
189
170
 
190
171
  while (counter++ < this[kClients].length) {
191
172
  this[kIndex] = (this[kIndex] + 1) % this[kClients].length
192
173
  const pool = this[kClients][this[kIndex]]
193
174
 
194
- // find pool index with the largest weight
195
- if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) {
196
- maxWeightIndex = this[kIndex]
197
- }
198
-
199
175
  // decrease the current weight every `this[kClients].length`.
200
176
  if (this[kIndex] === 0) {
201
177
  // Set the current weight to the next lower weight.
@@ -205,11 +181,30 @@ class BalancedPool extends PoolBase {
205
181
  this[kCurrentWeight] = this[kMaxWeightPerServer]
206
182
  }
207
183
  }
208
- if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) {
184
+
185
+ // Skip unavailable pools after updating the current weight for this cycle.
186
+ if (
187
+ pool[kNeedDrain] ||
188
+ pool.closed === true ||
189
+ pool.destroyed === true
190
+ ) {
191
+ continue
192
+ }
193
+
194
+ // Track the best fallback if no pool matches the current weight.
195
+ if (maxWeightIndex === -1 || pool[kWeight] > this[kClients][maxWeightIndex][kWeight]) {
196
+ maxWeightIndex = this[kIndex]
197
+ }
198
+
199
+ if (pool[kWeight] >= this[kCurrentWeight]) {
209
200
  return pool
210
201
  }
211
202
  }
212
203
 
204
+ if (maxWeightIndex === -1) {
205
+ return
206
+ }
207
+
213
208
  this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight]
214
209
  this[kIndex] = maxWeightIndex
215
210
  return this[kClients][maxWeightIndex]