threadwire 0.1.24 → 0.1.26
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/CHANGELOG.md +33 -0
- package/docs/delegated-result-protocol.md +7 -0
- package/package.json +1 -1
- package/scripts/verify-package.js +3 -0
- package/src/absolute-deadline.js +9 -2
- package/src/cli.js +30 -5
- package/src/delegated-result-admission.js +44 -4
- package/src/isolated-runtime-client.js +2 -2
- package/src/isolated-runtime.js +367 -68
- package/src/isolated-worker.js +139 -11
- package/src/model-broker-policy.js +14 -3
- package/src/model-broker.js +50 -13
- package/src/mutation-policy.js +65 -0
- package/src/mutation-snapshot.js +307 -0
- package/src/provider-capacity-kimi.js +12 -3
- package/src/provider-capacity.js +1 -1
- package/src/providers/kimi.js +2 -8
- package/src/record-helpers.js +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Enforce per-run worker mutation boundaries in isolated runtime. `threadwire run
|
|
6
|
+
--relay-write` accepts a closed `--mutation-policy <json>` with
|
|
7
|
+
`{worktreeEdit, commit, push, githubWrite}` booleans; omitted capabilities
|
|
8
|
+
default to allowed to preserve existing behavior. The policy is sealed at
|
|
9
|
+
preflight and enforced at container creation: denied worktree edits make the
|
|
10
|
+
worktree mount read-only, denied commits mount `.git` read-only, denied pushes
|
|
11
|
+
run with no remote credentials and no egress, and denied GitHub writes remove
|
|
12
|
+
GitHub CLI/token surfaces. The same policy is now accepted, sealed, and
|
|
13
|
+
enforced for Kimi runs: denied worktree or commit access makes the Kimi source
|
|
14
|
+
volume read-only, push denial keeps the worker free of remote credentials, and
|
|
15
|
+
GitHub-write denial avoids broad GitHub CLI config while preserving the Kimi
|
|
16
|
+
model-broker relay and exact session-resume behavior. Pre/post run git
|
|
17
|
+
snapshots capture HEAD, refs, porcelain status, reflog delta, and
|
|
18
|
+
remote-tracking refs in a credential-free subprocess for both Codex and Kimi;
|
|
19
|
+
Kimi snapshots run through an offline helper container so source Git state can
|
|
20
|
+
be observed without exposing credentials. The runtime reconciles observations
|
|
21
|
+
with the sealed policy and any closed provider `mutation_claim`, failing with
|
|
22
|
+
exit code 2 when a denied mutation occurs or the provider claim contradicts
|
|
23
|
+
observations. The `mutation_claim` parser now requires the exact closed schema
|
|
24
|
+
(`{type, mutations}` with exactly the four boolean capabilities) and rejects
|
|
25
|
+
malformed or multiple claims as deterministic reconciliation failures. Add
|
|
26
|
+
focused unit tests and a Docker E2E fixture covering forbidden commit/push and
|
|
27
|
+
a false "no mutation" provider narrative.
|
|
28
|
+
|
|
29
|
+
- Harden `threadwire capacity` edge cases. Kimi quota remaining percent is now
|
|
30
|
+
computed with integer-safe arithmetic so values such as 29 % of 100 no longer
|
|
31
|
+
lose the inclusive reserve boundary to floating-point rounding. Kimi probe
|
|
32
|
+
body-read aborts, timeouts, and transport errors are classified as
|
|
33
|
+
`unavailable`, while oversized bodies and malformed JSON remain `protocol`;
|
|
34
|
+
no token or response detail is leaked. `--timeout-ms` is now rejected above
|
|
35
|
+
Node's maximum timer delay (`2147483647`), matching the existing positive
|
|
36
|
+
integer error convention and updated help text.
|
|
37
|
+
|
|
5
38
|
- Fix a race in isolated-runtime worker cleanup: Docker's ContainerStop returns
|
|
6
39
|
HTTP 304 when the owned container exits before the stop call, and
|
|
7
40
|
ContainerKill returns HTTP 409 when the container exits between the post-stop
|
|
@@ -100,6 +100,13 @@ The only optional fields are:
|
|
|
100
100
|
| `artifactHandles` | array of opaque ASCII IDs or safe POSIX-style paths | 16 entries; 512 characters each |
|
|
101
101
|
| `references` | array of `{kind,value}` | 16 entries; 2,048 code points per value |
|
|
102
102
|
| `validationSummary` | string | 2,048 Unicode code points |
|
|
103
|
+
| `observedMutations` | object | closed `{worktreeEdit,commit,push,githubWrite}` booleans |
|
|
104
|
+
|
|
105
|
+
`observedMutations` is populated only by the isolated runtime after reconciling
|
|
106
|
+
pre/post run git snapshots with the sealed per-run mutation policy and any closed
|
|
107
|
+
provider `mutation_claim`. A claim that contradicts the observed state makes the
|
|
108
|
+
terminal state `failed` with exit code 2. When the field is absent, callers must
|
|
109
|
+
not infer any mutation policy.
|
|
103
110
|
|
|
104
111
|
A reference `kind` is exactly `commit` or `url`. Commit values are 7–64
|
|
105
112
|
hexadecimal characters. URL values are non-whitespace HTTP or HTTPS URLs
|
package/package.json
CHANGED
|
@@ -61,6 +61,8 @@ const EXPECTED_FILES = [
|
|
|
61
61
|
"src/model-broker-policy.js",
|
|
62
62
|
"src/model-broker.js",
|
|
63
63
|
"src/mount-policy.js",
|
|
64
|
+
"src/mutation-policy.js",
|
|
65
|
+
"src/mutation-snapshot.js",
|
|
64
66
|
"src/notice-queue.js",
|
|
65
67
|
"src/notifiers/fetch-transport.js",
|
|
66
68
|
"src/notifiers/telegram.js",
|
|
@@ -71,6 +73,7 @@ const EXPECTED_FILES = [
|
|
|
71
73
|
"src/providers/index.js",
|
|
72
74
|
"src/providers/kimi.js",
|
|
73
75
|
"src/providers/opencode.js",
|
|
76
|
+
"src/record-helpers.js",
|
|
74
77
|
"src/redaction.js",
|
|
75
78
|
"src/provider-capacity-codex.js",
|
|
76
79
|
"src/provider-capacity-kimi.js",
|
package/src/absolute-deadline.js
CHANGED
|
@@ -75,6 +75,13 @@ export async function abortable(promise, signal) {
|
|
|
75
75
|
})
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export class ResponseCapacityError extends Error {
|
|
79
|
+
constructor() {
|
|
80
|
+
super("Response exceeded capacity")
|
|
81
|
+
this.name = "ResponseCapacityError"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
78
85
|
export async function readResponseCapped(response, capacity, signal) {
|
|
79
86
|
const reader = response.body?.getReader()
|
|
80
87
|
if (!reader) return Buffer.alloc(0)
|
|
@@ -87,8 +94,8 @@ export async function readResponseCapped(response, capacity, signal) {
|
|
|
87
94
|
const chunk = Buffer.from(value)
|
|
88
95
|
size += chunk.length
|
|
89
96
|
if (size > capacity) {
|
|
90
|
-
await reader.cancel(new
|
|
91
|
-
throw new
|
|
97
|
+
await reader.cancel(new ResponseCapacityError()).catch(() => {})
|
|
98
|
+
throw new ResponseCapacityError()
|
|
92
99
|
}
|
|
93
100
|
chunks.push(chunk)
|
|
94
101
|
}
|
package/src/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {lstat, readFile, readlink, realpath} from "node:fs/promises"
|
|
|
4
4
|
import {randomUUID} from "node:crypto"
|
|
5
5
|
import {basename, dirname, isAbsolute, join, normalize, resolve} from "node:path"
|
|
6
6
|
import {stdin, stderr, stdout} from "node:process"
|
|
7
|
-
import {createFetchTransport} from "./notifiers/fetch-transport.js"
|
|
7
|
+
import {createFetchTransport, MAX_TIMER_DELAY_MS} from "./notifiers/fetch-transport.js"
|
|
8
8
|
import {createTelegramSender, parseTelegramTarget} from "./notifiers/telegram.js"
|
|
9
9
|
import {createProvider, PROVIDERS} from "./providers/index.js"
|
|
10
10
|
import {runWorker} from "./run-worker.js"
|
|
@@ -18,6 +18,7 @@ import {ContextBudgetMetrics} from "./context-budget-metrics.js"
|
|
|
18
18
|
import {workerEventToIndexEvent} from "./evidence-index.js"
|
|
19
19
|
import {isolatedRuntimeClientFromEnvironment} from "./isolated-runtime-client.js"
|
|
20
20
|
import {validateRelayWriteProviderArguments} from "./relay-write.js"
|
|
21
|
+
import {parseMutationPolicy} from "./mutation-policy.js"
|
|
21
22
|
import {abortable} from "./absolute-deadline.js"
|
|
22
23
|
import {NormalizedOutput} from "./normalized-output.js"
|
|
23
24
|
import {
|
|
@@ -34,6 +35,7 @@ import {probeKimiCapacity} from "./provider-capacity-kimi.js"
|
|
|
34
35
|
const HELP = `Usage: threadwire run --provider <codex|claude|kimi|opencode> --target telegram:<chat-id> | telegram:<chat-id>:<thread-id> | file:<absolute-path>
|
|
35
36
|
[--process-number <positive-integer>] [--cwd <directory>]
|
|
36
37
|
[--relay-write]
|
|
38
|
+
[--mutation-policy <json>]
|
|
37
39
|
[--tool-messages] [--max-output-length <positive-integer>]
|
|
38
40
|
[--resume-session <provider-session-id>] [--transcript <normalized-jsonl-path>]
|
|
39
41
|
[--activity-log <local-jsonl-path>]
|
|
@@ -45,11 +47,11 @@ const HELP = `Usage: threadwire run --provider <codex|claude|kimi|opencode> --ta
|
|
|
45
47
|
(--summary | --events [--kind <kind>] [--after <order>] [--before <order>] [--limit <positive-integer>] | --failures [--after <order>] [--limit <positive-integer>])
|
|
46
48
|
threadwire capacity [--provider <codex|kimi>]...
|
|
47
49
|
[--short-reserve-percent <0-100>] [--long-reserve-percent <0-100>]
|
|
48
|
-
[--timeout-ms <
|
|
50
|
+
[--timeout-ms <1-2147483647>]
|
|
49
51
|
threadwire status --activity-log <absolute-path>
|
|
50
52
|
(Emits one closed versioned JSON status document from the activity log.)`
|
|
51
53
|
|
|
52
|
-
/** @typedef {{provider: string, target: string, cwd: string, toolMessages: boolean, relayWrite: boolean, prompt?: string, promptFile?: string, processNumber?: number, maxOutputLength?: number, resumeSession?: string, transcript?: string, activityLog?: string, providerArguments: string[]}} ParsedArguments */
|
|
54
|
+
/** @typedef {{provider: string, target: string, cwd: string, toolMessages: boolean, relayWrite: boolean, mutationPolicy?: import("./mutation-policy.js").MutationPolicy | undefined, prompt?: string, promptFile?: string, processNumber?: number, maxOutputLength?: number, resumeSession?: string, transcript?: string, activityLog?: string, providerArguments: string[]}} ParsedArguments */
|
|
53
55
|
/** @typedef {{evidenceRead: true, request: unknown} | {evidenceInspect: true, request: unknown}} EvidenceParsedArguments */
|
|
54
56
|
/** @typedef {{capacity: true, providers: import("./provider-capacity.js").CapacityProvider[], shortReservePercent: number, longReservePercent: number, timeoutMs: number}} CapacityParsedArguments */
|
|
55
57
|
/** @typedef {{probe: (provider: import("./provider-capacity.js").CapacityProvider, timeoutMs?: number) => Promise<import("./provider-capacity.js").CapacityCandidate>}} CapacityProbeDependency */
|
|
@@ -114,6 +116,8 @@ export function parseArguments(arguments_) {
|
|
|
114
116
|
if (value.includes("\0")) throw new Error("--transcript path is invalid")
|
|
115
117
|
parsed.transcript = resolve(value)
|
|
116
118
|
transcriptProvided = true
|
|
119
|
+
} else if (option === "--mutation-policy") {
|
|
120
|
+
parsed.mutationPolicy = parseMutationPolicy(value)
|
|
117
121
|
} else throw new Error(`Unknown option: ${option}`)
|
|
118
122
|
index += 2
|
|
119
123
|
}
|
|
@@ -127,6 +131,7 @@ export function parseArguments(arguments_) {
|
|
|
127
131
|
throw new Error("--transcript and --activity-log must resolve to different paths")
|
|
128
132
|
}
|
|
129
133
|
if (parsed.relayWrite && parsed.provider !== "codex") throw new Error("--relay-write is only supported for codex")
|
|
134
|
+
if (parsed.mutationPolicy !== undefined && !parsed.relayWrite) throw new Error("--mutation-policy requires --relay-write")
|
|
130
135
|
return /** @type {ParsedArguments} */ (parsed)
|
|
131
136
|
}
|
|
132
137
|
|
|
@@ -231,7 +236,7 @@ function parseCapacityArguments(arguments_) {
|
|
|
231
236
|
providers.push(provider)
|
|
232
237
|
} else if (option === "--short-reserve-percent") shortReservePercent = reservePercent(value, option)
|
|
233
238
|
else if (option === "--long-reserve-percent") longReservePercent = reservePercent(value, option)
|
|
234
|
-
else if (option === "--timeout-ms") timeoutMs =
|
|
239
|
+
else if (option === "--timeout-ms") timeoutMs = capacityTimeoutMs(value, option)
|
|
235
240
|
else throw new Error(HELP)
|
|
236
241
|
}
|
|
237
242
|
return {
|
|
@@ -383,7 +388,8 @@ export async function main(arguments_, dependencies = {}) {
|
|
|
383
388
|
repositoryRoot: parsed.cwd,
|
|
384
389
|
cwd: parsed.cwd,
|
|
385
390
|
providerArguments: parsed.providerArguments,
|
|
386
|
-
...(parsed.resumeSession === undefined ? {} : {resumeSession: parsed.resumeSession})
|
|
391
|
+
...(parsed.resumeSession === undefined ? {} : {resumeSession: parsed.resumeSession}),
|
|
392
|
+
...(parsed.mutationPolicy === undefined ? {} : {mutationPolicy: parsed.mutationPolicy})
|
|
387
393
|
})
|
|
388
394
|
: undefined
|
|
389
395
|
const admission = /** @type {DelegatedResultAdmission} */ (runAdmission)
|
|
@@ -532,6 +538,11 @@ export async function main(arguments_, dependencies = {}) {
|
|
|
532
538
|
if (activity && pid !== undefined) activity.recordStarted(provider.name, pid)
|
|
533
539
|
},
|
|
534
540
|
onRecord: (record) => {
|
|
541
|
+
const recordObject = /** @type {Record<string, unknown> | undefined} */ (isRecord(record) ? record : undefined)
|
|
542
|
+
if (recordObject !== undefined && recordObject.type === "observed_mutations" && isRecord(recordObject.mutations)) {
|
|
543
|
+
admission.setObservedMutations(/** @type {import("./mutation-policy.js").MutationPolicy} */ (recordObject.mutations))
|
|
544
|
+
return
|
|
545
|
+
}
|
|
535
546
|
metrics.recordParsedProviderRecord("provider_stdout")
|
|
536
547
|
const id = provider.sessionId(record)
|
|
537
548
|
if (id !== undefined) {
|
|
@@ -843,6 +854,20 @@ function positiveInteger(value, option) {
|
|
|
843
854
|
return number
|
|
844
855
|
}
|
|
845
856
|
|
|
857
|
+
/** @param {string} value @param {string} option */
|
|
858
|
+
function capacityTimeoutMs(value, option) {
|
|
859
|
+
const number = positiveInteger(value, option)
|
|
860
|
+
if (number > MAX_TIMER_DELAY_MS) {
|
|
861
|
+
throw new Error(`${option} must be no greater than ${MAX_TIMER_DELAY_MS}`)
|
|
862
|
+
}
|
|
863
|
+
return number
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/** @param {unknown} value */
|
|
867
|
+
function isRecord(value) {
|
|
868
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
869
|
+
}
|
|
870
|
+
|
|
846
871
|
/** @param {string} value */
|
|
847
872
|
function sessionId(value) {
|
|
848
873
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import {types as utilTypes} from "node:util"
|
|
4
|
+
import {MUTATION_CAPABILITIES} from "./mutation-policy.js"
|
|
4
5
|
import {sanitizeOutput} from "./relay.js"
|
|
5
6
|
|
|
6
7
|
export const CONCLUSION_LIMIT = 16_384
|
|
@@ -24,7 +25,8 @@ const CANDIDATE_FIELDS = new Set([
|
|
|
24
25
|
"continuationHandle",
|
|
25
26
|
"artifactHandles",
|
|
26
27
|
"references",
|
|
27
|
-
"validationSummary"
|
|
28
|
+
"validationSummary",
|
|
29
|
+
"observedMutations"
|
|
28
30
|
])
|
|
29
31
|
const STATES = new Set(["completed", "failed", "blocked", "needs_decision"])
|
|
30
32
|
|
|
@@ -40,7 +42,8 @@ const STATES = new Set(["completed", "failed", "blocked", "needs_decision"])
|
|
|
40
42
|
* continuationHandle?: string,
|
|
41
43
|
* artifactHandles?: string[],
|
|
42
44
|
* references?: AdmissionReference[],
|
|
43
|
-
* validationSummary?: string
|
|
45
|
+
* validationSummary?: string,
|
|
46
|
+
* observedMutations?: import("./mutation-policy.js").MutationPolicy
|
|
44
47
|
* }} DelegatedResultCandidate
|
|
45
48
|
*/
|
|
46
49
|
|
|
@@ -57,6 +60,8 @@ export class DelegatedResultAdmission {
|
|
|
57
60
|
this.artifactHandles = []
|
|
58
61
|
/** @type {AdmissionReference[]} */
|
|
59
62
|
this.references = []
|
|
63
|
+
/** @type {import("./mutation-policy.js").MutationPolicy | undefined} */
|
|
64
|
+
this.observedMutations = undefined
|
|
60
65
|
this.completed = false
|
|
61
66
|
}
|
|
62
67
|
|
|
@@ -100,6 +105,17 @@ export class DelegatedResultAdmission {
|
|
|
100
105
|
this.artifactHandles = this.artifactHandles.filter((candidate) => candidate !== handle)
|
|
101
106
|
}
|
|
102
107
|
|
|
108
|
+
/** @param {import("./mutation-policy.js").MutationPolicy} value */
|
|
109
|
+
setObservedMutations(value) {
|
|
110
|
+
if (
|
|
111
|
+
typeof value !== "object" || value === null || Array.isArray(value)
|
|
112
|
+
|| !MUTATION_CAPABILITIES.every((capability) => typeof value[capability] === "boolean")
|
|
113
|
+
) {
|
|
114
|
+
throw new Error("Invalid observed mutations")
|
|
115
|
+
}
|
|
116
|
+
this.observedMutations = value
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
/** @param {{state: TerminalState, exitCode: number}} terminal */
|
|
104
120
|
preview(terminal) {
|
|
105
121
|
return this.createEnvelope(terminal)
|
|
@@ -140,7 +156,8 @@ export class DelegatedResultAdmission {
|
|
|
140
156
|
...(terminal.state !== "completed" || safeConclusion === undefined ? {} : {conclusion: safeConclusion}),
|
|
141
157
|
...(this.continuationHandle === undefined ? {} : {continuationHandle: this.continuationHandle}),
|
|
142
158
|
...(this.artifactHandles.length === 0 ? {} : {artifactHandles: this.artifactHandles}),
|
|
143
|
-
...(this.references.length === 0 ? {} : {references: this.references})
|
|
159
|
+
...(this.references.length === 0 ? {} : {references: this.references}),
|
|
160
|
+
...(this.observedMutations === undefined ? {} : {observedMutations: this.observedMutations})
|
|
144
161
|
})
|
|
145
162
|
}
|
|
146
163
|
}
|
|
@@ -166,6 +183,7 @@ export function createDelegatedResultEnvelope(candidate) {
|
|
|
166
183
|
const validationSummary = optionalBoundedString(clonedCandidate.validationSummary, "validationSummary", VALIDATION_SUMMARY_LIMIT)
|
|
167
184
|
const artifactHandles = optionalArtifactHandles(clonedCandidate.artifactHandles)
|
|
168
185
|
const references = optionalReferences(clonedCandidate.references)
|
|
186
|
+
const observedMutations = optionalObservedMutations(clonedCandidate.observedMutations)
|
|
169
187
|
|
|
170
188
|
const envelope = {
|
|
171
189
|
version: 1,
|
|
@@ -178,7 +196,8 @@ export function createDelegatedResultEnvelope(candidate) {
|
|
|
178
196
|
...(continuationHandle === undefined ? {} : {continuationHandle}),
|
|
179
197
|
...(artifactHandles === undefined ? {} : {artifactHandles}),
|
|
180
198
|
...(references === undefined ? {} : {references}),
|
|
181
|
-
...(validationSummary === undefined ? {} : {validationSummary})
|
|
199
|
+
...(validationSummary === undefined ? {} : {validationSummary}),
|
|
200
|
+
...(observedMutations === undefined ? {} : {observedMutations})
|
|
182
201
|
}
|
|
183
202
|
if (Buffer.byteLength(JSON.stringify(envelope), "utf8") > MAX_ENVELOPE_BYTES) {
|
|
184
203
|
throw new Error("Delegated result candidate exceeds the envelope byte limit")
|
|
@@ -256,6 +275,27 @@ function optionalReferences(value) {
|
|
|
256
275
|
})
|
|
257
276
|
}
|
|
258
277
|
|
|
278
|
+
/** @param {unknown} value */
|
|
279
|
+
function optionalObservedMutations(value) {
|
|
280
|
+
if (value === undefined) return undefined
|
|
281
|
+
if (!isPlainObject(value)) throw new Error("Invalid delegated result observedMutations")
|
|
282
|
+
if (!hasExactEnumerableFields(value, [...MUTATION_CAPABILITIES])) {
|
|
283
|
+
throw new Error("Invalid delegated result observedMutations")
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
worktreeEdit: booleanCapability(value.worktreeEdit),
|
|
287
|
+
commit: booleanCapability(value.commit),
|
|
288
|
+
push: booleanCapability(value.push),
|
|
289
|
+
githubWrite: booleanCapability(value.githubWrite)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** @param {unknown} value */
|
|
294
|
+
function booleanCapability(value) {
|
|
295
|
+
if (typeof value !== "boolean") throw new Error("Invalid delegated result observedMutations")
|
|
296
|
+
return value
|
|
297
|
+
}
|
|
298
|
+
|
|
259
299
|
/**
|
|
260
300
|
* @param {unknown} value
|
|
261
301
|
* @param {"artifactHandles" | "references"} field
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
/* eslint-disable jsdoc/require-jsdoc */
|
|
3
3
|
|
|
4
|
-
import {AbsoluteDeadline, deadlineAfter, abortable, readResponseCapped} from "./absolute-deadline.js"
|
|
4
|
+
import {AbsoluteDeadline, deadlineAfter, abortable, readResponseCapped, ResponseCapacityError} from "./absolute-deadline.js"
|
|
5
5
|
|
|
6
6
|
const MAX_RESPONSE_BYTES = 2_097_152
|
|
7
7
|
const MAX_RAW_OUTPUT_BYTES = 1_048_576
|
|
@@ -207,7 +207,7 @@ export class IsolatedRuntimeClient {
|
|
|
207
207
|
try {
|
|
208
208
|
bytes = await readResponseCapped(response, MAX_RESPONSE_BYTES, signal)
|
|
209
209
|
} catch (error) {
|
|
210
|
-
if (error instanceof
|
|
210
|
+
if (error instanceof ResponseCapacityError) throw new Error("Isolated runtime response exceeded the configured capacity", {cause: error})
|
|
211
211
|
throw error
|
|
212
212
|
}
|
|
213
213
|
if (!response.ok) throw new Error(safeRemoteError(bytes))
|