@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
@@ -23,6 +23,49 @@ const COLON = 0x3A
23
23
  */
24
24
  const SPACE = 0x20
25
25
 
26
+ const DATA = Buffer.from('data')
27
+ const EVENT = Buffer.from('event')
28
+ const ID = Buffer.from('id')
29
+ const RETRY = Buffer.from('retry')
30
+
31
+ function isASCIINumberBytes (buffer, start) {
32
+ if (start >= buffer.length) {
33
+ return false
34
+ }
35
+
36
+ for (let i = start; i < buffer.length; i++) {
37
+ if (buffer[i] < 0x30 || buffer[i] > 0x39) {
38
+ return false
39
+ }
40
+ }
41
+
42
+ return true
43
+ }
44
+
45
+ function isValidLastEventIdBytes (buffer, start) {
46
+ for (let i = start; i < buffer.length; i++) {
47
+ if (buffer[i] === 0x00) {
48
+ return false
49
+ }
50
+ }
51
+
52
+ return true
53
+ }
54
+
55
+ function isFieldName (line, length, field) {
56
+ if (length !== field.length) {
57
+ return false
58
+ }
59
+
60
+ for (let i = 0; i < length; i++) {
61
+ if (line[i] !== field[i]) {
62
+ return false
63
+ }
64
+ }
65
+
66
+ return true
67
+ }
68
+
26
69
  /**
27
70
  * @typedef {object} EventSourceStreamEvent
28
71
  * @type {object}
@@ -63,11 +106,14 @@ class EventSourceStream extends Transform {
63
106
  eventEndCheck = false
64
107
 
65
108
  /**
66
- * @type {Buffer|null}
109
+ * @type {Buffer[]}
67
110
  */
68
- buffer = null
111
+ chunks = []
69
112
 
113
+ chunkIndex = 0
70
114
  pos = 0
115
+ lineChunkIndex = 0
116
+ linePos = 0
71
117
 
72
118
  event = {
73
119
  data: undefined,
@@ -107,92 +153,20 @@ class EventSourceStream extends Transform {
107
153
  return
108
154
  }
109
155
 
110
- // Cache the chunk in the buffer, as the data might not be complete while
111
- // processing it
112
- // TODO: Investigate if there is a more performant way to handle
113
- // incoming chunks
114
- // see: https://github.com/nodejs/undici/issues/2630
115
- if (this.buffer) {
116
- this.buffer = Buffer.concat([this.buffer, chunk])
117
- } else {
118
- this.buffer = chunk
119
- }
156
+ this.chunks.push(chunk)
120
157
 
121
158
  // Strip leading byte-order-mark if we opened the stream and started
122
159
  // the processing of the incoming data
123
160
  if (this.checkBOM) {
124
- switch (this.buffer.length) {
125
- case 1:
126
- // Check if the first byte is the same as the first byte of the BOM
127
- if (this.buffer[0] === BOM[0]) {
128
- // If it is, we need to wait for more data
129
- callback()
130
- return
131
- }
132
- // Set the checkBOM flag to false as we don't need to check for the
133
- // BOM anymore
134
- this.checkBOM = false
135
-
136
- // The buffer only contains one byte so we need to wait for more data
137
- callback()
138
- return
139
- case 2:
140
- // Check if the first two bytes are the same as the first two bytes
141
- // of the BOM
142
- if (
143
- this.buffer[0] === BOM[0] &&
144
- this.buffer[1] === BOM[1]
145
- ) {
146
- // If it is, we need to wait for more data, because the third byte
147
- // is needed to determine if it is the BOM or not
148
- callback()
149
- return
150
- }
151
-
152
- // Set the checkBOM flag to false as we don't need to check for the
153
- // BOM anymore
154
- this.checkBOM = false
155
- break
156
- case 3:
157
- // Check if the first three bytes are the same as the first three
158
- // bytes of the BOM
159
- if (
160
- this.buffer[0] === BOM[0] &&
161
- this.buffer[1] === BOM[1] &&
162
- this.buffer[2] === BOM[2]
163
- ) {
164
- // If it is, we can drop the buffered data, as it is only the BOM
165
- this.buffer = Buffer.alloc(0)
166
- // Set the checkBOM flag to false as we don't need to check for the
167
- // BOM anymore
168
- this.checkBOM = false
169
-
170
- // Await more data
171
- callback()
172
- return
173
- }
174
- // If it is not the BOM, we can start processing the data
175
- this.checkBOM = false
176
- break
177
- default:
178
- // The buffer is longer than 3 bytes, so we can drop the BOM if it is
179
- // present
180
- if (
181
- this.buffer[0] === BOM[0] &&
182
- this.buffer[1] === BOM[1] &&
183
- this.buffer[2] === BOM[2]
184
- ) {
185
- // Remove the BOM from the buffer
186
- this.buffer = this.buffer.subarray(3)
187
- }
188
-
189
- // Set the checkBOM flag to false as we don't need to check for the
190
- this.checkBOM = false
191
- break
161
+ if (this.handleBOM()) {
162
+ callback()
163
+ return
192
164
  }
193
165
  }
194
166
 
195
- while (this.pos < this.buffer.length) {
167
+ while (this.hasCurrentByte()) {
168
+ const byte = this.currentByte()
169
+
196
170
  // If the previous line ended with an end-of-line, we need to check
197
171
  // if the next character is also an end-of-line.
198
172
  if (this.eventEndCheck) {
@@ -205,10 +179,9 @@ class EventSourceStream extends Transform {
205
179
  if (this.crlfCheck) {
206
180
  // If the current character is a line feed, we can remove it
207
181
  // from the buffer and reset the crlfCheck flag
208
- if (this.buffer[this.pos] === LF) {
209
- this.buffer = this.buffer.subarray(this.pos + 1)
210
- this.pos = 0
182
+ if (byte === LF) {
211
183
  this.crlfCheck = false
184
+ this.consumeCurrentByte()
212
185
 
213
186
  // It is possible that the line feed is not the end of the
214
187
  // event. We need to check if the next character is an
@@ -224,19 +197,17 @@ class EventSourceStream extends Transform {
224
197
  this.crlfCheck = false
225
198
  }
226
199
 
227
- if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
200
+ if (byte === LF || byte === CR) {
228
201
  // If the current character is a carriage return, we need to
229
202
  // set the crlfCheck flag to true, as we need to check if the
230
203
  // next character is a line feed so we can remove it from the
231
204
  // buffer
232
- if (this.buffer[this.pos] === CR) {
205
+ if (byte === CR) {
233
206
  this.crlfCheck = true
234
207
  }
235
208
 
236
- this.buffer = this.buffer.subarray(this.pos + 1)
237
- this.pos = 0
238
- if (
239
- this.event.data !== undefined || this.event.event || this.event.id !== undefined || this.event.retry) {
209
+ this.consumeCurrentByte()
210
+ if (this.hasPendingEvent()) {
240
211
  this.processEvent(this.event)
241
212
  }
242
213
  this.clearEvent()
@@ -250,22 +221,18 @@ class EventSourceStream extends Transform {
250
221
 
251
222
  // If the current character is an end-of-line, we can process the
252
223
  // line
253
- if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
224
+ if (byte === LF || byte === CR) {
254
225
  // If the current character is a carriage return, we need to
255
226
  // set the crlfCheck flag to true, as we need to check if the
256
227
  // next character is a line feed
257
- if (this.buffer[this.pos] === CR) {
228
+ if (byte === CR) {
258
229
  this.crlfCheck = true
259
230
  }
260
231
 
261
232
  // In any case, we can process the line as we reached an
262
233
  // end-of-line character
263
- this.parseLine(this.buffer.subarray(0, this.pos), this.event)
264
-
265
- // Remove the processed line from the buffer
266
- this.buffer = this.buffer.subarray(this.pos + 1)
267
- // Reset the position as we removed the processed line from the buffer
268
- this.pos = 0
234
+ this.parseLine(this.readLine(), this.event)
235
+ this.consumeCurrentByte()
269
236
  // A line was processed and this could be the end of the event. We need
270
237
  // to check if the next line is empty to determine if the event is
271
238
  // finished.
@@ -273,7 +240,7 @@ class EventSourceStream extends Transform {
273
240
  continue
274
241
  }
275
242
 
276
- this.pos++
243
+ this.advanceCursor()
277
244
  }
278
245
 
279
246
  callback()
@@ -298,64 +265,53 @@ class EventSourceStream extends Transform {
298
265
  return
299
266
  }
300
267
 
301
- let field = ''
302
- let value = ''
268
+ let fieldLength = line.length
269
+ let valueStart = line.length
303
270
 
304
271
  // If the line contains a U+003A COLON character (:)
305
272
  if (colonPosition !== -1) {
306
- // Collect the characters on the line before the first U+003A COLON
307
- // character (:), and let field be that string.
308
- // TODO: Investigate if there is a more performant way to extract the
309
- // field
310
- // see: https://github.com/nodejs/undici/issues/2630
311
- field = line.subarray(0, colonPosition).toString('utf8')
273
+ fieldLength = colonPosition
312
274
 
313
275
  // Collect the characters on the line after the first U+003A COLON
314
276
  // character (:), and let value be that string.
315
277
  // If value starts with a U+0020 SPACE character, remove it from value.
316
- let valueStart = colonPosition + 1
278
+ valueStart = colonPosition + 1
317
279
  if (line[valueStart] === SPACE) {
318
280
  ++valueStart
319
281
  }
320
- // TODO: Investigate if there is a more performant way to extract the
321
- // value
322
- // see: https://github.com/nodejs/undici/issues/2630
323
- value = line.subarray(valueStart).toString('utf8')
324
-
325
- // Otherwise, the string is not empty but does not contain a U+003A COLON
326
- // character (:)
327
- } else {
328
- // Process the field using the steps described below, using the whole
329
- // line as the field name, and the empty string as the field value.
330
- field = line.toString('utf8')
331
- value = ''
332
282
  }
333
283
 
334
- // Modify the event with the field name and value. The value is also
335
- // decoded as UTF-8
336
- switch (field) {
337
- case 'data':
338
- if (event[field] === undefined) {
339
- event[field] = value
340
- } else {
341
- event[field] += `\n${value}`
342
- }
343
- break
344
- case 'retry':
345
- if (isASCIINumber(value)) {
346
- event[field] = value
347
- }
348
- break
349
- case 'id':
350
- if (isValidLastEventId(value)) {
351
- event[field] = value
352
- }
353
- break
354
- case 'event':
355
- if (value.length > 0) {
356
- event[field] = value
357
- }
358
- break
284
+ if (isFieldName(line, fieldLength, DATA)) {
285
+ const value = line.toString('utf8', valueStart)
286
+
287
+ if (event.data === undefined) {
288
+ event.data = value
289
+ } else {
290
+ event.data += `\n${value}`
291
+ }
292
+ return
293
+ }
294
+
295
+ if (isFieldName(line, fieldLength, RETRY)) {
296
+ if (isASCIINumberBytes(line, valueStart)) {
297
+ event.retry = line.toString('utf8', valueStart)
298
+ }
299
+ return
300
+ }
301
+
302
+ if (isFieldName(line, fieldLength, ID)) {
303
+ if (isValidLastEventIdBytes(line, valueStart)) {
304
+ event.id = line.toString('utf8', valueStart)
305
+ }
306
+ return
307
+ }
308
+
309
+ if (isFieldName(line, fieldLength, EVENT)) {
310
+ const value = line.toString('utf8', valueStart)
311
+
312
+ if (value.length > 0) {
313
+ event.event = value
314
+ }
359
315
  }
360
316
  }
361
317
 
@@ -385,12 +341,151 @@ class EventSourceStream extends Transform {
385
341
  }
386
342
 
387
343
  clearEvent () {
388
- this.event = {
389
- data: undefined,
390
- event: undefined,
391
- id: undefined,
392
- retry: undefined
344
+ this.event.data = undefined
345
+ this.event.event = undefined
346
+ this.event.id = undefined
347
+ this.event.retry = undefined
348
+ }
349
+
350
+ hasPendingEvent () {
351
+ return this.event.data !== undefined ||
352
+ this.event.event !== undefined ||
353
+ this.event.id !== undefined ||
354
+ this.event.retry !== undefined
355
+ }
356
+
357
+ hasCurrentByte () {
358
+ return this.chunkIndex < this.chunks.length &&
359
+ this.pos < this.chunks[this.chunkIndex].length
360
+ }
361
+
362
+ currentByte () {
363
+ return this.chunks[this.chunkIndex][this.pos]
364
+ }
365
+
366
+ consumeCurrentByte () {
367
+ this.advanceCursor()
368
+ this.syncLineStartToCursor()
369
+ }
370
+
371
+ advanceCursor () {
372
+ this.pos++
373
+
374
+ while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
375
+ this.chunkIndex++
376
+ this.pos = 0
377
+ }
378
+ }
379
+
380
+ syncLineStartToCursor () {
381
+ this.lineChunkIndex = this.chunkIndex
382
+ this.linePos = this.pos
383
+ this.dropConsumedChunks()
384
+ }
385
+
386
+ dropConsumedChunks () {
387
+ while (this.lineChunkIndex > 0) {
388
+ this.chunks.shift()
389
+ this.lineChunkIndex--
390
+ this.chunkIndex--
391
+ }
392
+
393
+ if (this.chunkIndex === this.chunks.length) {
394
+ this.chunks.length = 0
395
+ this.chunkIndex = 0
396
+ this.pos = 0
397
+ this.lineChunkIndex = 0
398
+ this.linePos = 0
399
+ }
400
+ }
401
+
402
+ readLine () {
403
+ if (this.lineChunkIndex === this.chunkIndex) {
404
+ return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
405
+ }
406
+
407
+ const chunks = []
408
+ let length = 0
409
+
410
+ for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
411
+ const chunk = this.chunks[i]
412
+ const start = i === this.lineChunkIndex ? this.linePos : 0
413
+ const end = i === this.chunkIndex ? this.pos : chunk.length
414
+ const slice = chunk.subarray(start, end)
415
+ length += slice.length
416
+ chunks.push(slice)
417
+ }
418
+
419
+ return Buffer.concat(chunks, length)
420
+ }
421
+
422
+ peekBufferedByte (offset) {
423
+ let chunkIndex = this.lineChunkIndex
424
+ let pos = this.linePos
425
+
426
+ while (chunkIndex < this.chunks.length) {
427
+ const chunk = this.chunks[chunkIndex]
428
+ const remaining = chunk.length - pos
429
+
430
+ if (offset < remaining) {
431
+ return chunk[pos + offset]
432
+ }
433
+
434
+ offset -= remaining
435
+ chunkIndex++
436
+ pos = 0
437
+ }
438
+ }
439
+
440
+ discardLeadingBytes (count) {
441
+ while (count > 0 && this.lineChunkIndex < this.chunks.length) {
442
+ const chunk = this.chunks[this.lineChunkIndex]
443
+ const remaining = chunk.length - this.linePos
444
+
445
+ if (count < remaining) {
446
+ this.linePos += count
447
+ count = 0
448
+ } else {
449
+ count -= remaining
450
+ this.lineChunkIndex++
451
+ this.linePos = 0
452
+ }
453
+ }
454
+
455
+ this.chunkIndex = this.lineChunkIndex
456
+ this.pos = this.linePos
457
+ this.dropConsumedChunks()
458
+ }
459
+
460
+ handleBOM () {
461
+ const first = this.peekBufferedByte(0)
462
+ const second = this.peekBufferedByte(1)
463
+ const third = this.peekBufferedByte(2)
464
+
465
+ if (second === undefined) {
466
+ if (first === BOM[0]) {
467
+ return true
468
+ }
469
+
470
+ this.checkBOM = false
471
+ return true
472
+ }
473
+
474
+ if (third === undefined) {
475
+ if (first === BOM[0] && second === BOM[1]) {
476
+ return true
477
+ }
478
+
479
+ this.checkBOM = false
480
+ return false
393
481
  }
482
+
483
+ if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
484
+ this.discardLeadingBytes(3)
485
+ }
486
+
487
+ this.checkBOM = false
488
+ return !this.hasCurrentByte()
394
489
  }
395
490
  }
396
491
 
@@ -7,21 +7,15 @@ const {
7
7
  fullyReadBody,
8
8
  extractMimeType
9
9
  } = require('./util')
10
- const { FormData, setFormDataState } = require('./formdata')
10
+ const { FormData, setFormDataState, getFormDataBoundary } = require('./formdata')
11
11
  const { webidl } = require('../webidl')
12
12
  const assert = require('node:assert')
13
13
  const { isErrored, isDisturbed } = require('node:stream')
14
14
  const { isUint8Array } = require('node:util/types')
15
15
  const { serializeAMimeType } = require('./data-url')
16
16
  const { multipartFormDataParser } = require('./formdata-parser')
17
- const { createDeferredPromise } = require('../../util/promise')
18
17
  const { parseJSONFromBytes } = require('../infra')
19
18
  const { utf8DecodeBytes } = require('../../encoding')
20
- const { runtimeFeatures } = require('../../util/runtime-features.js')
21
-
22
- const random = runtimeFeatures.has('crypto')
23
- ? require('node:crypto').randomInt
24
- : (max) => Math.floor(Math.random() * max)
25
19
 
26
20
  const textEncoder = new TextEncoder()
27
21
  function noop () {}
@@ -107,7 +101,7 @@ function extractBody (object, keepalive = false) {
107
101
  // Set source to a copy of the bytes held by object.
108
102
  source = webidl.util.getCopyOfBytesHeldByBufferSource(object)
109
103
  } else if (webidl.is.FormData(object)) {
110
- const boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
104
+ const boundary = getFormDataBoundary(object)
111
105
  const prefix = `--${boundary}\r\nContent-Disposition: form-data`
112
106
 
113
107
  /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
@@ -431,7 +425,7 @@ function consumeBody (object, convertBytesToJSValue, instance, getInternalState)
431
425
  }
432
426
 
433
427
  // 2. Let promise be a new promise.
434
- const promise = createDeferredPromise()
428
+ const promise = Promise.withResolvers()
435
429
 
436
430
  // 3. Let errorSteps given error be to reject promise with error.
437
431
  const errorSteps = promise.reject
@@ -204,7 +204,7 @@ function multipartFormDataParser (input, mimeType) {
204
204
  * Parses content-disposition attributes (e.g., name="value" or filename*=utf-8''encoded)
205
205
  * @param {Buffer} input
206
206
  * @param {{ position: number }} position
207
- * @returns {{ name: string, value: string }}
207
+ * @returns {{ name: string, value: string, extended: boolean } | null}
208
208
  */
209
209
  function parseContentDispositionAttribute (input, position) {
210
210
  // Skip leading semicolon and whitespace
@@ -304,7 +304,7 @@ function parseContentDispositionAttribute (input, position) {
304
304
  value = decoder.decode(tokenValue)
305
305
  }
306
306
 
307
- return { name: attrNameStr, value }
307
+ return { name: attrNameStr, value, extended: isExtended }
308
308
  }
309
309
 
310
310
  /**
@@ -368,6 +368,9 @@ function parseMultipartFormDataHeaders (input, position) {
368
368
  switch (bufferToLowerCasedHeaderName(headerName)) {
369
369
  case 'content-disposition': {
370
370
  name = filename = null
371
+ // Track whether filename was set from the extended (RFC 5987) form so
372
+ // a subsequent legacy `filename` attribute does not override it.
373
+ let filenameIsExtended = false
371
374
 
372
375
  // Collect the disposition type (should be "form-data")
373
376
  const dispositionType = collectASequenceOfBytes(
@@ -383,8 +386,8 @@ function parseMultipartFormDataHeaders (input, position) {
383
386
  // Parse attributes recursively until CRLF
384
387
  while (
385
388
  position.position < input.length &&
386
- input[position.position] !== 0x0d &&
387
- input[position.position + 1] !== 0x0a
389
+ (input[position.position] !== 0x0d ||
390
+ input[position.position + 1] !== 0x0a)
388
391
  ) {
389
392
  const attribute = parseContentDispositionAttribute(input, position)
390
393
 
@@ -395,7 +398,15 @@ function parseMultipartFormDataHeaders (input, position) {
395
398
  if (attribute.name === 'name') {
396
399
  name = attribute.value
397
400
  } else if (attribute.name === 'filename') {
398
- filename = attribute.value
401
+ // Per RFC 5987 §4.1, when both legacy and extended forms of the
402
+ // same parameter are present, the extended (filename*) form takes
403
+ // precedence regardless of the order they appear in.
404
+ if (attribute.extended) {
405
+ filename = attribute.value
406
+ filenameIsExtended = true
407
+ } else if (!filenameIsExtended) {
408
+ filename = attribute.value
409
+ }
399
410
  }
400
411
  }
401
412
 
@@ -448,7 +459,7 @@ function parseMultipartFormDataHeaders (input, position) {
448
459
 
449
460
  // 2.9. If position does not point to a sequence of bytes starting with 0x0D 0x0A
450
461
  // (CR LF), return failure. Otherwise, advance position by 2 (past the newline).
451
- if (input[position.position] !== 0x0d && input[position.position + 1] !== 0x0a) {
462
+ if (input[position.position] !== 0x0d || input[position.position + 1] !== 0x0a) {
452
463
  throw parsingError('expected CRLF')
453
464
  } else {
454
465
  position.position += 2
@@ -4,10 +4,16 @@ const { iteratorMixin } = require('./util')
4
4
  const { kEnumerableProperty } = require('../../core/util')
5
5
  const { webidl } = require('../webidl')
6
6
  const nodeUtil = require('node:util')
7
+ const { runtimeFeatures } = require('../../util/runtime-features.js')
8
+
9
+ const random = runtimeFeatures.has('crypto')
10
+ ? require('node:crypto').randomInt
11
+ : (max) => Math.floor(Math.random() * max)
7
12
 
8
13
  // https://xhr.spec.whatwg.org/#formdata
9
14
  class FormData {
10
15
  #state = []
16
+ #boundary = null
11
17
 
12
18
  constructor (form = undefined) {
13
19
  webidl.util.markAsUncloneable(this)
@@ -192,11 +198,24 @@ class FormData {
192
198
  static setFormDataState (formData, newState) {
193
199
  formData.#state = newState
194
200
  }
201
+
202
+ /**
203
+ * @param {FormData} formData
204
+ * @returns {string | null}
205
+ */
206
+ static getFormDataBoundary (formData) {
207
+ const boundary = formData.#boundary
208
+ if (boundary != null) return boundary
209
+
210
+ // eslint-disable-next-line no-return-assign
211
+ return formData.#boundary = `----formdata-undici-0${`${random(1e11)}`.padStart(11, '0')}`
212
+ }
195
213
  }
196
214
 
197
- const { getFormDataState, setFormDataState } = FormData
215
+ const { getFormDataState, setFormDataState, getFormDataBoundary } = FormData
198
216
  Reflect.deleteProperty(FormData, 'getFormDataState')
199
217
  Reflect.deleteProperty(FormData, 'setFormDataState')
218
+ Reflect.deleteProperty(FormData, 'getFormDataBoundary')
200
219
 
201
220
  iteratorMixin('FormData', FormData, getFormDataState, 'name', 'value')
202
221
 
@@ -256,4 +275,4 @@ function makeEntry (name, value, filename) {
256
275
 
257
276
  webidl.is.FormData = webidl.util.MakeTypeAssertion(FormData)
258
277
 
259
- module.exports = { FormData, makeEntry, setFormDataState }
278
+ module.exports = { FormData, makeEntry, setFormDataState, getFormDataBoundary }