@adcp/sdk 16.0.0-beta.2 → 16.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/dist/lib/core/ConversationTypes.d.mts +8 -3
- package/dist/lib/core/ConversationTypes.d.ts +8 -3
- package/dist/lib/core/SingleAgentClient.d.mts +5 -3
- package/dist/lib/core/SingleAgentClient.d.ts +5 -3
- package/dist/lib/core/TaskExecutor.js +5 -1
- package/dist/lib/core/TaskExecutor.mjs +5 -1
- package/dist/lib/errors/index.d.mts +9 -4
- package/dist/lib/errors/index.d.ts +9 -4
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.mjs +7 -1
- package/dist/lib/negotiation/types.d.mts +20 -39
- package/dist/lib/negotiation/types.d.ts +20 -39
- package/dist/lib/protocols/index.d.mts +60 -24
- package/dist/lib/protocols/index.d.ts +60 -24
- package/dist/lib/protocols/index.js +9 -4
- package/dist/lib/protocols/index.mjs +3 -0
- package/dist/lib/protocols/mcp-modern.js +1 -0
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +1 -0
- package/dist/lib/protocols/mcp.mjs +2 -1
- package/dist/lib/protocols/responseSizeLimit.d.mts +91 -5
- package/dist/lib/protocols/responseSizeLimit.d.ts +91 -5
- package/dist/lib/protocols/responseSizeLimit.js +195 -31
- package/dist/lib/protocols/responseSizeLimit.mjs +192 -31
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/crypto.d.mts +15 -0
- package/dist/lib/signing/crypto.d.ts +15 -0
- package/dist/lib/signing/crypto.js +75 -3
- package/dist/lib/signing/crypto.mjs +78 -3
- package/dist/lib/signing/server.d.mts +1 -1
- package/dist/lib/signing/server.d.ts +1 -1
- package/dist/lib/signing/server.js +2 -0
- package/dist/lib/signing/server.mjs +2 -1
- package/dist/lib/signing/signer.js +2 -8
- package/dist/lib/signing/signer.mjs +3 -9
- package/dist/lib/signing/testing.js +2 -6
- package/dist/lib/signing/testing.mjs +3 -7
- package/dist/lib/signing/verifier.js +2 -2
- package/dist/lib/signing/verifier.mjs +3 -3
- package/dist/lib/signing/web-bot-auth.js +20 -7
- package/dist/lib/signing/web-bot-auth.mjs +21 -8
- package/dist/lib/signing/webhook-verifier.js +2 -2
- package/dist/lib/signing/webhook-verifier.mjs +3 -3
- package/dist/lib/substitution/compile.js +2 -1
- package/dist/lib/substitution/compile.mjs +2 -1
- package/dist/lib/substitution/consent-macros.d.mts +4 -0
- package/dist/lib/substitution/consent-macros.d.ts +4 -0
- package/dist/lib/substitution/consent-macros.js +45 -0
- package/dist/lib/substitution/consent-macros.mjs +20 -0
- package/dist/lib/substitution/index.d.mts +1 -1
- package/dist/lib/substitution/index.d.ts +1 -1
- package/dist/lib/substitution/index.js +2 -0
- package/dist/lib/substitution/index.mjs +2 -1
- package/dist/lib/substitution/translate.d.mts +15 -2
- package/dist/lib/substitution/translate.d.ts +15 -2
- package/dist/lib/substitution/translate.js +148 -26
- package/dist/lib/substitution/translate.mjs +147 -26
- package/dist/lib/testing/client.js +2 -1
- package/dist/lib/testing/client.mjs +2 -1
- package/dist/lib/testing/scenarios/capabilities.js +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +2 -5
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +3 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.js +2 -7
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +2 -7
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/enums.generated.d.mts +3 -0
- package/dist/lib/types/enums.generated.d.ts +3 -0
- package/dist/lib/types/enums.generated.js +9 -0
- package/dist/lib/types/enums.generated.mjs +6 -0
- package/dist/lib/validation/schema-loader.js +25 -0
- package/dist/lib/validation/schema-loader.mjs +24 -0
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +6 -1
- package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +3 -2
- package/docs/guides/UNIVERSAL-MACRO-COMPILATION.md +38 -0
- package/docs/guides/VALIDATE-YOUR-AGENT.md +12 -0
- package/docs/llms.txt +1 -1
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +2 -1
|
@@ -18,6 +18,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var responseSizeLimit_exports = {};
|
|
20
20
|
__export(responseSizeLimit_exports, {
|
|
21
|
+
DEFAULT_MAX_RESPONSE_BYTES: () => DEFAULT_MAX_RESPONSE_BYTES,
|
|
22
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER: () => SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
23
|
+
responseSizeCapCacheKey: () => responseSizeCapCacheKey,
|
|
21
24
|
responseSizeLimitStorage: () => responseSizeLimitStorage,
|
|
22
25
|
withResponseSizeLimit: () => withResponseSizeLimit,
|
|
23
26
|
wrapFetchWithSizeLimit: () => wrapFetchWithSizeLimit
|
|
@@ -25,52 +28,116 @@ __export(responseSizeLimit_exports, {
|
|
|
25
28
|
module.exports = __toCommonJS(responseSizeLimit_exports);
|
|
26
29
|
var import_global_async_local_storage = require('../utils/global-async-local-storage.js');
|
|
27
30
|
var import_errors = require('../errors/index.js');
|
|
31
|
+
var import_transportDiagnostics = require('./transportDiagnostics.js');
|
|
32
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
33
|
+
const SSE_CUMULATIVE_BUDGET_MULTIPLIER = 2;
|
|
28
34
|
const responseSizeLimitStorage = (0, import_global_async_local_storage.globalAsyncLocalStorage)("responseSizeLimit");
|
|
35
|
+
function resolveResponseSizeCap(maxResponseBytes) {
|
|
36
|
+
if (maxResponseBytes === void 0) return DEFAULT_MAX_RESPONSE_BYTES;
|
|
37
|
+
if (maxResponseBytes === 0) return null;
|
|
38
|
+
if (!Number.isFinite(maxResponseBytes) || maxResponseBytes < 0) return DEFAULT_MAX_RESPONSE_BYTES;
|
|
39
|
+
return maxResponseBytes;
|
|
40
|
+
}
|
|
41
|
+
function newSlot(maxResponseBytes) {
|
|
42
|
+
const resolved = resolveResponseSizeCap(maxResponseBytes);
|
|
43
|
+
if (resolved === null) return { maxResponseBytes: null };
|
|
44
|
+
const enclosing = responseSizeLimitStorage.getStore();
|
|
45
|
+
const limit = sseBudgetFor(resolved);
|
|
46
|
+
return {
|
|
47
|
+
// Nesting narrows both numbers. `?? resolved` deliberately also covers an
|
|
48
|
+
// enclosing opt-out (`null`): an inner scope that asks for a cap is the
|
|
49
|
+
// more specific instruction, so it still gets one.
|
|
50
|
+
maxResponseBytes: Math.min(resolved, enclosing?.maxResponseBytes ?? resolved),
|
|
51
|
+
streamLimit: Math.min(limit, enclosing?.streamLimit ?? limit),
|
|
52
|
+
streamMeter: enclosing?.streamMeter ?? { consumed: 0 }
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function sseBudgetFor(maxResponseBytes) {
|
|
56
|
+
return Math.min(maxResponseBytes * SSE_CUMULATIVE_BUDGET_MULTIPLIER, Number.MAX_SAFE_INTEGER);
|
|
57
|
+
}
|
|
58
|
+
function responseSizeCapCacheKey() {
|
|
59
|
+
const slot = responseSizeLimitStorage.getStore();
|
|
60
|
+
const cap = slot ? slot.maxResponseBytes : DEFAULT_MAX_RESPONSE_BYTES;
|
|
61
|
+
return `max-response-bytes:${cap === null ? "off" : cap}`;
|
|
62
|
+
}
|
|
29
63
|
function withResponseSizeLimit(maxResponseBytes, fn) {
|
|
30
|
-
if (
|
|
64
|
+
if (maxResponseBytes === void 0 && responseSizeLimitStorage.getStore()) {
|
|
31
65
|
return fn();
|
|
32
66
|
}
|
|
33
|
-
|
|
67
|
+
const slot = newSlot(maxResponseBytes);
|
|
68
|
+
return responseSizeLimitStorage.run(slot, () => rethrowRecordedViolation(slot, fn));
|
|
69
|
+
}
|
|
70
|
+
async function rethrowRecordedViolation(slot, fn) {
|
|
71
|
+
try {
|
|
72
|
+
return await fn();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const violation = slot.violation;
|
|
75
|
+
if (!violation || error instanceof import_errors.ResponseTooLargeError) throw error;
|
|
76
|
+
const surfaced = new import_errors.ResponseTooLargeError(
|
|
77
|
+
violation.limit,
|
|
78
|
+
violation.bytesRead,
|
|
79
|
+
violation.url,
|
|
80
|
+
violation.contentLengthHeader
|
|
81
|
+
);
|
|
82
|
+
Object.defineProperty(surfaced, "cause", { value: error, configurable: true });
|
|
83
|
+
throw surfaced;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function recordViolation(slot, error) {
|
|
87
|
+
if (slot && !slot.violation) slot.violation = error;
|
|
88
|
+
return error;
|
|
34
89
|
}
|
|
35
90
|
function urlOfInput(input) {
|
|
36
91
|
if (typeof input === "string") return input;
|
|
37
92
|
if (input instanceof URL) return input.toString();
|
|
38
93
|
return input.url;
|
|
39
94
|
}
|
|
40
|
-
function redactUrlForError(url) {
|
|
41
|
-
try {
|
|
42
|
-
const parsed = new URL(url);
|
|
43
|
-
parsed.search = "";
|
|
44
|
-
return parsed.toString();
|
|
45
|
-
} catch {
|
|
46
|
-
return url;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function withIdentityEncoding(init) {
|
|
50
|
-
const headers = new Headers(init?.headers);
|
|
51
|
-
if (!headers.has("accept-encoding")) {
|
|
52
|
-
headers.set("accept-encoding", "identity");
|
|
53
|
-
}
|
|
54
|
-
return { ...init ?? {}, headers };
|
|
55
|
-
}
|
|
56
95
|
function wrapFetchWithSizeLimit(upstream) {
|
|
57
96
|
const wrapped = async (input, init) => {
|
|
58
97
|
const slot = responseSizeLimitStorage.getStore();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
const maxBytes = slot ? slot.maxResponseBytes : DEFAULT_MAX_RESPONSE_BYTES;
|
|
99
|
+
if (maxBytes === null) return upstream(input, init);
|
|
100
|
+
const response = await upstream(input, init);
|
|
101
|
+
if (NULL_BODY_STATUSES.has(response.status)) return response;
|
|
102
|
+
const skipDeclaredLength = methodOf(input, init) === "HEAD";
|
|
103
|
+
const url = (0, import_transportDiagnostics.sanitizeTransportUrl)(urlOfInput(input));
|
|
104
|
+
if (isEventStream(response)) {
|
|
105
|
+
const connectionScoped = methodOf(input, init) === "GET";
|
|
106
|
+
const { limit, meter } = streamBudgetFor(connectionScoped, slot, maxBytes);
|
|
107
|
+
return enforceEventStreamLimit(
|
|
108
|
+
response,
|
|
109
|
+
maxBytes,
|
|
110
|
+
limit,
|
|
111
|
+
meter,
|
|
112
|
+
url,
|
|
113
|
+
connectionScoped ? void 0 : slot,
|
|
114
|
+
skipDeclaredLength
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return enforceSizeLimit(response, maxBytes, url, slot, skipDeclaredLength);
|
|
62
118
|
};
|
|
63
119
|
return wrapped;
|
|
64
120
|
}
|
|
65
|
-
function
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
121
|
+
function streamBudgetFor(connectionScoped, slot, maxBytes) {
|
|
122
|
+
if (connectionScoped) return { limit: sseBudgetFor(maxBytes), meter: { consumed: 0 } };
|
|
123
|
+
return {
|
|
124
|
+
limit: slot?.streamLimit ?? sseBudgetFor(maxBytes),
|
|
125
|
+
meter: slot?.streamMeter ?? { consumed: 0 }
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function methodOf(input, init) {
|
|
129
|
+
const method = init?.method ?? (typeof Request !== "undefined" && input instanceof Request ? input.method : "GET");
|
|
130
|
+
return method.toUpperCase();
|
|
131
|
+
}
|
|
132
|
+
const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
133
|
+
function isEventStream(response) {
|
|
134
|
+
return response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream") === true;
|
|
135
|
+
}
|
|
136
|
+
function enforceSizeLimit(response, maxBytes, url, slot, skipDeclaredLength = false) {
|
|
137
|
+
const declared = skipDeclaredLength ? void 0 : parseContentLength(response.headers.get("content-length"));
|
|
70
138
|
if (declared !== void 0 && declared > maxBytes) {
|
|
71
|
-
response
|
|
72
|
-
|
|
73
|
-
throw new import_errors.ResponseTooLargeError(maxBytes, 0, url, declared);
|
|
139
|
+
cancelBody(response);
|
|
140
|
+
throw recordViolation(slot, new import_errors.ResponseTooLargeError(maxBytes, 0, url, declared));
|
|
74
141
|
}
|
|
75
142
|
if (!response.body) return response;
|
|
76
143
|
let bytesRead = 0;
|
|
@@ -78,13 +145,107 @@ function enforceSizeLimit(response, maxBytes, url) {
|
|
|
78
145
|
transform(chunk, controller) {
|
|
79
146
|
bytesRead += chunk.byteLength;
|
|
80
147
|
if (bytesRead > maxBytes) {
|
|
81
|
-
controller.error(new import_errors.ResponseTooLargeError(maxBytes, bytesRead, url));
|
|
148
|
+
controller.error(recordViolation(slot, new import_errors.ResponseTooLargeError(maxBytes, bytesRead, url)));
|
|
82
149
|
return;
|
|
83
150
|
}
|
|
84
151
|
controller.enqueue(chunk);
|
|
85
152
|
}
|
|
86
153
|
});
|
|
87
|
-
return
|
|
154
|
+
return respondWith(response, response.body.pipeThrough(counter));
|
|
155
|
+
}
|
|
156
|
+
const CR = 13;
|
|
157
|
+
const LF = 10;
|
|
158
|
+
function enforceEventStreamLimit(response, maxBytes, streamLimit, meter, url, slot, skipDeclaredLength = false) {
|
|
159
|
+
const remaining = streamLimit - meter.consumed;
|
|
160
|
+
const declared = skipDeclaredLength ? void 0 : parseContentLength(response.headers.get("content-length"));
|
|
161
|
+
if (declared !== void 0 && declared > remaining) {
|
|
162
|
+
cancelBody(response);
|
|
163
|
+
throw recordViolation(slot, new import_errors.ResponseTooLargeError(streamLimit, meter.consumed, url, declared));
|
|
164
|
+
}
|
|
165
|
+
if (!response.body) return response;
|
|
166
|
+
let bytesInEvent = 0;
|
|
167
|
+
let lineEndings = 0;
|
|
168
|
+
let pendingCR = false;
|
|
169
|
+
let separatorTail = false;
|
|
170
|
+
let pendingReset = false;
|
|
171
|
+
const counter = new TransformStream({
|
|
172
|
+
transform(chunk, controller) {
|
|
173
|
+
meter.consumed += chunk.byteLength;
|
|
174
|
+
if (meter.consumed > streamLimit) {
|
|
175
|
+
controller.error(recordViolation(slot, new import_errors.ResponseTooLargeError(streamLimit, meter.consumed, url)));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (chunk.length === 0) {
|
|
179
|
+
controller.enqueue(chunk);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (chunk.indexOf(LF) === -1 && chunk.indexOf(CR) === -1) {
|
|
183
|
+
if (pendingReset) {
|
|
184
|
+
pendingReset = false;
|
|
185
|
+
separatorTail = false;
|
|
186
|
+
bytesInEvent = 0;
|
|
187
|
+
}
|
|
188
|
+
bytesInEvent += chunk.length;
|
|
189
|
+
lineEndings = 0;
|
|
190
|
+
pendingCR = false;
|
|
191
|
+
if (bytesInEvent > maxBytes) {
|
|
192
|
+
controller.error(recordViolation(slot, new import_errors.ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
controller.enqueue(chunk);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
199
|
+
const byte = chunk[i];
|
|
200
|
+
if (pendingReset) {
|
|
201
|
+
pendingReset = false;
|
|
202
|
+
if (separatorTail && byte === LF) {
|
|
203
|
+
separatorTail = false;
|
|
204
|
+
pendingCR = false;
|
|
205
|
+
bytesInEvent += 1;
|
|
206
|
+
if (bytesInEvent > maxBytes) {
|
|
207
|
+
controller.error(recordViolation(slot, new import_errors.ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
bytesInEvent = 0;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
separatorTail = false;
|
|
214
|
+
bytesInEvent = 0;
|
|
215
|
+
}
|
|
216
|
+
bytesInEvent += 1;
|
|
217
|
+
if (byte === CR) {
|
|
218
|
+
lineEndings += 1;
|
|
219
|
+
pendingCR = true;
|
|
220
|
+
} else if (byte === LF) {
|
|
221
|
+
if (!pendingCR) lineEndings += 1;
|
|
222
|
+
pendingCR = false;
|
|
223
|
+
} else {
|
|
224
|
+
lineEndings = 0;
|
|
225
|
+
pendingCR = false;
|
|
226
|
+
}
|
|
227
|
+
if (bytesInEvent > maxBytes) {
|
|
228
|
+
controller.error(recordViolation(slot, new import_errors.ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (lineEndings >= 2) {
|
|
232
|
+
lineEndings = 0;
|
|
233
|
+
pendingReset = true;
|
|
234
|
+
separatorTail = pendingCR;
|
|
235
|
+
pendingCR = false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
controller.enqueue(chunk);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
return respondWith(response, response.body.pipeThrough(counter));
|
|
242
|
+
}
|
|
243
|
+
function cancelBody(response) {
|
|
244
|
+
response.body?.cancel().catch(() => {
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
function respondWith(response, body) {
|
|
248
|
+
return new Response(body, {
|
|
88
249
|
status: response.status,
|
|
89
250
|
statusText: response.statusText,
|
|
90
251
|
headers: response.headers
|
|
@@ -99,6 +260,9 @@ function parseContentLength(value) {
|
|
|
99
260
|
}
|
|
100
261
|
// Annotate the CommonJS export names for ESM import in node:
|
|
101
262
|
0 && (module.exports = {
|
|
263
|
+
DEFAULT_MAX_RESPONSE_BYTES,
|
|
264
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
265
|
+
responseSizeCapCacheKey,
|
|
102
266
|
responseSizeLimitStorage,
|
|
103
267
|
withResponseSizeLimit,
|
|
104
268
|
wrapFetchWithSizeLimit
|
|
@@ -5,52 +5,116 @@ const __dirname = __adcpDirname(__adcpFileURLToPath(import.meta.url));
|
|
|
5
5
|
const require2 = __adcpCreateRequire(import.meta.url);
|
|
6
6
|
import { globalAsyncLocalStorage } from "../utils/global-async-local-storage.mjs";
|
|
7
7
|
import { ResponseTooLargeError } from "../errors/index.mjs";
|
|
8
|
+
import { sanitizeTransportUrl } from "./transportDiagnostics.mjs";
|
|
9
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
10
|
+
const SSE_CUMULATIVE_BUDGET_MULTIPLIER = 2;
|
|
8
11
|
const responseSizeLimitStorage = globalAsyncLocalStorage("responseSizeLimit");
|
|
12
|
+
function resolveResponseSizeCap(maxResponseBytes) {
|
|
13
|
+
if (maxResponseBytes === void 0) return DEFAULT_MAX_RESPONSE_BYTES;
|
|
14
|
+
if (maxResponseBytes === 0) return null;
|
|
15
|
+
if (!Number.isFinite(maxResponseBytes) || maxResponseBytes < 0) return DEFAULT_MAX_RESPONSE_BYTES;
|
|
16
|
+
return maxResponseBytes;
|
|
17
|
+
}
|
|
18
|
+
function newSlot(maxResponseBytes) {
|
|
19
|
+
const resolved = resolveResponseSizeCap(maxResponseBytes);
|
|
20
|
+
if (resolved === null) return { maxResponseBytes: null };
|
|
21
|
+
const enclosing = responseSizeLimitStorage.getStore();
|
|
22
|
+
const limit = sseBudgetFor(resolved);
|
|
23
|
+
return {
|
|
24
|
+
// Nesting narrows both numbers. `?? resolved` deliberately also covers an
|
|
25
|
+
// enclosing opt-out (`null`): an inner scope that asks for a cap is the
|
|
26
|
+
// more specific instruction, so it still gets one.
|
|
27
|
+
maxResponseBytes: Math.min(resolved, enclosing?.maxResponseBytes ?? resolved),
|
|
28
|
+
streamLimit: Math.min(limit, enclosing?.streamLimit ?? limit),
|
|
29
|
+
streamMeter: enclosing?.streamMeter ?? { consumed: 0 }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function sseBudgetFor(maxResponseBytes) {
|
|
33
|
+
return Math.min(maxResponseBytes * SSE_CUMULATIVE_BUDGET_MULTIPLIER, Number.MAX_SAFE_INTEGER);
|
|
34
|
+
}
|
|
35
|
+
function responseSizeCapCacheKey() {
|
|
36
|
+
const slot = responseSizeLimitStorage.getStore();
|
|
37
|
+
const cap = slot ? slot.maxResponseBytes : DEFAULT_MAX_RESPONSE_BYTES;
|
|
38
|
+
return `max-response-bytes:${cap === null ? "off" : cap}`;
|
|
39
|
+
}
|
|
9
40
|
function withResponseSizeLimit(maxResponseBytes, fn) {
|
|
10
|
-
if (
|
|
41
|
+
if (maxResponseBytes === void 0 && responseSizeLimitStorage.getStore()) {
|
|
11
42
|
return fn();
|
|
12
43
|
}
|
|
13
|
-
|
|
44
|
+
const slot = newSlot(maxResponseBytes);
|
|
45
|
+
return responseSizeLimitStorage.run(slot, () => rethrowRecordedViolation(slot, fn));
|
|
46
|
+
}
|
|
47
|
+
async function rethrowRecordedViolation(slot, fn) {
|
|
48
|
+
try {
|
|
49
|
+
return await fn();
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const violation = slot.violation;
|
|
52
|
+
if (!violation || error instanceof ResponseTooLargeError) throw error;
|
|
53
|
+
const surfaced = new ResponseTooLargeError(
|
|
54
|
+
violation.limit,
|
|
55
|
+
violation.bytesRead,
|
|
56
|
+
violation.url,
|
|
57
|
+
violation.contentLengthHeader
|
|
58
|
+
);
|
|
59
|
+
Object.defineProperty(surfaced, "cause", { value: error, configurable: true });
|
|
60
|
+
throw surfaced;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function recordViolation(slot, error) {
|
|
64
|
+
if (slot && !slot.violation) slot.violation = error;
|
|
65
|
+
return error;
|
|
14
66
|
}
|
|
15
67
|
function urlOfInput(input) {
|
|
16
68
|
if (typeof input === "string") return input;
|
|
17
69
|
if (input instanceof URL) return input.toString();
|
|
18
70
|
return input.url;
|
|
19
71
|
}
|
|
20
|
-
function redactUrlForError(url) {
|
|
21
|
-
try {
|
|
22
|
-
const parsed = new URL(url);
|
|
23
|
-
parsed.search = "";
|
|
24
|
-
return parsed.toString();
|
|
25
|
-
} catch {
|
|
26
|
-
return url;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function withIdentityEncoding(init) {
|
|
30
|
-
const headers = new Headers(init?.headers);
|
|
31
|
-
if (!headers.has("accept-encoding")) {
|
|
32
|
-
headers.set("accept-encoding", "identity");
|
|
33
|
-
}
|
|
34
|
-
return { ...init ?? {}, headers };
|
|
35
|
-
}
|
|
36
72
|
function wrapFetchWithSizeLimit(upstream) {
|
|
37
73
|
const wrapped = async (input, init) => {
|
|
38
74
|
const slot = responseSizeLimitStorage.getStore();
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
const maxBytes = slot ? slot.maxResponseBytes : DEFAULT_MAX_RESPONSE_BYTES;
|
|
76
|
+
if (maxBytes === null) return upstream(input, init);
|
|
77
|
+
const response = await upstream(input, init);
|
|
78
|
+
if (NULL_BODY_STATUSES.has(response.status)) return response;
|
|
79
|
+
const skipDeclaredLength = methodOf(input, init) === "HEAD";
|
|
80
|
+
const url = sanitizeTransportUrl(urlOfInput(input));
|
|
81
|
+
if (isEventStream(response)) {
|
|
82
|
+
const connectionScoped = methodOf(input, init) === "GET";
|
|
83
|
+
const { limit, meter } = streamBudgetFor(connectionScoped, slot, maxBytes);
|
|
84
|
+
return enforceEventStreamLimit(
|
|
85
|
+
response,
|
|
86
|
+
maxBytes,
|
|
87
|
+
limit,
|
|
88
|
+
meter,
|
|
89
|
+
url,
|
|
90
|
+
connectionScoped ? void 0 : slot,
|
|
91
|
+
skipDeclaredLength
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return enforceSizeLimit(response, maxBytes, url, slot, skipDeclaredLength);
|
|
42
95
|
};
|
|
43
96
|
return wrapped;
|
|
44
97
|
}
|
|
45
|
-
function
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
98
|
+
function streamBudgetFor(connectionScoped, slot, maxBytes) {
|
|
99
|
+
if (connectionScoped) return { limit: sseBudgetFor(maxBytes), meter: { consumed: 0 } };
|
|
100
|
+
return {
|
|
101
|
+
limit: slot?.streamLimit ?? sseBudgetFor(maxBytes),
|
|
102
|
+
meter: slot?.streamMeter ?? { consumed: 0 }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function methodOf(input, init) {
|
|
106
|
+
const method = init?.method ?? (typeof Request !== "undefined" && input instanceof Request ? input.method : "GET");
|
|
107
|
+
return method.toUpperCase();
|
|
108
|
+
}
|
|
109
|
+
const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([101, 103, 204, 205, 304]);
|
|
110
|
+
function isEventStream(response) {
|
|
111
|
+
return response.headers.get("content-type")?.toLowerCase().startsWith("text/event-stream") === true;
|
|
112
|
+
}
|
|
113
|
+
function enforceSizeLimit(response, maxBytes, url, slot, skipDeclaredLength = false) {
|
|
114
|
+
const declared = skipDeclaredLength ? void 0 : parseContentLength(response.headers.get("content-length"));
|
|
50
115
|
if (declared !== void 0 && declared > maxBytes) {
|
|
51
|
-
response
|
|
52
|
-
|
|
53
|
-
throw new ResponseTooLargeError(maxBytes, 0, url, declared);
|
|
116
|
+
cancelBody(response);
|
|
117
|
+
throw recordViolation(slot, new ResponseTooLargeError(maxBytes, 0, url, declared));
|
|
54
118
|
}
|
|
55
119
|
if (!response.body) return response;
|
|
56
120
|
let bytesRead = 0;
|
|
@@ -58,13 +122,107 @@ function enforceSizeLimit(response, maxBytes, url) {
|
|
|
58
122
|
transform(chunk, controller) {
|
|
59
123
|
bytesRead += chunk.byteLength;
|
|
60
124
|
if (bytesRead > maxBytes) {
|
|
61
|
-
controller.error(new ResponseTooLargeError(maxBytes, bytesRead, url));
|
|
125
|
+
controller.error(recordViolation(slot, new ResponseTooLargeError(maxBytes, bytesRead, url)));
|
|
62
126
|
return;
|
|
63
127
|
}
|
|
64
128
|
controller.enqueue(chunk);
|
|
65
129
|
}
|
|
66
130
|
});
|
|
67
|
-
return
|
|
131
|
+
return respondWith(response, response.body.pipeThrough(counter));
|
|
132
|
+
}
|
|
133
|
+
const CR = 13;
|
|
134
|
+
const LF = 10;
|
|
135
|
+
function enforceEventStreamLimit(response, maxBytes, streamLimit, meter, url, slot, skipDeclaredLength = false) {
|
|
136
|
+
const remaining = streamLimit - meter.consumed;
|
|
137
|
+
const declared = skipDeclaredLength ? void 0 : parseContentLength(response.headers.get("content-length"));
|
|
138
|
+
if (declared !== void 0 && declared > remaining) {
|
|
139
|
+
cancelBody(response);
|
|
140
|
+
throw recordViolation(slot, new ResponseTooLargeError(streamLimit, meter.consumed, url, declared));
|
|
141
|
+
}
|
|
142
|
+
if (!response.body) return response;
|
|
143
|
+
let bytesInEvent = 0;
|
|
144
|
+
let lineEndings = 0;
|
|
145
|
+
let pendingCR = false;
|
|
146
|
+
let separatorTail = false;
|
|
147
|
+
let pendingReset = false;
|
|
148
|
+
const counter = new TransformStream({
|
|
149
|
+
transform(chunk, controller) {
|
|
150
|
+
meter.consumed += chunk.byteLength;
|
|
151
|
+
if (meter.consumed > streamLimit) {
|
|
152
|
+
controller.error(recordViolation(slot, new ResponseTooLargeError(streamLimit, meter.consumed, url)));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (chunk.length === 0) {
|
|
156
|
+
controller.enqueue(chunk);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (chunk.indexOf(LF) === -1 && chunk.indexOf(CR) === -1) {
|
|
160
|
+
if (pendingReset) {
|
|
161
|
+
pendingReset = false;
|
|
162
|
+
separatorTail = false;
|
|
163
|
+
bytesInEvent = 0;
|
|
164
|
+
}
|
|
165
|
+
bytesInEvent += chunk.length;
|
|
166
|
+
lineEndings = 0;
|
|
167
|
+
pendingCR = false;
|
|
168
|
+
if (bytesInEvent > maxBytes) {
|
|
169
|
+
controller.error(recordViolation(slot, new ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
controller.enqueue(chunk);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
176
|
+
const byte = chunk[i];
|
|
177
|
+
if (pendingReset) {
|
|
178
|
+
pendingReset = false;
|
|
179
|
+
if (separatorTail && byte === LF) {
|
|
180
|
+
separatorTail = false;
|
|
181
|
+
pendingCR = false;
|
|
182
|
+
bytesInEvent += 1;
|
|
183
|
+
if (bytesInEvent > maxBytes) {
|
|
184
|
+
controller.error(recordViolation(slot, new ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
bytesInEvent = 0;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
separatorTail = false;
|
|
191
|
+
bytesInEvent = 0;
|
|
192
|
+
}
|
|
193
|
+
bytesInEvent += 1;
|
|
194
|
+
if (byte === CR) {
|
|
195
|
+
lineEndings += 1;
|
|
196
|
+
pendingCR = true;
|
|
197
|
+
} else if (byte === LF) {
|
|
198
|
+
if (!pendingCR) lineEndings += 1;
|
|
199
|
+
pendingCR = false;
|
|
200
|
+
} else {
|
|
201
|
+
lineEndings = 0;
|
|
202
|
+
pendingCR = false;
|
|
203
|
+
}
|
|
204
|
+
if (bytesInEvent > maxBytes) {
|
|
205
|
+
controller.error(recordViolation(slot, new ResponseTooLargeError(maxBytes, bytesInEvent, url)));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (lineEndings >= 2) {
|
|
209
|
+
lineEndings = 0;
|
|
210
|
+
pendingReset = true;
|
|
211
|
+
separatorTail = pendingCR;
|
|
212
|
+
pendingCR = false;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
controller.enqueue(chunk);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return respondWith(response, response.body.pipeThrough(counter));
|
|
219
|
+
}
|
|
220
|
+
function cancelBody(response) {
|
|
221
|
+
response.body?.cancel().catch(() => {
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function respondWith(response, body) {
|
|
225
|
+
return new Response(body, {
|
|
68
226
|
status: response.status,
|
|
69
227
|
statusText: response.statusText,
|
|
70
228
|
headers: response.headers
|
|
@@ -78,6 +236,9 @@ function parseContentLength(value) {
|
|
|
78
236
|
return Number.isFinite(parsed) ? parsed : void 0;
|
|
79
237
|
}
|
|
80
238
|
export {
|
|
239
|
+
DEFAULT_MAX_RESPONSE_BYTES,
|
|
240
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
241
|
+
responseSizeCapCacheKey,
|
|
81
242
|
responseSizeLimitStorage,
|
|
82
243
|
withResponseSizeLimit,
|
|
83
244
|
wrapFetchWithSizeLimit
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"source_sha": "4e553ad955f83b49c7d221ab5c3ff78237ad02e3",
|
|
5
5
|
"source_tarball_sha256": "580656d6466ef9f0d1119985e6726c2efea718dc671e2ad30957fcb2fd54af0f",
|
|
6
6
|
"upstream_adcp_version": "2.5.3",
|
|
7
|
-
"synced_at": "2026-10-
|
|
7
|
+
"synced_at": "2026-10-15T19:30:04.842Z"
|
|
8
8
|
}
|
|
@@ -2,3 +2,18 @@ import { type KeyObject } from 'crypto';
|
|
|
2
2
|
import type { AdcpJsonWebKey } from './types.mjs';
|
|
3
3
|
export declare function jwkToPublicKey(jwk: AdcpJsonWebKey): KeyObject;
|
|
4
4
|
export declare function verifySignature(alg: string, publicKey: KeyObject, data: Uint8Array, signature: Uint8Array): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Verify with WebCrypto when available. ES256 uses `crypto.subtle`, whose
|
|
7
|
+
* ECDSA signatures are IEEE P1363 (r||s) natively, so edge runtimes such as
|
|
8
|
+
* Cloudflare workerd verify without node:crypto option-shape differences
|
|
9
|
+
* (#3243). Other algorithms, and runtimes without WebCrypto, use
|
|
10
|
+
* {@link verifySignature}.
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifySignatureAsync(alg: string, publicKey: KeyObject | AdcpJsonWebKey, data: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Sign with a private JWK. ES256 output is IEEE P1363 (r||s). The node:crypto
|
|
15
|
+
* path passes the key as a JWK so workerd accepts it (#3243).
|
|
16
|
+
*/
|
|
17
|
+
export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
|
|
18
|
+
/** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available. */
|
|
19
|
+
export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
|
|
@@ -2,3 +2,18 @@ import { type KeyObject } from 'crypto';
|
|
|
2
2
|
import type { AdcpJsonWebKey } from './types';
|
|
3
3
|
export declare function jwkToPublicKey(jwk: AdcpJsonWebKey): KeyObject;
|
|
4
4
|
export declare function verifySignature(alg: string, publicKey: KeyObject, data: Uint8Array, signature: Uint8Array): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Verify with WebCrypto when available. ES256 uses `crypto.subtle`, whose
|
|
7
|
+
* ECDSA signatures are IEEE P1363 (r||s) natively, so edge runtimes such as
|
|
8
|
+
* Cloudflare workerd verify without node:crypto option-shape differences
|
|
9
|
+
* (#3243). Other algorithms, and runtimes without WebCrypto, use
|
|
10
|
+
* {@link verifySignature}.
|
|
11
|
+
*/
|
|
12
|
+
export declare function verifySignatureAsync(alg: string, publicKey: KeyObject | AdcpJsonWebKey, data: Uint8Array, signature: Uint8Array): Promise<boolean>;
|
|
13
|
+
/**
|
|
14
|
+
* Sign with a private JWK. ES256 output is IEEE P1363 (r||s). The node:crypto
|
|
15
|
+
* path passes the key as a JWK so workerd accepts it (#3243).
|
|
16
|
+
*/
|
|
17
|
+
export declare function signWithPrivateJwk(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Uint8Array;
|
|
18
|
+
/** {@link signWithPrivateJwk}, preferring WebCrypto for ES256 when available. */
|
|
19
|
+
export declare function signWithPrivateJwkAsync(alg: string, privateJwk: AdcpJsonWebKey, data: Uint8Array): Promise<Uint8Array>;
|