niceeval 0.14.1-canary.51 → 0.14.2
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/INDEX.md +1 -1
- package/dist/agents/codex-app-server.cjs +7 -1
- package/dist/agents/codex-app-server.cjs.map +1 -1
- package/dist/inspection/overview.cjs +7 -3
- package/dist/inspection/overview.cjs.map +1 -1
- package/dist/sandbox/incus/control.cjs +28 -1
- package/dist/sandbox/incus/control.cjs.map +1 -1
- package/dist/show/render.cjs +28 -15
- package/dist/show/render.cjs.map +1 -1
- package/dist/view/function-runtime.mjs +2 -2
- package/docs-site/zh/reference/cli.mdx +4 -2
- package/docs-site/zh/troubleshooting/debugging.mdx +20 -1
- package/docs-site/zh/tutorials/criteria-files.mdx +2 -0
- package/docs-site/zh/tutorials/evaluation-kinds.mdx +1 -1
- package/docs-site/zh/tutorials/rerun-and-cache.mdx +3 -1
- package/docs-site/zh/tutorials/viewing-results.mdx +43 -4
- package/node_modules/undici/docs/docs/api/Client.md +4 -0
- package/node_modules/undici/docs/docs/api/EnvHttpProxyAgent.md +3 -2
- package/node_modules/undici/docs/docs/api/EventSource.md +4 -0
- package/node_modules/undici/lib/cache/memory-cache-store.js +1 -1
- package/node_modules/undici/lib/dispatcher/agent.js +7 -2
- package/node_modules/undici/lib/dispatcher/balanced-pool.js +1 -1
- package/node_modules/undici/lib/dispatcher/client-h1.js +12 -4
- package/node_modules/undici/lib/dispatcher/client-h2.js +43 -3
- package/node_modules/undici/lib/dispatcher/client.js +3 -2
- package/node_modules/undici/lib/dispatcher/dispatcher-base.js +13 -1
- package/node_modules/undici/lib/dispatcher/dispatcher.js +10 -0
- package/node_modules/undici/lib/dispatcher/env-http-proxy-agent.js +10 -3
- package/node_modules/undici/lib/dispatcher/proxy-agent.js +1 -1
- package/node_modules/undici/lib/dispatcher/round-robin-pool.js +1 -1
- package/node_modules/undici/lib/dispatcher/socks5-proxy-agent.js +3 -1
- package/node_modules/undici/lib/handler/cache-handler.js +8 -0
- package/node_modules/undici/lib/handler/decorator-handler.js +7 -1
- package/node_modules/undici/lib/handler/deduplication-handler.js +12 -6
- package/node_modules/undici/lib/handler/redirect-handler.js +8 -0
- package/node_modules/undici/lib/handler/retry-handler.js +83 -11
- package/node_modules/undici/lib/interceptor/dump.js +1 -1
- package/node_modules/undici/lib/mock/mock-agent.js +13 -1
- package/node_modules/undici/lib/mock/mock-utils.js +11 -10
- package/node_modules/undici/lib/web/cookies/parse.js +4 -2
- package/node_modules/undici/lib/web/eventsource/eventsource-stream.js +28 -1
- package/node_modules/undici/lib/web/eventsource/eventsource.js +4 -1
- package/node_modules/undici/lib/web/fetch/request.js +2 -3
- package/node_modules/undici/package.json +3 -3
- package/node_modules/undici/types/client.d.ts +10 -0
- package/package.json +4 -4
|
@@ -51,7 +51,7 @@ checkout cart/add-item ordinal 1 execute: input-identity-changed
|
|
|
51
51
|
# 默认采用符合条件的 passed 与 failed Attempt
|
|
52
52
|
npx niceeval exp checkout
|
|
53
53
|
|
|
54
|
-
#
|
|
54
|
+
# 单独写 --rerun 等价于 --rerun failed;已经 passed/scored 的 slot 仍会采用
|
|
55
55
|
npx niceeval exp checkout --rerun
|
|
56
56
|
|
|
57
57
|
# 重新执行选中范围内的全部 slot
|
|
@@ -60,6 +60,8 @@ npx niceeval exp checkout --rerun all
|
|
|
60
60
|
|
|
61
61
|
改变 `--rerun` 只影响本次 Invocation,不会修改已有 Run 或 Attempt。
|
|
62
62
|
|
|
63
|
+
不要在 `exp` 仍运行时并发执行 `show --experiment`:Experiment 可能尚未产生任何已发布结果。等待命令完成,从 receipt 复制本次 `runIds`,再用 `niceeval show --run <run-id>` 审阅确定的 Run。若目的是重做所有已通过或已评分的 slot,必须明确写 `--rerun all`;不带值的 `--rerun` 不会做到这一点。
|
|
64
|
+
|
|
63
65
|
## 明确接受已有 Attempt
|
|
64
66
|
|
|
65
67
|
身份变化不一定表示历史结果已经失效,但 NiceEval 不会替操作者猜测。先从 dry run 或结果页取得完整 locator,核对候选 Attempt;确认它在新配置下仍然成立时,明确接受它:
|
|
@@ -12,7 +12,7 @@ description: "在终端用 show 逐层查看结果;在浏览器用 View 审阅
|
|
|
12
12
|
niceeval show
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
不带 selector 时,`show` 输出当前 PublicationCutoff 的 Overview。它按 Experiment、评估用例和 Attempt
|
|
15
|
+
不带 selector 时,`show` 输出当前 PublicationCutoff 的 Overview。它按 Experiment、评估用例和 Attempt 展开汇总,并保留适用的通过率或分数、coverage、缺失项和 issues。纯计分制显示 `Outcomes`、`scored` 与 `Score`,不显示 `passed` 或通过率。先从这里找出需要继续查看的 Experiment、Run 或 Attempt locator。
|
|
16
16
|
|
|
17
17
|
Overview 来自项目唯一的 `.niceeval/record.sqlite` 中已发布的结果。即使当前源码或当前安装候选的 identity 已经改变,
|
|
18
18
|
`show` 也会保留每个逻辑 slot 的最新结果,不会把存在的历史显示为 `Observed 0/0`。这表示结果可查看,
|
|
@@ -51,6 +51,45 @@ niceeval show @<locator> --execution --expand <stable-id>
|
|
|
51
51
|
|
|
52
52
|
`--timing` 显示活动的顺序和耗时,`--usage` 显示 token、请求和成本总计,`--diff` 显示已捕获的文件变更窗口。详情只读取 Record 已发布的事实;`partial`、`not-recorded` 或 `unavailable` 表示实际可用范围,不能当作零值或失败。
|
|
53
53
|
|
|
54
|
+
默认 Overview 会隐藏没有问题的 Attempt,让错误和下一条下钻命令留在一屏内。看到 `Attempts hidden` 时,使用输出建议的 `show --experiment <experiment-id>` 缩小范围;需要显示该范围的全部 Attempt 时再加 `--all`。隐藏只影响终端排版,不表示 Record 缺少结果。
|
|
55
|
+
|
|
56
|
+
## 从 Show 进入 Assertion 详情
|
|
57
|
+
|
|
58
|
+
`show @<locator>` 适合先找出 Assertion 名称和 Entry ID,但不展开每项 contribution、measurement 或 Judge rationale。需要解释具体得分或失败原因时,转到结构化 `query`,不要解析 `show` 的终端文本。
|
|
59
|
+
|
|
60
|
+
先动态发现当前版本支持的 operation ID,再取得该 Attempt 的 Assertion 清单。`discover` 是紧凑目录,不包含 request schema;下面的请求就是 `attempt.get` 的最小形式,可先交给 `query explain` 校验:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
niceeval query discover
|
|
64
|
+
|
|
65
|
+
niceeval query run --request - <<'JSON'
|
|
66
|
+
{
|
|
67
|
+
"protocol": "niceeval.query/v1",
|
|
68
|
+
"operation": {
|
|
69
|
+
"kind": "attempt.get",
|
|
70
|
+
"locator": "@<locator>"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
JSON
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
从返回的 `assertions.entries` 选择一个 `entryId`,再读取这一项:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
niceeval query run --request - <<'JSON'
|
|
80
|
+
{
|
|
81
|
+
"protocol": "niceeval.query/v1",
|
|
82
|
+
"operation": {
|
|
83
|
+
"kind": "attempt.assertion.detail",
|
|
84
|
+
"locator": "@<locator>",
|
|
85
|
+
"entryId": "<entry-id>"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
JSON
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
详情中的 contribution 说明这一项获得多少分,evaluation 保存实际测量结果,explanation retention 则说明 Judge rationale 等解释材料是否可用。字段不存在或状态为 `unavailable` 时,按返回的 reason 解释,不能从其它字段猜值。
|
|
92
|
+
|
|
54
93
|
## 正确解释完成、判定与分数
|
|
55
94
|
|
|
56
95
|
先确认 Attempt 是否形成业务结果,再解释它的质量:
|
|
@@ -58,9 +97,9 @@ niceeval show @<locator> --execution --expand <stable-id>
|
|
|
58
97
|
- `errored`:执行链中断,没有形成可评分的业务结论。
|
|
59
98
|
- `failed`:执行完成,但通过制评估的业务断言没有满足。
|
|
60
99
|
- `passed`:通过制评估满足了判定条件。仍需检查 warning、coverage 和证据限制。
|
|
61
|
-
- Score Eval:评分过程完成后读取 `Score` 和逐项
|
|
100
|
+
- Score Eval:评分过程完成后读取 `Score` 和逐项 contribution。计分制没有 Attempt Verdict,不得用 `passed` 或通过率解释质量;执行中断时只报告 `errored` 和可用的 partial score。
|
|
62
101
|
|
|
63
|
-
|
|
102
|
+
比较结果时使用同一种评估、相同分母和相同证据覆盖。不要把 `unavailable` 当作零分。需要解释一项得失时,固定同一个 Attempt locator,交叉查看 overview、`--execution`、`--source` 与 `attempt.assertion.detail`。
|
|
64
103
|
|
|
65
104
|
## 在浏览器中审阅
|
|
66
105
|
|
|
@@ -83,7 +122,7 @@ niceeval query explain --request request.json
|
|
|
83
122
|
niceeval query run --request request.json
|
|
84
123
|
```
|
|
85
124
|
|
|
86
|
-
`query` 是给 AI、脚本和 CI 使用的机器入口,只输出 `niceeval.query/v1` 的结构化 JSON,不提供适合人阅读的终端排版。先从 `discover`
|
|
125
|
+
`query` 是给 AI、脚本和 CI 使用的机器入口,只输出 `niceeval.query/v1` 的结构化 JSON,不提供适合人阅读的终端排版。先从 `discover` 返回的紧凑 catalog 确认 operation ID,再按文档中的最小请求构造 request,并用 `explain` 校验。`run` 向 stdout 写一个 `niceeval.query/v1` document。不要解析 View 页面,也不要使用未公开的 Record 文件。
|
|
87
126
|
|
|
88
127
|
常见读取任务由固定 operation 覆盖:列出 Run、取得 Run summary、读取 Attempt detail、trace、diff、sources、artifacts 或比较两个 Run。查看 [CLI](/zh/reference/cli) 获取完整说明。
|
|
89
128
|
|
|
@@ -147,6 +147,10 @@ added: v1.0.0
|
|
|
147
147
|
WebSocket messages. Applied to uncompressed messages, compressed frame
|
|
148
148
|
payloads, and decompressed (`permessage-deflate`) messages. Set to `0` to
|
|
149
149
|
disable the limit. **Default:** `134217728`.
|
|
150
|
+
* `eventSource` {Object} (optional) EventSource-specific configuration.
|
|
151
|
+
* `maxEventSize` {number} The maximum allowed event size, in bytes, for
|
|
152
|
+
EventSource messages. Set to `0` to disable the limit.
|
|
153
|
+
**Default:** `buffer.kStringMaxLength`.
|
|
150
154
|
* Returns: {Client}
|
|
151
155
|
|
|
152
156
|
Instantiating a `Client` does not open a connection; the connection is
|
|
@@ -25,8 +25,9 @@ it is used only for HTTPS requests.
|
|
|
25
25
|
proxied. Each entry may include a leading dot or `*.` wildcard (for example
|
|
26
26
|
`.example.com`) to match subdomains, and an optional `:port` suffix to restrict
|
|
27
27
|
the match to a specific port. A request bypasses the proxy when its host equals
|
|
28
|
-
an entry or is a subdomain of one.
|
|
29
|
-
|
|
28
|
+
an entry or is a subdomain of one. A trailing dot is ignored on both sides, so
|
|
29
|
+
`example.com.` and `example.com` match each other. Setting `no_proxy` to `*`
|
|
30
|
+
bypasses the proxy for every request.
|
|
30
31
|
|
|
31
32
|
The uppercase variants `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` are also
|
|
32
33
|
honored. When both the lowercase and uppercase forms of a variable are set, the
|
|
@@ -66,6 +66,9 @@ added: v6.5.0
|
|
|
66
66
|
wait before re-establishing a dropped connection. The server may override
|
|
67
67
|
this value with a `retry` field. **Default:** `3000`.
|
|
68
68
|
|
|
69
|
+
EventSource-specific limits can be configured on the dispatcher using the
|
|
70
|
+
`eventSource` option. See [`Client`][] for details.
|
|
71
|
+
|
|
69
72
|
Creates a new `EventSource` and immediately begins connecting to `url`. The
|
|
70
73
|
request is sent with the `Accept: text/event-stream` header, a cache mode of
|
|
71
74
|
`no-store`, and an initiator type of `other`.
|
|
@@ -349,6 +352,7 @@ eventSource.onerror = () => {
|
|
|
349
352
|
```
|
|
350
353
|
|
|
351
354
|
[WHATWG-conformant]: https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events
|
|
355
|
+
[`Client`]: Client.md#new-clienturl-options
|
|
352
356
|
[`Dispatcher`]: Dispatcher.md#class-dispatcher
|
|
353
357
|
[`addEventListener()`]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
|
|
354
358
|
[server-sent events]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
|
|
@@ -179,7 +179,7 @@ class MemoryCacheStore extends EventEmitter {
|
|
|
179
179
|
|
|
180
180
|
// Perform eviction
|
|
181
181
|
for (const [key, entries] of store.#entries) {
|
|
182
|
-
for (const entry of entries.splice(0, entries.length / 2)) {
|
|
182
|
+
for (const entry of entries.splice(0, Math.ceil(entries.length / 2))) {
|
|
183
183
|
store.#size -= entry.size
|
|
184
184
|
store.#count -= 1
|
|
185
185
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { InvalidArgumentError, MaxOriginsReachedError } = require('../core/errors')
|
|
4
|
-
const { kBusy, kClients, kConnected, kRunning, kClose, kDestroy, kDispatch, kUrl } = require('../core/symbols')
|
|
4
|
+
const { kBusy, kClients, kConnected, kRunning, kPending, 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')
|
|
@@ -97,7 +97,12 @@ class Agent extends DispatcherBase {
|
|
|
97
97
|
return
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
// A GOAWAY detaches the HTTP/2 session before requeued requests are
|
|
101
|
+
// dispatched on a replacement connection. At that point the pool has
|
|
102
|
+
// no connected clients and is not busy, but it still has pending work.
|
|
103
|
+
// Closing it here lets the replacement Client finish those requests
|
|
104
|
+
// and then destroys that new connection with ClientDestroyedError.
|
|
105
|
+
if (dispatcher[kConnected] > 0 || dispatcher[kBusy] || dispatcher[kPending] > 0) {
|
|
101
106
|
return
|
|
102
107
|
}
|
|
103
108
|
|
|
@@ -1052,7 +1052,7 @@ function onSocketClose () {
|
|
|
1052
1052
|
|
|
1053
1053
|
function clearIdleSocketValidation (socket) {
|
|
1054
1054
|
if (socket[kIdleSocketValidationTimeout]) {
|
|
1055
|
-
|
|
1055
|
+
clearImmediate(socket[kIdleSocketValidationTimeout])
|
|
1056
1056
|
socket[kIdleSocketValidationTimeout] = null
|
|
1057
1057
|
}
|
|
1058
1058
|
|
|
@@ -1061,15 +1061,23 @@ function clearIdleSocketValidation (socket) {
|
|
|
1061
1061
|
|
|
1062
1062
|
function scheduleIdleSocketValidation (client, socket) {
|
|
1063
1063
|
socket[kIdleSocketValidation] = 1
|
|
1064
|
-
|
|
1064
|
+
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
|
|
1065
|
+
// already pending on this idle keep-alive socket are processed before the
|
|
1066
|
+
// next request is written (GHSA-35p6-xmwp-9g52).
|
|
1067
|
+
//
|
|
1068
|
+
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
|
|
1069
|
+
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
|
|
1070
|
+
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
|
|
1071
|
+
// A ref'd Immediate both keeps the pending request alive and makes poll
|
|
1072
|
+
// return immediately — the hybrid those issues asked for.
|
|
1073
|
+
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
|
|
1065
1074
|
socket[kIdleSocketValidationTimeout] = null
|
|
1066
1075
|
socket[kIdleSocketValidation] = 2
|
|
1067
1076
|
|
|
1068
1077
|
if (client[kSocket] === socket && !socket.destroyed) {
|
|
1069
1078
|
client[kResume]()
|
|
1070
1079
|
}
|
|
1071
|
-
}
|
|
1072
|
-
socket[kIdleSocketValidationTimeout].unref?.()
|
|
1080
|
+
})
|
|
1073
1081
|
}
|
|
1074
1082
|
|
|
1075
1083
|
/**
|
|
@@ -10,7 +10,8 @@ const {
|
|
|
10
10
|
InformationalError,
|
|
11
11
|
InvalidArgumentError,
|
|
12
12
|
HeadersTimeoutError,
|
|
13
|
-
BodyTimeoutError
|
|
13
|
+
BodyTimeoutError,
|
|
14
|
+
ResponseExceededMaxSizeError
|
|
14
15
|
} = require('../core/errors.js')
|
|
15
16
|
const {
|
|
16
17
|
kUrl,
|
|
@@ -39,7 +40,8 @@ const {
|
|
|
39
40
|
kRemoteSettings,
|
|
40
41
|
kHTTP2Stream,
|
|
41
42
|
kHTTP2SessionState,
|
|
42
|
-
kHTTP2Options
|
|
43
|
+
kHTTP2Options,
|
|
44
|
+
kMaxResponseSize
|
|
43
45
|
} = require('../core/symbols.js')
|
|
44
46
|
const { channels } = require('../core/diagnostics.js')
|
|
45
47
|
|
|
@@ -1022,9 +1024,11 @@ function writeH2 (client, request) {
|
|
|
1022
1024
|
const state = {
|
|
1023
1025
|
abort: null,
|
|
1024
1026
|
body: request.body,
|
|
1027
|
+
bytesRead: 0,
|
|
1025
1028
|
client,
|
|
1026
1029
|
contentLength: null,
|
|
1027
1030
|
expectsPayload: false,
|
|
1031
|
+
maxResponseSize: client[kMaxResponseSize],
|
|
1028
1032
|
request,
|
|
1029
1033
|
headersTimeout,
|
|
1030
1034
|
bodyTimeout,
|
|
@@ -1260,6 +1264,7 @@ function writeH2 (client, request) {
|
|
|
1260
1264
|
// become unreachable once the stream closes, so plain `on` avoids the
|
|
1261
1265
|
// per-listener `once` wrapper allocation.
|
|
1262
1266
|
stream.on('response', onResponse)
|
|
1267
|
+
stream.on('headers', onInterimResponse)
|
|
1263
1268
|
stream.on('end', onEnd)
|
|
1264
1269
|
stream.on('error', onError)
|
|
1265
1270
|
stream.on('frameError', onFrameError)
|
|
@@ -1280,6 +1285,7 @@ function removeRequestStreamListeners (stream) {
|
|
|
1280
1285
|
stream.off('error', noop)
|
|
1281
1286
|
stream.off('continue', writeBodyH2)
|
|
1282
1287
|
stream.off('response', onResponse)
|
|
1288
|
+
stream.off('headers', onInterimResponse)
|
|
1283
1289
|
stream.off('end', onEnd)
|
|
1284
1290
|
stream.off('error', onError)
|
|
1285
1291
|
stream.off('frameError', onFrameError)
|
|
@@ -1322,17 +1328,51 @@ function onData (chunk) {
|
|
|
1322
1328
|
return
|
|
1323
1329
|
}
|
|
1324
1330
|
|
|
1325
|
-
const { request } = state
|
|
1331
|
+
const { request, maxResponseSize } = state
|
|
1326
1332
|
|
|
1327
1333
|
if (request.aborted || request.completed) {
|
|
1328
1334
|
return
|
|
1329
1335
|
}
|
|
1330
1336
|
|
|
1337
|
+
if (maxResponseSize > -1 && state.bytesRead + chunk.length > maxResponseSize) {
|
|
1338
|
+
// Unlike HTTP/1.1, which destroys the socket because it cannot abandon one
|
|
1339
|
+
// response without losing framing, resetting the offending stream leaves
|
|
1340
|
+
// the session usable for its siblings.
|
|
1341
|
+
state.abort(new ResponseExceededMaxSizeError())
|
|
1342
|
+
return
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
state.bytesRead += chunk.length
|
|
1346
|
+
|
|
1331
1347
|
if (request.onResponseData(chunk) === false) {
|
|
1332
1348
|
stream.pause()
|
|
1333
1349
|
}
|
|
1334
1350
|
}
|
|
1335
1351
|
|
|
1352
|
+
function onInterimResponse (headers) {
|
|
1353
|
+
const stream = this
|
|
1354
|
+
const state = stream[kRequestStreamState]
|
|
1355
|
+
|
|
1356
|
+
if (state == null) {
|
|
1357
|
+
return
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const { request } = state
|
|
1361
|
+
|
|
1362
|
+
if (request.aborted || request.completed) {
|
|
1363
|
+
return
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
// node http2 emits 'headers' for interim (1xx) informational responses,
|
|
1367
|
+
// while the final response arrives via 'response'. Forward these to the
|
|
1368
|
+
// handler so that onInfo is invoked, matching the HTTP/1 behaviour and the
|
|
1369
|
+
// documented onInfo contract.
|
|
1370
|
+
const statusCode = headers[HTTP2_HEADER_STATUS]
|
|
1371
|
+
delete headers[HTTP2_HEADER_STATUS]
|
|
1372
|
+
|
|
1373
|
+
request.onResponseStart(Number(statusCode), headers, noop, '')
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1336
1376
|
function onResponse (headers) {
|
|
1337
1377
|
const stream = this
|
|
1338
1378
|
const state = stream[kRequestStreamState]
|
|
@@ -137,7 +137,8 @@ class Client extends DispatcherBase {
|
|
|
137
137
|
connectionWindowSize,
|
|
138
138
|
pingInterval,
|
|
139
139
|
webSocket,
|
|
140
|
-
h2Options
|
|
140
|
+
h2Options,
|
|
141
|
+
eventSource
|
|
141
142
|
} = {}) {
|
|
142
143
|
if (keepAlive !== undefined) {
|
|
143
144
|
throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead')
|
|
@@ -276,7 +277,7 @@ class Client extends DispatcherBase {
|
|
|
276
277
|
}
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
super({ webSocket })
|
|
280
|
+
super({ webSocket, eventSource })
|
|
280
281
|
|
|
281
282
|
if (typeof connect !== 'function') {
|
|
282
283
|
connect = buildConnector({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const buffer = require('node:buffer')
|
|
3
4
|
const Dispatcher = require('./dispatcher')
|
|
4
5
|
const {
|
|
5
6
|
ClientDestroyedError,
|
|
@@ -11,6 +12,7 @@ const { kDestroy, kClose, kClosed, kDestroyed, kDispatch } = require('../core/sy
|
|
|
11
12
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
12
13
|
const kOnClosed = Symbol('onClosed')
|
|
13
14
|
const kWebSocketOptions = Symbol('webSocketOptions')
|
|
15
|
+
const kEventSourceOptions = Symbol('eventSourceOptions')
|
|
14
16
|
|
|
15
17
|
class DispatcherBase extends Dispatcher {
|
|
16
18
|
/** @type {boolean} */
|
|
@@ -31,10 +33,11 @@ class DispatcherBase extends Dispatcher {
|
|
|
31
33
|
constructor (opts) {
|
|
32
34
|
super()
|
|
33
35
|
this[kWebSocketOptions] = opts?.webSocket ?? {}
|
|
36
|
+
this[kEventSourceOptions] = opts?.eventSource ?? {}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
|
-
* @returns {import('../../types/
|
|
40
|
+
* @returns {import('../../types/client').Client.WebSocketOptions}
|
|
38
41
|
*/
|
|
39
42
|
get webSocketOptions () {
|
|
40
43
|
return {
|
|
@@ -43,6 +46,15 @@ class DispatcherBase extends Dispatcher {
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* @returns {import('../../types/client').Client.EventSourceOptions}
|
|
51
|
+
*/
|
|
52
|
+
get eventSourceOptions () {
|
|
53
|
+
return {
|
|
54
|
+
maxEventSize: this[kEventSourceOptions].maxEventSize ?? buffer.kStringMaxLength
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
/** @returns {boolean} */
|
|
47
59
|
get destroyed () {
|
|
48
60
|
return this[kDestroyed]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
const EventEmitter = require('node:events')
|
|
3
|
+
const { kUrl } = require('../core/symbols')
|
|
3
4
|
|
|
4
5
|
class Dispatcher extends EventEmitter {
|
|
5
6
|
dispatch () {
|
|
@@ -35,6 +36,15 @@ class Dispatcher extends EventEmitter {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
const originalDispatch = dispatch
|
|
40
|
+
const self = this
|
|
41
|
+
dispatch = function (opts, handler) {
|
|
42
|
+
if (opts && typeof opts === 'object' && !opts.origin && self[kUrl]) {
|
|
43
|
+
opts = Object.assign({}, opts, { origin: self[kUrl].origin })
|
|
44
|
+
}
|
|
45
|
+
return originalDispatch(opts, handler)
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
return new Proxy(this, {
|
|
39
49
|
get: (target, key) => key === 'dispatch' ? dispatch : target[key]
|
|
40
50
|
})
|
|
@@ -16,7 +16,7 @@ class EnvHttpProxyAgent extends DispatcherBase {
|
|
|
16
16
|
#opts = null
|
|
17
17
|
|
|
18
18
|
constructor (opts = {}) {
|
|
19
|
-
super()
|
|
19
|
+
super(opts)
|
|
20
20
|
this.#opts = opts
|
|
21
21
|
|
|
22
22
|
const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts
|
|
@@ -69,6 +69,13 @@ class EnvHttpProxyAgent extends DispatcherBase {
|
|
|
69
69
|
// brackets from IPv6 literals (e.g. "[::1]" -> "::1") so that the
|
|
70
70
|
// result matches the unbracketed form stored by #parseNoProxy.
|
|
71
71
|
hostname = hostname.replace(/:\d*$/, '').replace(/^\[(.+)\]$/, '$1').toLowerCase()
|
|
72
|
+
// Drop a trailing dot: it only marks the fully qualified form of a domain
|
|
73
|
+
// name ("example.com." and "example.com" are the same name, RFC 1034 root
|
|
74
|
+
// label). This runs on every dispatch, so it is a charCode check rather
|
|
75
|
+
// than a third regex. `length > 1` leaves the degenerate host "." alone.
|
|
76
|
+
if (hostname.length > 1 && hostname.charCodeAt(hostname.length - 1) === 46) {
|
|
77
|
+
hostname = hostname.slice(0, -1)
|
|
78
|
+
}
|
|
72
79
|
port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0
|
|
73
80
|
if (!this.#shouldProxy(hostname, port)) {
|
|
74
81
|
return this[kNoProxyAgent]
|
|
@@ -143,8 +150,8 @@ class EnvHttpProxyAgent extends DispatcherBase {
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
noProxyEntries.push({
|
|
146
|
-
// strip leading dot or asterisk with dot
|
|
147
|
-
hostname: hostname.replace(/^\*?\./, '').toLowerCase(),
|
|
153
|
+
// strip leading dot or asterisk with dot, and any trailing dot
|
|
154
|
+
hostname: hostname.replace(/^\*?\./, '').replace(/^(.+)\.$/, '$1').toLowerCase(),
|
|
148
155
|
port
|
|
149
156
|
})
|
|
150
157
|
}
|
|
@@ -120,7 +120,7 @@ class ProxyAgent extends DispatcherBase {
|
|
|
120
120
|
|
|
121
121
|
const { proxyTunnel, connectTimeout } = opts
|
|
122
122
|
|
|
123
|
-
super()
|
|
123
|
+
super(opts)
|
|
124
124
|
|
|
125
125
|
const url = this.#getUrl(opts)
|
|
126
126
|
const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
|
|
@@ -29,7 +29,7 @@ let experimentalWarningEmitted = false
|
|
|
29
29
|
*/
|
|
30
30
|
class Socks5ProxyAgent extends DispatcherBase {
|
|
31
31
|
constructor (proxyUrl, options = {}) {
|
|
32
|
-
super()
|
|
32
|
+
super(options)
|
|
33
33
|
|
|
34
34
|
// Emit experimental warning only once
|
|
35
35
|
if (!experimentalWarningEmitted) {
|
|
@@ -115,6 +115,7 @@ class Socks5ProxyAgent extends DispatcherBase {
|
|
|
115
115
|
const authenticationReady = Promise.withResolvers()
|
|
116
116
|
|
|
117
117
|
const authenticationTimeout = setTimeout(() => {
|
|
118
|
+
socks5Client.destroy()
|
|
118
119
|
authenticationReady.reject(new Error('SOCKS5 authentication timeout'))
|
|
119
120
|
}, 5000)
|
|
120
121
|
|
|
@@ -148,6 +149,7 @@ class Socks5ProxyAgent extends DispatcherBase {
|
|
|
148
149
|
const connectionReady = Promise.withResolvers()
|
|
149
150
|
|
|
150
151
|
const connectionTimeout = setTimeout(() => {
|
|
152
|
+
socks5Client.destroy()
|
|
151
153
|
connectionReady.reject(new Error('SOCKS5 connection timeout'))
|
|
152
154
|
}, 5000)
|
|
153
155
|
|
|
@@ -173,6 +173,14 @@ class CacheHandler {
|
|
|
173
173
|
this.#handler.onRequestStart?.(controller, context)
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
onBodySent (chunk) {
|
|
177
|
+
this.#handler.onBodySent?.(chunk)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
onRequestSent () {
|
|
181
|
+
this.#handler.onRequestSent?.()
|
|
182
|
+
}
|
|
183
|
+
|
|
176
184
|
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
177
185
|
this.#handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
178
186
|
}
|
|
@@ -62,5 +62,11 @@ module.exports = class DecoratorHandler {
|
|
|
62
62
|
/**
|
|
63
63
|
* @deprecated
|
|
64
64
|
*/
|
|
65
|
-
onBodySent () {
|
|
65
|
+
onBodySent (...args) {
|
|
66
|
+
return this.#handler.onBodySent?.(...args)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onRequestSent (...args) {
|
|
70
|
+
return this.#handler.onRequestSent?.(...args)
|
|
71
|
+
}
|
|
66
72
|
}
|
|
@@ -365,12 +365,22 @@ class DeduplicationHandler {
|
|
|
365
365
|
get aborted () { return state.aborted },
|
|
366
366
|
get reason () { return state.reason },
|
|
367
367
|
abort: (reason) => {
|
|
368
|
+
if (state.aborted) {
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
|
|
368
372
|
state.aborted = true
|
|
369
373
|
state.reason = reason ?? null
|
|
370
374
|
waitingHandler.done = true
|
|
371
375
|
waitingHandler.pendingTrailers = null
|
|
372
376
|
waitingHandler.bufferedChunks = []
|
|
373
377
|
waitingHandler.bufferedBytes = 0
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
handler.onResponseError?.(waitingHandler.controller, state.reason ?? new RequestAbortedError())
|
|
381
|
+
} catch {
|
|
382
|
+
// Ignore errors from waiting handlers
|
|
383
|
+
}
|
|
374
384
|
}
|
|
375
385
|
}
|
|
376
386
|
|
|
@@ -444,12 +454,8 @@ class DeduplicationHandler {
|
|
|
444
454
|
waitingHandler.bufferedChunks = []
|
|
445
455
|
waitingHandler.bufferedBytes = 0
|
|
446
456
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
waitingHandler.handler.onResponseError?.(waitingHandler.controller, err)
|
|
450
|
-
} catch {
|
|
451
|
-
// Ignore errors from waiting handlers
|
|
452
|
-
}
|
|
457
|
+
// controller.abort(err) notifies the handler via onResponseError
|
|
458
|
+
waitingHandler.controller.abort(err)
|
|
453
459
|
}
|
|
454
460
|
|
|
455
461
|
#pruneDoneWaitingHandlers () {
|
|
@@ -43,6 +43,14 @@ class RedirectHandler {
|
|
|
43
43
|
this.handler.onRequestStart?.(controller, { ...context, history: this.history })
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
onBodySent (chunk) {
|
|
47
|
+
this.handler.onBodySent?.(chunk)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onRequestSent () {
|
|
51
|
+
this.handler.onRequestSent?.()
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
onRequestUpgrade (controller, statusCode, headers, socket) {
|
|
47
55
|
this.handler.onRequestUpgrade?.(controller, statusCode, headers, socket)
|
|
48
56
|
}
|