libfx 0.0.12 → 0.0.13
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 +168 -30
- package/fx-core.wasm +0 -0
- package/fx-sdk.js +395 -124
- package/fx-term.wasm +0 -0
- package/libfx.darwin-arm64.node +0 -0
- package/libfx.darwin-x64.node +0 -0
- package/libfx.linux-arm64.node +0 -0
- package/libfx.linux-x64.node +0 -0
- package/node.cjs +389 -134
- package/node.js +18 -5
- package/package.json +1 -1
package/node.cjs
CHANGED
|
@@ -198,7 +198,12 @@ var maxUnreadEventBytes = 1024 * 1024;
|
|
|
198
198
|
var maxUnreadEvents = 256;
|
|
199
199
|
var maxPromptImages = 8;
|
|
200
200
|
var maxPromptImageDataBytes = 5 * 1024 * 1024;
|
|
201
|
-
var
|
|
201
|
+
var maxPromptImagesDataBytes = 8 * 1024 * 1024;
|
|
202
|
+
var maxPromptImageBytes = maxPromptImageDataBytes / 4 * 3;
|
|
203
|
+
var maxPromptImagesBytes = maxPromptImagesDataBytes / 4 * 3;
|
|
204
|
+
var maxPendingAttachments = 8;
|
|
205
|
+
var maxOutboundAttachments = 4;
|
|
206
|
+
var maxCheckpointBytes = 4 * 1024 * 1024;
|
|
202
207
|
var maxPromptFrameBytes = 8 * 1024 * 1024;
|
|
203
208
|
var promptFrameEnvelopeBytes = 128;
|
|
204
209
|
var maxSteeringMessageBytes = 64 * 1024;
|
|
@@ -255,6 +260,14 @@ function normalizeFast(value) {
|
|
|
255
260
|
}
|
|
256
261
|
return value;
|
|
257
262
|
}
|
|
263
|
+
function normalizeUltrafast(value) {
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
return;
|
|
266
|
+
if (typeof value !== "boolean") {
|
|
267
|
+
throw new TypeError("ultrafast must be a boolean");
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
270
|
+
}
|
|
258
271
|
function normalizeAgentOptions(value) {
|
|
259
272
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
260
273
|
throw new TypeError("createFxAgent() options must be an object");
|
|
@@ -265,24 +278,29 @@ function normalizeAgentOptions(value) {
|
|
|
265
278
|
}
|
|
266
279
|
options.apiKey = boundedString(options.apiKey, "apiKey", maxApiKeyBytes, true);
|
|
267
280
|
if (options.model !== null && typeof options.model === "object" && !Array.isArray(options.model)) {
|
|
268
|
-
if (Object.hasOwn(options, "effort") || Object.hasOwn(options, "fast")) {
|
|
269
|
-
throw new TypeError("model options cannot be mixed with top-level effort or
|
|
281
|
+
if (Object.hasOwn(options, "effort") || Object.hasOwn(options, "fast") || Object.hasOwn(options, "ultrafast")) {
|
|
282
|
+
throw new TypeError("model options cannot be mixed with top-level effort, fast, or ultrafast");
|
|
270
283
|
}
|
|
271
284
|
const model = options.model;
|
|
272
285
|
for (const name of Object.keys(model)) {
|
|
273
|
-
if (name !== "id" && name !== "effort" && name !== "fast") {
|
|
286
|
+
if (name !== "id" && name !== "effort" && name !== "fast" && name !== "ultrafast") {
|
|
274
287
|
throw new TypeError(`unsupported model option: ${name}`);
|
|
275
288
|
}
|
|
276
289
|
}
|
|
277
290
|
options.model = boundedString(model.id, "model.id", maxModelBytes, true);
|
|
278
291
|
options.effort = normalizeEffort(model.effort);
|
|
279
292
|
options.fast = normalizeFast(model.fast);
|
|
293
|
+
options.ultrafast = normalizeUltrafast(model.ultrafast);
|
|
280
294
|
} else {
|
|
281
295
|
options.model = boundedString(options.model, "model", maxModelBytes, false);
|
|
282
296
|
options.effort = normalizeEffort(options.effort);
|
|
283
297
|
options.fast = normalizeFast(options.fast);
|
|
298
|
+
options.ultrafast = normalizeUltrafast(options.ultrafast);
|
|
284
299
|
}
|
|
285
300
|
validateGatewayChatUrl(options.gatewayChatUrl);
|
|
301
|
+
if (options.resizeImage !== undefined && typeof options.resizeImage !== "function") {
|
|
302
|
+
throw new TypeError("resizeImage must be a function");
|
|
303
|
+
}
|
|
286
304
|
return options;
|
|
287
305
|
}
|
|
288
306
|
function agentEnvironment(options) {
|
|
@@ -291,13 +309,14 @@ function agentEnvironment(options) {
|
|
|
291
309
|
...options.model === undefined ? {} : { FX_MODEL: options.model },
|
|
292
310
|
...options.effort === undefined ? {} : { FX_EFFORT: options.effort },
|
|
293
311
|
...options.fast === undefined ? {} : { FX_FAST: options.fast ? "true" : "false" },
|
|
312
|
+
...options.ultrafast === undefined ? {} : { FX_ULTRAFAST: options.ultrafast ? "true" : "false" },
|
|
294
313
|
...options.gatewayChatUrl === undefined ? {} : { FX_GATEWAY_CHAT_URL: options.gatewayChatUrl }
|
|
295
314
|
};
|
|
296
315
|
}
|
|
297
316
|
function agentRpcError(response) {
|
|
298
317
|
const error = new Error(response.message);
|
|
299
318
|
const data = response.data;
|
|
300
|
-
if (data && ["LIBFX_MODEL_UNSUPPORTED_EFFORT", "LIBFX_MODEL_UNSUPPORTED_FAST"].includes(data.code) && typeof data.model === "string" && data.capability === (data.code === "LIBFX_MODEL_UNSUPPORTED_FAST" ? "fast" : "effort")) {
|
|
319
|
+
if (data && ["LIBFX_MODEL_UNSUPPORTED_EFFORT", "LIBFX_MODEL_UNSUPPORTED_FAST", "LIBFX_MODEL_UNSUPPORTED_ULTRAFAST"].includes(data.code) && typeof data.model === "string" && data.capability === (data.code === "LIBFX_MODEL_UNSUPPORTED_FAST" ? "fast" : data.code === "LIBFX_MODEL_UNSUPPORTED_ULTRAFAST" ? "ultrafast" : "effort")) {
|
|
301
320
|
error.code = data.code;
|
|
302
321
|
error.model = data.model;
|
|
303
322
|
error.capability = data.capability;
|
|
@@ -660,6 +679,9 @@ function createRuntime(options) {
|
|
|
660
679
|
const steering = [];
|
|
661
680
|
let steeringBytes = 0;
|
|
662
681
|
let steeringOpen = false;
|
|
682
|
+
const inboundAttachments = new Map;
|
|
683
|
+
const outboundAttachments = new Map;
|
|
684
|
+
let nextOutboundAttachment = 1;
|
|
663
685
|
const workspaceExecs = new Set;
|
|
664
686
|
const workspace = prepareWorkspaceAdapter(options.workspace);
|
|
665
687
|
const args = ["fx", ...options.args || []];
|
|
@@ -983,6 +1005,37 @@ function createRuntime(options) {
|
|
|
983
1005
|
steeringBytes -= value.length;
|
|
984
1006
|
return value.length;
|
|
985
1007
|
}
|
|
1008
|
+
function writeAttachment(id, data) {
|
|
1009
|
+
if (inboundAttachments.size >= maxPendingAttachments)
|
|
1010
|
+
throw new Error("attachment table is full");
|
|
1011
|
+
inboundAttachments.set(id, data.slice());
|
|
1012
|
+
}
|
|
1013
|
+
function attachmentSize(id) {
|
|
1014
|
+
return inboundAttachments.get(id >>> 0)?.length ?? -1;
|
|
1015
|
+
}
|
|
1016
|
+
function attachmentTake(id, outputPtr, outputCap) {
|
|
1017
|
+
const key = id >>> 0;
|
|
1018
|
+
const value = inboundAttachments.get(key);
|
|
1019
|
+
if (!value)
|
|
1020
|
+
return -1;
|
|
1021
|
+
if (value.length > 0) {
|
|
1022
|
+
const output = checkedBytes(outputPtr, outputCap);
|
|
1023
|
+
if (!output || value.length > output.length)
|
|
1024
|
+
return -1;
|
|
1025
|
+
output.set(value);
|
|
1026
|
+
}
|
|
1027
|
+
inboundAttachments.delete(key);
|
|
1028
|
+
return value.length;
|
|
1029
|
+
}
|
|
1030
|
+
function attachmentPut(inputPtr, inputLen) {
|
|
1031
|
+
const input = checkedBytes(inputPtr, inputLen);
|
|
1032
|
+
if (!input || outboundAttachments.size >= maxOutboundAttachments)
|
|
1033
|
+
return -1;
|
|
1034
|
+
const id = nextOutboundAttachment;
|
|
1035
|
+
nextOutboundAttachment = id === 2147483647 ? 1 : id + 1;
|
|
1036
|
+
outboundAttachments.set(id, input.slice());
|
|
1037
|
+
return id;
|
|
1038
|
+
}
|
|
986
1039
|
let pendingHostToolResult = null;
|
|
987
1040
|
function hostToolCall(namePtr, nameLen, argumentsPtr, argumentsLen, outputPtr, outputCap, statusPtr) {
|
|
988
1041
|
pendingHostToolResult = null;
|
|
@@ -1446,6 +1499,9 @@ function createRuntime(options) {
|
|
|
1446
1499
|
steeringOpen = false;
|
|
1447
1500
|
clearSteering();
|
|
1448
1501
|
},
|
|
1502
|
+
fx_attachment_size: attachmentSize,
|
|
1503
|
+
fx_attachment_take: attachmentTake,
|
|
1504
|
+
fx_attachment_put: attachmentPut,
|
|
1449
1505
|
fx_open_url: new WebAssembly.Suspending(openUrl),
|
|
1450
1506
|
fx_oauth_session_load: new WebAssembly.Suspending(oauthSessionLoad),
|
|
1451
1507
|
fx_oauth_session_commit: new WebAssembly.Suspending(oauthSessionCommit),
|
|
@@ -1476,6 +1532,15 @@ function createRuntime(options) {
|
|
|
1476
1532
|
write(data) {
|
|
1477
1533
|
stdin.push(typeof data === "string" ? encoder.encode(data) : data);
|
|
1478
1534
|
},
|
|
1535
|
+
writeAttachment,
|
|
1536
|
+
takeAttachment(id) {
|
|
1537
|
+
const value = outboundAttachments.get(id) ?? null;
|
|
1538
|
+
outboundAttachments.delete(id);
|
|
1539
|
+
return value;
|
|
1540
|
+
},
|
|
1541
|
+
discardAttachments() {
|
|
1542
|
+
inboundAttachments.clear();
|
|
1543
|
+
},
|
|
1479
1544
|
wake() {
|
|
1480
1545
|
stdin.wake();
|
|
1481
1546
|
},
|
|
@@ -1659,45 +1724,122 @@ function blobByteLength(value) {
|
|
|
1659
1724
|
return null;
|
|
1660
1725
|
}
|
|
1661
1726
|
}
|
|
1662
|
-
function
|
|
1727
|
+
function normalizeImageSourceRef(value, name) {
|
|
1728
|
+
boundedString(value, `${name} sourceRef`, 512, false);
|
|
1729
|
+
if (value !== undefined && /[\x00-\x1f\x7f\uD800-\uDFFF]/u.test(value)) {
|
|
1730
|
+
throw new TypeError(`${name} sourceRef must be valid UTF-8 without ASCII controls`);
|
|
1731
|
+
}
|
|
1732
|
+
return value;
|
|
1733
|
+
}
|
|
1734
|
+
function omitReferencedImageData(blocks) {
|
|
1735
|
+
return blocks.map((block) => block.type !== "image" || block.sourceRef === undefined ? block : {
|
|
1736
|
+
type: "image",
|
|
1737
|
+
mimeType: block.mimeType,
|
|
1738
|
+
sourceRef: block.sourceRef
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
function promptImageDataBytes(prompt) {
|
|
1742
|
+
return prompt.reduce((total, block) => {
|
|
1743
|
+
if (block.type !== "image")
|
|
1744
|
+
return total;
|
|
1745
|
+
if (typeof block.data === "string")
|
|
1746
|
+
return total + block.data.length;
|
|
1747
|
+
const byteLength = block.bytes?.byteLength ?? block.byteLength ?? blobByteLength(block.data) ?? 0;
|
|
1748
|
+
return total + Math.ceil(byteLength / 3) * 4;
|
|
1749
|
+
}, 0);
|
|
1750
|
+
}
|
|
1751
|
+
function byteView(value) {
|
|
1752
|
+
if (value instanceof ArrayBuffer)
|
|
1753
|
+
return new Uint8Array(value);
|
|
1754
|
+
if (ArrayBuffer.isView(value))
|
|
1755
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
1756
|
+
return null;
|
|
1757
|
+
}
|
|
1758
|
+
var base64Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
1759
|
+
function canonicalBase64ByteLength(value) {
|
|
1760
|
+
if (value.length === 0 || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(value))
|
|
1761
|
+
return -1;
|
|
1762
|
+
const groups = value.length / 4;
|
|
1763
|
+
if (value.endsWith("==")) {
|
|
1764
|
+
return (base64Alphabet.indexOf(value[value.length - 3]) & 15) === 0 ? groups * 3 - 2 : -1;
|
|
1765
|
+
}
|
|
1766
|
+
if (value.endsWith("=")) {
|
|
1767
|
+
return (base64Alphabet.indexOf(value[value.length - 2]) & 3) === 0 ? groups * 3 - 1 : -1;
|
|
1768
|
+
}
|
|
1769
|
+
return groups * 3;
|
|
1770
|
+
}
|
|
1771
|
+
function requireImageMimeType(mimeType, index) {
|
|
1772
|
+
if (typeof mimeType !== "string" || mimeType.length === 0 || mimeType.length > 128) {
|
|
1773
|
+
throw new TypeError(`image prompt block ${index} requires a mimeType`);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function checkImageByteLength(byteLength, index) {
|
|
1777
|
+
if (byteLength > maxPromptImageBytes) {
|
|
1778
|
+
throw new RangeError(`image prompt block ${index} exceeds the ${maxPromptImageBytes} byte per-image libfx limit`);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
function checkPromptImagesByteLength(byteLength) {
|
|
1782
|
+
if (byteLength > maxPromptImagesBytes) {
|
|
1783
|
+
throw new RangeError(`prompt images exceed the ${maxPromptImagesBytes} byte libfx limit`);
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
function normalizePromptImage(block, index) {
|
|
1787
|
+
const sourceRef = normalizeImageSourceRef(block.sourceRef, `image prompt block ${index}`);
|
|
1788
|
+
const reference = sourceRef === undefined ? {} : { sourceRef };
|
|
1789
|
+
if (block.data === undefined && sourceRef !== undefined) {
|
|
1790
|
+
requireImageMimeType(block.mimeType, index);
|
|
1791
|
+
return { type: "image", mimeType: block.mimeType, ...reference };
|
|
1792
|
+
}
|
|
1793
|
+
const size = blobByteLength(block.data);
|
|
1794
|
+
if (size !== null) {
|
|
1795
|
+
const mimeType = block.data.type;
|
|
1796
|
+
if (block.mimeType !== undefined && block.mimeType !== mimeType) {
|
|
1797
|
+
throw new TypeError(`image prompt block ${index} mimeType disagrees with Blob.type`);
|
|
1798
|
+
}
|
|
1799
|
+
requireImageMimeType(mimeType, index);
|
|
1800
|
+
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
1801
|
+
throw new TypeError(`image prompt block ${index} requires a non-empty Blob with a valid size`);
|
|
1802
|
+
}
|
|
1803
|
+
return { type: "image", source: "blob", data: block.data, mimeType, byteLength: size, ...reference };
|
|
1804
|
+
}
|
|
1805
|
+
if (typeof block.data === "string" && block.data.length > 0) {
|
|
1806
|
+
requireImageMimeType(block.mimeType, index);
|
|
1807
|
+
const byteLength = canonicalBase64ByteLength(block.data);
|
|
1808
|
+
if (byteLength <= 0)
|
|
1809
|
+
throw new TypeError(`image prompt block ${index} requires canonical base64 data`);
|
|
1810
|
+
return { type: "image", source: "base64", data: block.data, mimeType: block.mimeType, byteLength, ...reference };
|
|
1811
|
+
}
|
|
1812
|
+
const bytes = typeof block.data === "string" ? null : byteView(block.data);
|
|
1813
|
+
if (!bytes || bytes.byteLength === 0) {
|
|
1814
|
+
throw new TypeError(`image prompt block ${index} requires base64 data, bytes, or a Blob`);
|
|
1815
|
+
}
|
|
1816
|
+
requireImageMimeType(block.mimeType, index);
|
|
1817
|
+
return { type: "image", source: "bytes", data: bytes, mimeType: block.mimeType, byteLength: bytes.byteLength, ...reference };
|
|
1818
|
+
}
|
|
1819
|
+
function normalizePromptInput(input, { deferImageLimits = false } = {}) {
|
|
1663
1820
|
if (typeof input === "string")
|
|
1664
1821
|
return [{ type: "text", text: input }];
|
|
1665
1822
|
if (!Array.isArray(input))
|
|
1666
1823
|
throw new TypeError("prompt input must be a string or an array of prompt blocks");
|
|
1667
1824
|
let imageCount = 0;
|
|
1668
1825
|
let imageBytes = 0;
|
|
1669
|
-
|
|
1826
|
+
let prompt = input.map((block, index) => {
|
|
1670
1827
|
if (!block || typeof block !== "object")
|
|
1671
1828
|
throw new TypeError(`prompt block ${index} must be an object`);
|
|
1672
1829
|
if (block.type === "image") {
|
|
1673
|
-
const
|
|
1674
|
-
const blob = size !== null;
|
|
1675
|
-
if (!blob && (typeof block.data !== "string" || block.data.length === 0)) {
|
|
1676
|
-
throw new TypeError(`image prompt block ${index} requires base64 data or a Blob`);
|
|
1677
|
-
}
|
|
1678
|
-
const mimeType = blob ? block.data.type : block.mimeType;
|
|
1679
|
-
if (blob && block.mimeType !== undefined && block.mimeType !== mimeType) {
|
|
1680
|
-
throw new TypeError(`image prompt block ${index} mimeType disagrees with Blob.type`);
|
|
1681
|
-
}
|
|
1682
|
-
if (typeof mimeType !== "string" || mimeType.length === 0 || mimeType.length > 128) {
|
|
1683
|
-
throw new TypeError(`image prompt block ${index} requires a mimeType`);
|
|
1684
|
-
}
|
|
1685
|
-
if (blob && (!Number.isSafeInteger(size) || size <= 0)) {
|
|
1686
|
-
throw new TypeError(`image prompt block ${index} requires a non-empty Blob with a valid size`);
|
|
1687
|
-
}
|
|
1688
|
-
const encodedLength = blob ? Math.ceil(size / 3) * 4 : block.data.length;
|
|
1689
|
-
if (encodedLength > maxPromptImageDataBytes) {
|
|
1690
|
-
throw new RangeError(`image prompt block ${index} exceeds the ${maxPromptImageDataBytes} byte per-image libfx limit`);
|
|
1691
|
-
}
|
|
1830
|
+
const image = normalizePromptImage(block, index);
|
|
1692
1831
|
imageCount += 1;
|
|
1693
1832
|
if (imageCount > maxPromptImages) {
|
|
1694
1833
|
throw new RangeError(`prompt cannot contain more than ${maxPromptImages} images`);
|
|
1695
1834
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1835
|
+
if (!deferImageLimits) {
|
|
1836
|
+
if (image.byteLength > maxPromptImageBytes && image.sourceRef !== undefined) {
|
|
1837
|
+
return omitReferencedImageData([image])[0];
|
|
1838
|
+
}
|
|
1839
|
+
checkImageByteLength(image.byteLength ?? 0, index);
|
|
1840
|
+
imageBytes += image.byteLength ?? 0;
|
|
1699
1841
|
}
|
|
1700
|
-
return
|
|
1842
|
+
return image;
|
|
1701
1843
|
}
|
|
1702
1844
|
if (block.type === "text") {
|
|
1703
1845
|
if (typeof block.text !== "string")
|
|
@@ -1714,52 +1856,89 @@ function normalizePromptInput(input) {
|
|
|
1714
1856
|
}
|
|
1715
1857
|
throw new TypeError(`unsupported prompt block type: ${String(block.type)}`);
|
|
1716
1858
|
});
|
|
1859
|
+
if (!deferImageLimits) {
|
|
1860
|
+
if (imageBytes > maxPromptImagesBytes) {
|
|
1861
|
+
prompt = omitReferencedImageData(prompt);
|
|
1862
|
+
imageBytes = prompt.reduce((total, block) => total + (block.type === "image" ? block.byteLength ?? 0 : 0), 0);
|
|
1863
|
+
}
|
|
1864
|
+
checkPromptImagesByteLength(imageBytes);
|
|
1865
|
+
if (promptFrameSize(prompt) > maxPromptFrameBytes)
|
|
1866
|
+
prompt = omitReferencedImageData(prompt);
|
|
1867
|
+
}
|
|
1868
|
+
return prompt;
|
|
1869
|
+
}
|
|
1870
|
+
function promptFrameSize(prompt, countImages = true) {
|
|
1871
|
+
const encodedImageBytes = countImages ? promptImageDataBytes(prompt) : 0;
|
|
1872
|
+
const projected = prompt.map((block) => block.type !== "image" ? block : {
|
|
1873
|
+
type: "image",
|
|
1874
|
+
mimeType: block.mimeType,
|
|
1875
|
+
...block.sourceRef === undefined ? {} : { sourceRef: block.sourceRef },
|
|
1876
|
+
...block.data === undefined && block.bytes === undefined ? {} : { _meta: { fx: { attachment: 4294967295 } } }
|
|
1877
|
+
});
|
|
1878
|
+
return encoder.encode(JSON.stringify({ sessionId: "", prompt: projected })).length + encodedImageBytes + promptFrameEnvelopeBytes;
|
|
1879
|
+
}
|
|
1880
|
+
function checkPromptFrameSize(prompt, countImages) {
|
|
1881
|
+
if (promptFrameSize(prompt, countImages) > maxPromptFrameBytes) {
|
|
1882
|
+
throw new RangeError(`prompt exceeds the ${maxPromptFrameBytes} byte libfx frame limit`);
|
|
1883
|
+
}
|
|
1717
1884
|
}
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
if (!Number.isSafeInteger(size))
|
|
1725
|
-
throw new TypeError("image prompt requires a valid Blob size");
|
|
1726
|
-
blobDataBytes += Math.ceil(size / 3) * 4;
|
|
1727
|
-
return { type: "image", data: "", mimeType: block.mimeType };
|
|
1885
|
+
function preparePromptImages(blocks) {
|
|
1886
|
+
return blocks.map((block) => block.type !== "image" || block.data === undefined ? block : {
|
|
1887
|
+
type: "image",
|
|
1888
|
+
mimeType: block.mimeType,
|
|
1889
|
+
bytes: block.source === "base64" ? base64ToBytes(block.data) : block.data,
|
|
1890
|
+
...block.sourceRef === undefined ? {} : { sourceRef: block.sourceRef }
|
|
1728
1891
|
});
|
|
1729
|
-
return encoder.encode(JSON.stringify({ sessionId: "", prompt: projected })).length + blobDataBytes + promptFrameEnvelopeBytes;
|
|
1730
1892
|
}
|
|
1731
|
-
|
|
1732
|
-
const
|
|
1893
|
+
function resizedPromptImage(value, index) {
|
|
1894
|
+
const bytes = byteView(value?.bytes);
|
|
1895
|
+
if (!bytes || bytes.byteLength === 0 || typeof value.mimeType !== "string" || value.mimeType.length === 0 || value.mimeType.length > 128) {
|
|
1896
|
+
throw new TypeError(`resizeImage must return non-empty bytes and a mimeType for image prompt block ${index}`);
|
|
1897
|
+
}
|
|
1898
|
+
return { bytes: bytes.slice(), mimeType: value.mimeType };
|
|
1899
|
+
}
|
|
1900
|
+
async function materializePromptImages(blocks, isCancelled, resizeImage) {
|
|
1901
|
+
let prepared = [];
|
|
1733
1902
|
let imageBytes = 0;
|
|
1734
|
-
for (
|
|
1903
|
+
for (let index = 0;index < blocks.length; index++) {
|
|
1735
1904
|
if (isCancelled())
|
|
1736
1905
|
return null;
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
}
|
|
1741
|
-
if (typeof block.data === "string") {
|
|
1742
|
-
imageBytes += block.data.length;
|
|
1743
|
-
if (imageBytes > maxPromptImagesBytes) {
|
|
1744
|
-
throw new RangeError(`prompt images exceed the ${maxPromptImagesBytes} byte libfx frame limit`);
|
|
1745
|
-
}
|
|
1746
|
-
encoded.push(block);
|
|
1906
|
+
const block = blocks[index];
|
|
1907
|
+
if (block.type !== "image" || block.data === undefined) {
|
|
1908
|
+
prepared.push(block);
|
|
1747
1909
|
continue;
|
|
1748
1910
|
}
|
|
1749
|
-
|
|
1911
|
+
let image = block.source === "blob" ? { mimeType: block.mimeType, bytes: new Uint8Array(await block.data.arrayBuffer()) } : preparePromptImages([block])[0];
|
|
1750
1912
|
if (isCancelled())
|
|
1751
1913
|
return null;
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1914
|
+
if (resizeImage) {
|
|
1915
|
+
image = resizedPromptImage(await resizeImage({ bytes: image.bytes, mimeType: image.mimeType }), index);
|
|
1916
|
+
if (isCancelled())
|
|
1917
|
+
return null;
|
|
1755
1918
|
}
|
|
1756
|
-
|
|
1919
|
+
image = {
|
|
1920
|
+
type: "image",
|
|
1921
|
+
mimeType: image.mimeType,
|
|
1922
|
+
bytes: image.bytes,
|
|
1923
|
+
...block.sourceRef === undefined ? {} : { sourceRef: block.sourceRef }
|
|
1924
|
+
};
|
|
1925
|
+
if (image.bytes.byteLength > maxPromptImageBytes && image.sourceRef !== undefined) {
|
|
1926
|
+
image = omitReferencedImageData([image])[0];
|
|
1927
|
+
}
|
|
1928
|
+
checkImageByteLength(image.bytes?.byteLength ?? 0, index);
|
|
1929
|
+
imageBytes += image.bytes?.byteLength ?? 0;
|
|
1930
|
+
prepared.push(image);
|
|
1757
1931
|
if (imageBytes > maxPromptImagesBytes) {
|
|
1758
|
-
|
|
1932
|
+
prepared = omitReferencedImageData(prepared);
|
|
1933
|
+
blocks = omitReferencedImageData(blocks);
|
|
1934
|
+
imageBytes = prepared.reduce((total, entry) => total + (entry.bytes?.byteLength ?? 0), 0);
|
|
1759
1935
|
}
|
|
1760
|
-
|
|
1936
|
+
checkPromptImagesByteLength(imageBytes);
|
|
1761
1937
|
}
|
|
1762
|
-
|
|
1938
|
+
if (promptFrameSize(prepared) > maxPromptFrameBytes)
|
|
1939
|
+
prepared = omitReferencedImageData(prepared);
|
|
1940
|
+
checkPromptFrameSize(prepared, true);
|
|
1941
|
+
return prepared;
|
|
1763
1942
|
}
|
|
1764
1943
|
function normalizeSteeringInput(input) {
|
|
1765
1944
|
const blocks = normalizePromptInput(input);
|
|
@@ -1846,18 +2025,31 @@ function hostToolContent(value) {
|
|
|
1846
2025
|
if (typeof value.text !== "string" || !Array.isArray(value.images) || value.images.length > 8) {
|
|
1847
2026
|
throw new TypeError("invalid typed tool result");
|
|
1848
2027
|
}
|
|
1849
|
-
let
|
|
1850
|
-
|
|
1851
|
-
|
|
2028
|
+
let images = value.images.map((image) => {
|
|
2029
|
+
if (image?.type !== "image")
|
|
2030
|
+
throw new TypeError("invalid tool image");
|
|
2031
|
+
const sourceRef = normalizeImageSourceRef(image.sourceRef, "tool image");
|
|
2032
|
+
const referenceOnly = image.data === undefined && sourceRef !== undefined;
|
|
2033
|
+
if (!referenceOnly && typeof image.data !== "string" || typeof image.mimeType !== "string" || image.mimeType.length === 0 || image.mimeType.length > 128 || image.data?.length > maxPromptImageDataBytes && sourceRef === undefined) {
|
|
1852
2034
|
throw new TypeError("invalid tool image");
|
|
1853
2035
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
2036
|
+
return {
|
|
2037
|
+
type: "image",
|
|
2038
|
+
...!referenceOnly && image.data.length <= maxPromptImageDataBytes ? { data: image.data } : {},
|
|
2039
|
+
mimeType: image.mimeType,
|
|
2040
|
+
...sourceRef === undefined ? {} : { sourceRef }
|
|
2041
|
+
};
|
|
1858
2042
|
});
|
|
1859
|
-
|
|
1860
|
-
|
|
2043
|
+
if (promptImageDataBytes(images) > maxPromptImagesDataBytes)
|
|
2044
|
+
images = omitReferencedImageData(images);
|
|
2045
|
+
if (promptImageDataBytes(images) > maxPromptImagesDataBytes)
|
|
2046
|
+
throw new RangeError("tool images exceed the result limit");
|
|
2047
|
+
let content = JSON.stringify({ text: value.text, images });
|
|
2048
|
+
if (encoder.encode(content).length > maxPromptImagesDataBytes) {
|
|
2049
|
+
images = omitReferencedImageData(images);
|
|
2050
|
+
content = JSON.stringify({ text: value.text, images });
|
|
2051
|
+
}
|
|
2052
|
+
if (encoder.encode(content).length > maxPromptImagesDataBytes)
|
|
1861
2053
|
throw new RangeError("typed tool result exceeds the result limit");
|
|
1862
2054
|
return { content, rich: true, isError: value.isError === true };
|
|
1863
2055
|
}
|
|
@@ -1880,13 +2072,6 @@ function checkpointBytes(value) {
|
|
|
1880
2072
|
}
|
|
1881
2073
|
throw new TypeError("checkpoint must be an ArrayBuffer or typed array");
|
|
1882
2074
|
}
|
|
1883
|
-
function bytesToBase64(value) {
|
|
1884
|
-
let binary = "";
|
|
1885
|
-
for (let offset = 0;offset < value.length; offset += 32768) {
|
|
1886
|
-
binary += String.fromCharCode(...value.subarray(offset, offset + 32768));
|
|
1887
|
-
}
|
|
1888
|
-
return btoa(binary);
|
|
1889
|
-
}
|
|
1890
2075
|
function base64ToBytes(value) {
|
|
1891
2076
|
const binary = atob(value);
|
|
1892
2077
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -1899,6 +2084,9 @@ async function createFxAgent(options = {}) {
|
|
|
1899
2084
|
const hostTools = normalizeHostTools(options.tools);
|
|
1900
2085
|
const instructions = normalizeInstructions(options.instructions);
|
|
1901
2086
|
const initialCheckpoint = checkpointBytes(options.checkpoint);
|
|
2087
|
+
if (initialCheckpoint && initialCheckpoint.byteLength > maxCheckpointBytes) {
|
|
2088
|
+
throw new Error("libfx checkpoint is too large");
|
|
2089
|
+
}
|
|
1902
2090
|
const pending = new Map;
|
|
1903
2091
|
let nextId = 1;
|
|
1904
2092
|
let sessionId = null;
|
|
@@ -2061,11 +2249,49 @@ async function createFxAgent(options = {}) {
|
|
|
2061
2249
|
reject(error);
|
|
2062
2250
|
}
|
|
2063
2251
|
});
|
|
2252
|
+
let nextAttachmentId = 1;
|
|
2253
|
+
const attachBytes = (payloads) => {
|
|
2254
|
+
if (typeof runtime.writeAttachment !== "function")
|
|
2255
|
+
throw new Error("fx runtime does not accept binary attachments");
|
|
2256
|
+
runtime.discardAttachments?.();
|
|
2257
|
+
return payloads.map((bytes) => {
|
|
2258
|
+
const id = nextAttachmentId;
|
|
2259
|
+
nextAttachmentId = id === 2147483647 ? 1 : id + 1;
|
|
2260
|
+
runtime.writeAttachment(id, bytes);
|
|
2261
|
+
return id;
|
|
2262
|
+
});
|
|
2263
|
+
};
|
|
2264
|
+
let checkpointTail = null;
|
|
2265
|
+
let pendingCheckpoints = 0;
|
|
2266
|
+
async function takeCheckpoint() {
|
|
2267
|
+
if (closing)
|
|
2268
|
+
throw new Error("fx agent is closed");
|
|
2269
|
+
if (activeTurn)
|
|
2270
|
+
throw new Error("cannot checkpoint while a prompt is active");
|
|
2271
|
+
const response = await request("libfx/checkpoint", { sessionId });
|
|
2272
|
+
const id = response?.checkpointAttachment;
|
|
2273
|
+
const bytes = Number.isSafeInteger(id) && id > 0 ? runtime.takeAttachment?.(id) : null;
|
|
2274
|
+
if (!(bytes instanceof Uint8Array) || bytes.byteLength === 0)
|
|
2275
|
+
throw new Error("fx returned an invalid checkpoint");
|
|
2276
|
+
return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2277
|
+
}
|
|
2278
|
+
const sendPrompt = (blocks) => {
|
|
2279
|
+
const images = blocks.filter((block) => block.type === "image" && block.bytes !== undefined);
|
|
2280
|
+
const ids = images.length ? attachBytes(images.map((block) => block.bytes)) : [];
|
|
2281
|
+
let next = 0;
|
|
2282
|
+
const prompt = blocks.map((block) => block.type !== "image" ? block : {
|
|
2283
|
+
type: "image",
|
|
2284
|
+
mimeType: block.mimeType,
|
|
2285
|
+
...block.sourceRef === undefined ? {} : { sourceRef: block.sourceRef },
|
|
2286
|
+
...block.bytes === undefined ? {} : { _meta: { fx: { attachment: ids[next++] } } }
|
|
2287
|
+
});
|
|
2288
|
+
return request("session/prompt", { sessionId, prompt });
|
|
2289
|
+
};
|
|
2064
2290
|
runtime.exited.then((code) => {
|
|
2065
2291
|
closing = true;
|
|
2066
2292
|
const error = runtime.error ?? new Error(`fx-core exited with code ${code} before completing the ACP request`);
|
|
2067
2293
|
coreExitError = error;
|
|
2068
|
-
activeTurn?.
|
|
2294
|
+
activeTurn?.failImagePrep(error);
|
|
2069
2295
|
for (const waiter of pending.values())
|
|
2070
2296
|
waiter.reject(error);
|
|
2071
2297
|
pending.clear();
|
|
@@ -2105,6 +2331,10 @@ async function createFxAgent(options = {}) {
|
|
|
2105
2331
|
if (cancelled || closing)
|
|
2106
2332
|
return;
|
|
2107
2333
|
const response = { jsonrpc: "2.0", id: message.id, result: { content, isError, ...rich ? { contentType: "rich" } : {} } };
|
|
2334
|
+
if (rich && encoder.encode(JSON.stringify(response)).length + 1 > maxPromptFrameBytes) {
|
|
2335
|
+
const result = JSON.parse(content);
|
|
2336
|
+
response.result.content = JSON.stringify({ ...result, images: omitReferencedImageData(result.images) });
|
|
2337
|
+
}
|
|
2108
2338
|
if (encoder.encode(JSON.stringify(response)).length + 1 > 8 * 1024 * 1024) {
|
|
2109
2339
|
response.result = { content: "Host tool result exceeded the response frame limit", isError: true };
|
|
2110
2340
|
}
|
|
@@ -2130,10 +2360,8 @@ async function createFxAgent(options = {}) {
|
|
|
2130
2360
|
const sessionResult = await request("libfx/new");
|
|
2131
2361
|
sessionId = sessionResult.sessionId;
|
|
2132
2362
|
if (initialCheckpoint) {
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
checkpoint: bytesToBase64(initialCheckpoint)
|
|
2136
|
-
});
|
|
2363
|
+
const [checkpointAttachment] = attachBytes([initialCheckpoint]);
|
|
2364
|
+
await request("libfx/restore", { sessionId, checkpointAttachment });
|
|
2137
2365
|
}
|
|
2138
2366
|
} catch (error) {
|
|
2139
2367
|
closing = true;
|
|
@@ -2156,15 +2384,20 @@ async function createFxAgent(options = {}) {
|
|
|
2156
2384
|
throw new Error("a prompt is already in progress for this session");
|
|
2157
2385
|
return normalizeTurn(startTurn(input, promptOptions));
|
|
2158
2386
|
},
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2387
|
+
checkpoint() {
|
|
2388
|
+
const previous = pendingCheckpoints > 0 ? checkpointTail : null;
|
|
2389
|
+
pendingCheckpoints++;
|
|
2390
|
+
let release;
|
|
2391
|
+
checkpointTail = new Promise((resolve) => {
|
|
2392
|
+
release = resolve;
|
|
2393
|
+
});
|
|
2394
|
+
const run = previous ? previous.then(takeCheckpoint) : takeCheckpoint();
|
|
2395
|
+
const settle = () => {
|
|
2396
|
+
pendingCheckpoints--;
|
|
2397
|
+
release();
|
|
2398
|
+
};
|
|
2399
|
+
run.then(settle, settle);
|
|
2400
|
+
return run;
|
|
2168
2401
|
},
|
|
2169
2402
|
async close() {
|
|
2170
2403
|
if (closing) {
|
|
@@ -2288,11 +2521,12 @@ async function createFxAgent(options = {}) {
|
|
|
2288
2521
|
return { inputTruncated: true, inputPreview: decoder.decode(utf8Prefix(bytes, maxToolStartInputBytes)) };
|
|
2289
2522
|
}
|
|
2290
2523
|
function startTurn(input, promptOptions) {
|
|
2291
|
-
const
|
|
2292
|
-
const
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2524
|
+
const resizeImage = options.resizeImage;
|
|
2525
|
+
const normalized = normalizePromptInput(input, { deferImageLimits: resizeImage !== undefined });
|
|
2526
|
+
const hasPixels = normalized.some((block) => block.type === "image" && block.data !== undefined);
|
|
2527
|
+
const asyncImages = hasPixels && (resizeImage !== undefined || normalized.some((block) => block.type === "image" && block.source === "blob"));
|
|
2528
|
+
checkPromptFrameSize(normalized, resizeImage === undefined);
|
|
2529
|
+
const prompt = asyncImages ? normalized.map((block) => block.type === "image" && block.source === "bytes" ? { ...block, data: block.data.slice() } : block) : preparePromptImages(normalized);
|
|
2296
2530
|
const signal = promptOptions.signal;
|
|
2297
2531
|
if (signal !== undefined && (typeof signal?.addEventListener !== "function" || typeof signal?.removeEventListener !== "function"))
|
|
2298
2532
|
throw new TypeError("prompt signal must be an AbortSignal");
|
|
@@ -2304,10 +2538,10 @@ async function createFxAgent(options = {}) {
|
|
|
2304
2538
|
let terminalError;
|
|
2305
2539
|
let reportedPressure = false;
|
|
2306
2540
|
let discardedBytes = 0;
|
|
2307
|
-
let
|
|
2308
|
-
let
|
|
2541
|
+
let cancelImagePrep = null;
|
|
2542
|
+
let rejectImagePrep = null;
|
|
2309
2543
|
let resolvePromptStart = null;
|
|
2310
|
-
const promptStarted =
|
|
2544
|
+
const promptStarted = asyncImages ? new Promise((resolve) => {
|
|
2311
2545
|
resolvePromptStart = resolve;
|
|
2312
2546
|
}) : null;
|
|
2313
2547
|
let pendingSteeringCount = 0;
|
|
@@ -2348,10 +2582,10 @@ async function createFxAgent(options = {}) {
|
|
|
2348
2582
|
get cancelled() {
|
|
2349
2583
|
return cancelled;
|
|
2350
2584
|
},
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2585
|
+
failImagePrep(error) {
|
|
2586
|
+
rejectImagePrep?.(error);
|
|
2587
|
+
rejectImagePrep = null;
|
|
2588
|
+
cancelImagePrep = null;
|
|
2355
2589
|
},
|
|
2356
2590
|
promptWritten() {
|
|
2357
2591
|
resolvePromptStart?.(true);
|
|
@@ -2404,9 +2638,9 @@ async function createFxAgent(options = {}) {
|
|
|
2404
2638
|
if (finished || cancelled)
|
|
2405
2639
|
return;
|
|
2406
2640
|
cancelled = true;
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2641
|
+
cancelImagePrep?.();
|
|
2642
|
+
cancelImagePrep = null;
|
|
2643
|
+
rejectImagePrep = null;
|
|
2410
2644
|
resolvePromptStart?.(false);
|
|
2411
2645
|
resolvePromptStart = null;
|
|
2412
2646
|
runtime.closeSteering?.();
|
|
@@ -2455,31 +2689,37 @@ async function createFxAgent(options = {}) {
|
|
|
2455
2689
|
runtime.openSteering?.();
|
|
2456
2690
|
const abort = () => turn.cancel();
|
|
2457
2691
|
signal?.addEventListener("abort", abort, { once: true });
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
|
|
2692
|
+
const imagePrepCancelled = asyncImages ? new Promise((resolve, reject) => {
|
|
2693
|
+
cancelImagePrep = () => resolve(null);
|
|
2694
|
+
rejectImagePrep = reject;
|
|
2461
2695
|
}) : null;
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2696
|
+
let response;
|
|
2697
|
+
if (asyncImages) {
|
|
2698
|
+
response = Promise.race([
|
|
2699
|
+
Promise.resolve().then(() => materializePromptImages(prompt, () => cancelled || closing, resizeImage)),
|
|
2700
|
+
imagePrepCancelled
|
|
2701
|
+
]).then((prepared) => {
|
|
2702
|
+
cancelImagePrep = null;
|
|
2703
|
+
rejectImagePrep = null;
|
|
2704
|
+
if (coreExitError && !cancelled)
|
|
2705
|
+
throw coreExitError;
|
|
2706
|
+
if (prepared === null || cancelled || closing) {
|
|
2707
|
+
resolvePromptStart?.(false);
|
|
2708
|
+
resolvePromptStart = null;
|
|
2709
|
+
return { stopReason: "cancelled" };
|
|
2710
|
+
}
|
|
2711
|
+
return sendPrompt(prepared);
|
|
2712
|
+
});
|
|
2713
|
+
} else {
|
|
2714
|
+
try {
|
|
2715
|
+
response = sendPrompt(prompt);
|
|
2716
|
+
} catch (error) {
|
|
2717
|
+
response = Promise.reject(error);
|
|
2477
2718
|
}
|
|
2478
|
-
|
|
2479
|
-
}) : request("session/prompt", { sessionId, prompt });
|
|
2719
|
+
}
|
|
2480
2720
|
turn.result = response.then((value) => ({ stopReason: cancelled ? "cancelled" : value.stopReason, usage: value.usage })).catch((error) => {
|
|
2481
|
-
|
|
2482
|
-
|
|
2721
|
+
cancelImagePrep = null;
|
|
2722
|
+
rejectImagePrep = null;
|
|
2483
2723
|
resolvePromptStart?.(error);
|
|
2484
2724
|
resolvePromptStart = null;
|
|
2485
2725
|
if (cancelled && error.message === "Cancelled")
|
|
@@ -2513,7 +2753,7 @@ async function createFxAgent(options = {}) {
|
|
|
2513
2753
|
|
|
2514
2754
|
// sdk/node.js
|
|
2515
2755
|
var libfxApiVersion = 2;
|
|
2516
|
-
var nativeCoreApiVersion =
|
|
2756
|
+
var nativeCoreApiVersion = 4;
|
|
2517
2757
|
var fetchOperationStale = 0;
|
|
2518
2758
|
var fetchOperationApplied = 1;
|
|
2519
2759
|
var fetchOperationBackpressure = 2;
|
|
@@ -2804,7 +3044,12 @@ async function getBackendInfo(value = {}) {
|
|
|
2804
3044
|
}
|
|
2805
3045
|
}
|
|
2806
3046
|
function createNativeCoreRuntime(addon, options) {
|
|
2807
|
-
const { apiKey, model, effort, fast, gatewayChatUrl } = options;
|
|
3047
|
+
const { apiKey, model, effort, fast, ultrafast, gatewayChatUrl } = options;
|
|
3048
|
+
if (ultrafast !== undefined && addon.supportsUltrafast !== true) {
|
|
3049
|
+
const error = new Error("native addon does not support the ultrafast option");
|
|
3050
|
+
error.code = "LIBFX_NATIVE_CAPABILITY_UNAVAILABLE";
|
|
3051
|
+
throw error;
|
|
3052
|
+
}
|
|
2808
3053
|
const core = addon.createCore({
|
|
2809
3054
|
apiKey,
|
|
2810
3055
|
home: options.home ?? import_node_os.homedir(),
|
|
@@ -2812,6 +3057,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2812
3057
|
...model === undefined ? {} : { model },
|
|
2813
3058
|
...effort === undefined ? {} : { effort },
|
|
2814
3059
|
...fast === undefined ? {} : { fast },
|
|
3060
|
+
...ultrafast === undefined ? {} : { ultrafast },
|
|
2815
3061
|
...gatewayChatUrl === undefined ? {} : { gatewayChatUrl }
|
|
2816
3062
|
});
|
|
2817
3063
|
let readyFd;
|
|
@@ -2858,7 +3104,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2858
3104
|
readySocket.destroy();
|
|
2859
3105
|
readyClosed.then(() => exitedResolve(code));
|
|
2860
3106
|
};
|
|
2861
|
-
const pumpFetch = async (request) => {
|
|
3107
|
+
const pumpFetch = async (request, body) => {
|
|
2862
3108
|
const controller = new AbortController;
|
|
2863
3109
|
const state = { handle: request.handle, controller };
|
|
2864
3110
|
fetchState = state;
|
|
@@ -2866,7 +3112,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2866
3112
|
const response = await (options.fetch ?? globalThis.fetch)(request.url, {
|
|
2867
3113
|
method: request.method,
|
|
2868
3114
|
headers: new Headers(JSON.parse(request.headers).map(({ name, value }) => [name, value])),
|
|
2869
|
-
body:
|
|
3115
|
+
body: body.length ? body : undefined,
|
|
2870
3116
|
signal: controller.signal
|
|
2871
3117
|
});
|
|
2872
3118
|
const started = addon.startCoreFetchResponse(core, state.handle, response.status);
|
|
@@ -2923,7 +3169,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2923
3169
|
} else {
|
|
2924
3170
|
const fetchRequest = addon.takeCoreFetch(core);
|
|
2925
3171
|
if (fetchRequest)
|
|
2926
|
-
pumpFetch(JSON.parse(fetchRequest.toString("utf8")));
|
|
3172
|
+
pumpFetch(JSON.parse(fetchRequest.request.toString("utf8")), fetchRequest.body);
|
|
2927
3173
|
}
|
|
2928
3174
|
if (addon.coreExitCode(core) !== 0) {
|
|
2929
3175
|
finish(1, new Error("native output delivery failed"));
|
|
@@ -2984,6 +3230,15 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2984
3230
|
write(data) {
|
|
2985
3231
|
addon.writeCore(core, Buffer.from(data));
|
|
2986
3232
|
},
|
|
3233
|
+
writeAttachment(id, bytes) {
|
|
3234
|
+
addon.writeCoreAttachment(core, id, Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength));
|
|
3235
|
+
},
|
|
3236
|
+
takeAttachment(id) {
|
|
3237
|
+
return addon.takeCoreAttachment(core, id);
|
|
3238
|
+
},
|
|
3239
|
+
discardAttachments() {
|
|
3240
|
+
addon.discardCoreAttachments(core);
|
|
3241
|
+
},
|
|
2987
3242
|
closeStdin() {
|
|
2988
3243
|
addon.closeCore(core);
|
|
2989
3244
|
},
|