libfx 0.0.11 → 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 +205 -39
- package/fx-core.wasm +0 -0
- package/fx-sdk.js +501 -83
- 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 +503 -88
- package/node.js +23 -10
- 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");
|
|
@@ -264,10 +277,30 @@ function normalizeAgentOptions(value) {
|
|
|
264
277
|
throw new TypeError("createFxAgent() does not accept env; pass apiKey and model directly");
|
|
265
278
|
}
|
|
266
279
|
options.apiKey = boundedString(options.apiKey, "apiKey", maxApiKeyBytes, true);
|
|
267
|
-
options.model
|
|
268
|
-
|
|
269
|
-
|
|
280
|
+
if (options.model !== null && typeof options.model === "object" && !Array.isArray(options.model)) {
|
|
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");
|
|
283
|
+
}
|
|
284
|
+
const model = options.model;
|
|
285
|
+
for (const name of Object.keys(model)) {
|
|
286
|
+
if (name !== "id" && name !== "effort" && name !== "fast" && name !== "ultrafast") {
|
|
287
|
+
throw new TypeError(`unsupported model option: ${name}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
options.model = boundedString(model.id, "model.id", maxModelBytes, true);
|
|
291
|
+
options.effort = normalizeEffort(model.effort);
|
|
292
|
+
options.fast = normalizeFast(model.fast);
|
|
293
|
+
options.ultrafast = normalizeUltrafast(model.ultrafast);
|
|
294
|
+
} else {
|
|
295
|
+
options.model = boundedString(options.model, "model", maxModelBytes, false);
|
|
296
|
+
options.effort = normalizeEffort(options.effort);
|
|
297
|
+
options.fast = normalizeFast(options.fast);
|
|
298
|
+
options.ultrafast = normalizeUltrafast(options.ultrafast);
|
|
299
|
+
}
|
|
270
300
|
validateGatewayChatUrl(options.gatewayChatUrl);
|
|
301
|
+
if (options.resizeImage !== undefined && typeof options.resizeImage !== "function") {
|
|
302
|
+
throw new TypeError("resizeImage must be a function");
|
|
303
|
+
}
|
|
271
304
|
return options;
|
|
272
305
|
}
|
|
273
306
|
function agentEnvironment(options) {
|
|
@@ -276,15 +309,17 @@ function agentEnvironment(options) {
|
|
|
276
309
|
...options.model === undefined ? {} : { FX_MODEL: options.model },
|
|
277
310
|
...options.effort === undefined ? {} : { FX_EFFORT: options.effort },
|
|
278
311
|
...options.fast === undefined ? {} : { FX_FAST: options.fast ? "true" : "false" },
|
|
312
|
+
...options.ultrafast === undefined ? {} : { FX_ULTRAFAST: options.ultrafast ? "true" : "false" },
|
|
279
313
|
...options.gatewayChatUrl === undefined ? {} : { FX_GATEWAY_CHAT_URL: options.gatewayChatUrl }
|
|
280
314
|
};
|
|
281
315
|
}
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
error.
|
|
316
|
+
function agentRpcError(response) {
|
|
317
|
+
const error = new Error(response.message);
|
|
318
|
+
const data = response.data;
|
|
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")) {
|
|
320
|
+
error.code = data.code;
|
|
321
|
+
error.model = data.model;
|
|
322
|
+
error.capability = data.capability;
|
|
288
323
|
}
|
|
289
324
|
return error;
|
|
290
325
|
}
|
|
@@ -644,6 +679,9 @@ function createRuntime(options) {
|
|
|
644
679
|
const steering = [];
|
|
645
680
|
let steeringBytes = 0;
|
|
646
681
|
let steeringOpen = false;
|
|
682
|
+
const inboundAttachments = new Map;
|
|
683
|
+
const outboundAttachments = new Map;
|
|
684
|
+
let nextOutboundAttachment = 1;
|
|
647
685
|
const workspaceExecs = new Set;
|
|
648
686
|
const workspace = prepareWorkspaceAdapter(options.workspace);
|
|
649
687
|
const args = ["fx", ...options.args || []];
|
|
@@ -967,6 +1005,37 @@ function createRuntime(options) {
|
|
|
967
1005
|
steeringBytes -= value.length;
|
|
968
1006
|
return value.length;
|
|
969
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
|
+
}
|
|
970
1039
|
let pendingHostToolResult = null;
|
|
971
1040
|
function hostToolCall(namePtr, nameLen, argumentsPtr, argumentsLen, outputPtr, outputCap, statusPtr) {
|
|
972
1041
|
pendingHostToolResult = null;
|
|
@@ -1430,6 +1499,9 @@ function createRuntime(options) {
|
|
|
1430
1499
|
steeringOpen = false;
|
|
1431
1500
|
clearSteering();
|
|
1432
1501
|
},
|
|
1502
|
+
fx_attachment_size: attachmentSize,
|
|
1503
|
+
fx_attachment_take: attachmentTake,
|
|
1504
|
+
fx_attachment_put: attachmentPut,
|
|
1433
1505
|
fx_open_url: new WebAssembly.Suspending(openUrl),
|
|
1434
1506
|
fx_oauth_session_load: new WebAssembly.Suspending(oauthSessionLoad),
|
|
1435
1507
|
fx_oauth_session_commit: new WebAssembly.Suspending(oauthSessionCommit),
|
|
@@ -1460,6 +1532,15 @@ function createRuntime(options) {
|
|
|
1460
1532
|
write(data) {
|
|
1461
1533
|
stdin.push(typeof data === "string" ? encoder.encode(data) : data);
|
|
1462
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
|
+
},
|
|
1463
1544
|
wake() {
|
|
1464
1545
|
stdin.wake();
|
|
1465
1546
|
},
|
|
@@ -1634,35 +1715,131 @@ async function createFxTerminal(options) {
|
|
|
1634
1715
|
}
|
|
1635
1716
|
};
|
|
1636
1717
|
}
|
|
1637
|
-
function
|
|
1718
|
+
function blobByteLength(value) {
|
|
1719
|
+
if (typeof Blob === "undefined" || value == null)
|
|
1720
|
+
return null;
|
|
1721
|
+
try {
|
|
1722
|
+
return Object.getOwnPropertyDescriptor(Blob.prototype, "size").get.call(value);
|
|
1723
|
+
} catch {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
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 } = {}) {
|
|
1638
1820
|
if (typeof input === "string")
|
|
1639
1821
|
return [{ type: "text", text: input }];
|
|
1640
1822
|
if (!Array.isArray(input))
|
|
1641
1823
|
throw new TypeError("prompt input must be a string or an array of prompt blocks");
|
|
1642
1824
|
let imageCount = 0;
|
|
1643
1825
|
let imageBytes = 0;
|
|
1644
|
-
|
|
1826
|
+
let prompt = input.map((block, index) => {
|
|
1645
1827
|
if (!block || typeof block !== "object")
|
|
1646
1828
|
throw new TypeError(`prompt block ${index} must be an object`);
|
|
1647
1829
|
if (block.type === "image") {
|
|
1648
|
-
|
|
1649
|
-
throw new TypeError(`image prompt block ${index} requires base64 data`);
|
|
1650
|
-
}
|
|
1651
|
-
if (typeof block.mimeType !== "string" || block.mimeType.length === 0 || block.mimeType.length > 128) {
|
|
1652
|
-
throw new TypeError(`image prompt block ${index} requires a mimeType`);
|
|
1653
|
-
}
|
|
1654
|
-
if (block.data.length > maxPromptImageDataBytes) {
|
|
1655
|
-
throw new RangeError(`image prompt block ${index} exceeds the ${maxPromptImageDataBytes} byte per-image libfx limit`);
|
|
1656
|
-
}
|
|
1830
|
+
const image = normalizePromptImage(block, index);
|
|
1657
1831
|
imageCount += 1;
|
|
1658
1832
|
if (imageCount > maxPromptImages) {
|
|
1659
1833
|
throw new RangeError(`prompt cannot contain more than ${maxPromptImages} images`);
|
|
1660
1834
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
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;
|
|
1664
1841
|
}
|
|
1665
|
-
return
|
|
1842
|
+
return image;
|
|
1666
1843
|
}
|
|
1667
1844
|
if (block.type === "text") {
|
|
1668
1845
|
if (typeof block.text !== "string")
|
|
@@ -1679,6 +1856,89 @@ function normalizePromptInput(input) {
|
|
|
1679
1856
|
}
|
|
1680
1857
|
throw new TypeError(`unsupported prompt block type: ${String(block.type)}`);
|
|
1681
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
|
+
}
|
|
1884
|
+
}
|
|
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 }
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
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 = [];
|
|
1902
|
+
let imageBytes = 0;
|
|
1903
|
+
for (let index = 0;index < blocks.length; index++) {
|
|
1904
|
+
if (isCancelled())
|
|
1905
|
+
return null;
|
|
1906
|
+
const block = blocks[index];
|
|
1907
|
+
if (block.type !== "image" || block.data === undefined) {
|
|
1908
|
+
prepared.push(block);
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
let image = block.source === "blob" ? { mimeType: block.mimeType, bytes: new Uint8Array(await block.data.arrayBuffer()) } : preparePromptImages([block])[0];
|
|
1912
|
+
if (isCancelled())
|
|
1913
|
+
return null;
|
|
1914
|
+
if (resizeImage) {
|
|
1915
|
+
image = resizedPromptImage(await resizeImage({ bytes: image.bytes, mimeType: image.mimeType }), index);
|
|
1916
|
+
if (isCancelled())
|
|
1917
|
+
return null;
|
|
1918
|
+
}
|
|
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);
|
|
1931
|
+
if (imageBytes > maxPromptImagesBytes) {
|
|
1932
|
+
prepared = omitReferencedImageData(prepared);
|
|
1933
|
+
blocks = omitReferencedImageData(blocks);
|
|
1934
|
+
imageBytes = prepared.reduce((total, entry) => total + (entry.bytes?.byteLength ?? 0), 0);
|
|
1935
|
+
}
|
|
1936
|
+
checkPromptImagesByteLength(imageBytes);
|
|
1937
|
+
}
|
|
1938
|
+
if (promptFrameSize(prepared) > maxPromptFrameBytes)
|
|
1939
|
+
prepared = omitReferencedImageData(prepared);
|
|
1940
|
+
checkPromptFrameSize(prepared, true);
|
|
1941
|
+
return prepared;
|
|
1682
1942
|
}
|
|
1683
1943
|
function normalizeSteeringInput(input) {
|
|
1684
1944
|
const blocks = normalizePromptInput(input);
|
|
@@ -1765,18 +2025,31 @@ function hostToolContent(value) {
|
|
|
1765
2025
|
if (typeof value.text !== "string" || !Array.isArray(value.images) || value.images.length > 8) {
|
|
1766
2026
|
throw new TypeError("invalid typed tool result");
|
|
1767
2027
|
}
|
|
1768
|
-
let
|
|
1769
|
-
|
|
1770
|
-
|
|
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) {
|
|
1771
2034
|
throw new TypeError("invalid tool image");
|
|
1772
2035
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
2036
|
+
return {
|
|
2037
|
+
type: "image",
|
|
2038
|
+
...!referenceOnly && image.data.length <= maxPromptImageDataBytes ? { data: image.data } : {},
|
|
2039
|
+
mimeType: image.mimeType,
|
|
2040
|
+
...sourceRef === undefined ? {} : { sourceRef }
|
|
2041
|
+
};
|
|
1777
2042
|
});
|
|
1778
|
-
|
|
1779
|
-
|
|
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)
|
|
1780
2053
|
throw new RangeError("typed tool result exceeds the result limit");
|
|
1781
2054
|
return { content, rich: true, isError: value.isError === true };
|
|
1782
2055
|
}
|
|
@@ -1799,13 +2072,6 @@ function checkpointBytes(value) {
|
|
|
1799
2072
|
}
|
|
1800
2073
|
throw new TypeError("checkpoint must be an ArrayBuffer or typed array");
|
|
1801
2074
|
}
|
|
1802
|
-
function bytesToBase64(value) {
|
|
1803
|
-
let binary = "";
|
|
1804
|
-
for (let offset = 0;offset < value.length; offset += 32768) {
|
|
1805
|
-
binary += String.fromCharCode(...value.subarray(offset, offset + 32768));
|
|
1806
|
-
}
|
|
1807
|
-
return btoa(binary);
|
|
1808
|
-
}
|
|
1809
2075
|
function base64ToBytes(value) {
|
|
1810
2076
|
const binary = atob(value);
|
|
1811
2077
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -1818,11 +2084,15 @@ async function createFxAgent(options = {}) {
|
|
|
1818
2084
|
const hostTools = normalizeHostTools(options.tools);
|
|
1819
2085
|
const instructions = normalizeInstructions(options.instructions);
|
|
1820
2086
|
const initialCheckpoint = checkpointBytes(options.checkpoint);
|
|
2087
|
+
if (initialCheckpoint && initialCheckpoint.byteLength > maxCheckpointBytes) {
|
|
2088
|
+
throw new Error("libfx checkpoint is too large");
|
|
2089
|
+
}
|
|
1821
2090
|
const pending = new Map;
|
|
1822
2091
|
let nextId = 1;
|
|
1823
2092
|
let sessionId = null;
|
|
1824
2093
|
let activeTurn = null;
|
|
1825
2094
|
let closing = false;
|
|
2095
|
+
let coreExitError = null;
|
|
1826
2096
|
const isCurrentTurn = (turn) => turn && activeTurn === turn && !turn.cancelled && !closing;
|
|
1827
2097
|
const emit = (type, detail = {}) => {
|
|
1828
2098
|
try {
|
|
@@ -1962,8 +2232,12 @@ async function createFxAgent(options = {}) {
|
|
|
1962
2232
|
if (closing)
|
|
1963
2233
|
throw new Error("fx agent is closing");
|
|
1964
2234
|
emit("acp.send", { message });
|
|
2235
|
+
if (message.method === "session/prompt" && activeTurn?.cancelled)
|
|
2236
|
+
throw new Error("Cancelled");
|
|
1965
2237
|
runtime.write(`${JSON.stringify(message)}
|
|
1966
2238
|
`);
|
|
2239
|
+
if (message.method === "session/prompt")
|
|
2240
|
+
activeTurn?.promptWritten();
|
|
1967
2241
|
};
|
|
1968
2242
|
const request = (method, params = {}) => new Promise((resolve, reject) => {
|
|
1969
2243
|
const id = nextId++;
|
|
@@ -1975,13 +2249,53 @@ async function createFxAgent(options = {}) {
|
|
|
1975
2249
|
reject(error);
|
|
1976
2250
|
}
|
|
1977
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
|
+
};
|
|
1978
2290
|
runtime.exited.then((code) => {
|
|
1979
|
-
emit("runtime.exit", { code });
|
|
1980
2291
|
closing = true;
|
|
1981
2292
|
const error = runtime.error ?? new Error(`fx-core exited with code ${code} before completing the ACP request`);
|
|
2293
|
+
coreExitError = error;
|
|
2294
|
+
activeTurn?.failImagePrep(error);
|
|
1982
2295
|
for (const waiter of pending.values())
|
|
1983
2296
|
waiter.reject(error);
|
|
1984
2297
|
pending.clear();
|
|
2298
|
+
emit("runtime.exit", { code });
|
|
1985
2299
|
});
|
|
1986
2300
|
runtime.setLineHandler((message, size) => {
|
|
1987
2301
|
emit("acp.receive", { message });
|
|
@@ -2017,6 +2331,10 @@ async function createFxAgent(options = {}) {
|
|
|
2017
2331
|
if (cancelled || closing)
|
|
2018
2332
|
return;
|
|
2019
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
|
+
}
|
|
2020
2338
|
if (encoder.encode(JSON.stringify(response)).length + 1 > 8 * 1024 * 1024) {
|
|
2021
2339
|
response.result = { content: "Host tool result exceeded the response frame limit", isError: true };
|
|
2022
2340
|
}
|
|
@@ -2028,7 +2346,7 @@ async function createFxAgent(options = {}) {
|
|
|
2028
2346
|
return;
|
|
2029
2347
|
pending.delete(message.id);
|
|
2030
2348
|
if (message.error)
|
|
2031
|
-
waiter.reject(
|
|
2349
|
+
waiter.reject(agentRpcError(message.error));
|
|
2032
2350
|
else
|
|
2033
2351
|
waiter.resolve(message.result);
|
|
2034
2352
|
}
|
|
@@ -2042,10 +2360,8 @@ async function createFxAgent(options = {}) {
|
|
|
2042
2360
|
const sessionResult = await request("libfx/new");
|
|
2043
2361
|
sessionId = sessionResult.sessionId;
|
|
2044
2362
|
if (initialCheckpoint) {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
checkpoint: bytesToBase64(initialCheckpoint)
|
|
2048
|
-
});
|
|
2363
|
+
const [checkpointAttachment] = attachBytes([initialCheckpoint]);
|
|
2364
|
+
await request("libfx/restore", { sessionId, checkpointAttachment });
|
|
2049
2365
|
}
|
|
2050
2366
|
} catch (error) {
|
|
2051
2367
|
closing = true;
|
|
@@ -2058,7 +2374,7 @@ async function createFxAgent(options = {}) {
|
|
|
2058
2374
|
try {
|
|
2059
2375
|
await runtime.exited;
|
|
2060
2376
|
} catch {}
|
|
2061
|
-
throw
|
|
2377
|
+
throw error;
|
|
2062
2378
|
}
|
|
2063
2379
|
const agent = {
|
|
2064
2380
|
prompt(input, promptOptions = {}) {
|
|
@@ -2068,15 +2384,20 @@ async function createFxAgent(options = {}) {
|
|
|
2068
2384
|
throw new Error("a prompt is already in progress for this session");
|
|
2069
2385
|
return normalizeTurn(startTurn(input, promptOptions));
|
|
2070
2386
|
},
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
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;
|
|
2080
2401
|
},
|
|
2081
2402
|
async close() {
|
|
2082
2403
|
if (closing) {
|
|
@@ -2200,10 +2521,12 @@ async function createFxAgent(options = {}) {
|
|
|
2200
2521
|
return { inputTruncated: true, inputPreview: decoder.decode(utf8Prefix(bytes, maxToolStartInputBytes)) };
|
|
2201
2522
|
}
|
|
2202
2523
|
function startTurn(input, promptOptions) {
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
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);
|
|
2207
2530
|
const signal = promptOptions.signal;
|
|
2208
2531
|
if (signal !== undefined && (typeof signal?.addEventListener !== "function" || typeof signal?.removeEventListener !== "function"))
|
|
2209
2532
|
throw new TypeError("prompt signal must be an AbortSignal");
|
|
@@ -2215,6 +2538,14 @@ async function createFxAgent(options = {}) {
|
|
|
2215
2538
|
let terminalError;
|
|
2216
2539
|
let reportedPressure = false;
|
|
2217
2540
|
let discardedBytes = 0;
|
|
2541
|
+
let cancelImagePrep = null;
|
|
2542
|
+
let rejectImagePrep = null;
|
|
2543
|
+
let resolvePromptStart = null;
|
|
2544
|
+
const promptStarted = asyncImages ? new Promise((resolve) => {
|
|
2545
|
+
resolvePromptStart = resolve;
|
|
2546
|
+
}) : null;
|
|
2547
|
+
let pendingSteeringCount = 0;
|
|
2548
|
+
let pendingSteeringBytes = 0;
|
|
2218
2549
|
const toolControllers = new Set;
|
|
2219
2550
|
let finished = false;
|
|
2220
2551
|
let cancelled = false;
|
|
@@ -2251,35 +2582,72 @@ async function createFxAgent(options = {}) {
|
|
|
2251
2582
|
get cancelled() {
|
|
2252
2583
|
return cancelled;
|
|
2253
2584
|
},
|
|
2585
|
+
failImagePrep(error) {
|
|
2586
|
+
rejectImagePrep?.(error);
|
|
2587
|
+
rejectImagePrep = null;
|
|
2588
|
+
cancelImagePrep = null;
|
|
2589
|
+
},
|
|
2590
|
+
promptWritten() {
|
|
2591
|
+
resolvePromptStart?.(true);
|
|
2592
|
+
resolvePromptStart = null;
|
|
2593
|
+
},
|
|
2254
2594
|
steer(text) {
|
|
2255
2595
|
if (finished || cancelled || activeTurn !== turn) {
|
|
2256
2596
|
return Promise.reject(new Error("no prompt is running"));
|
|
2257
2597
|
}
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2598
|
+
if (closing)
|
|
2599
|
+
return Promise.reject(coreExitError ?? new Error("fx agent is closing"));
|
|
2600
|
+
const apply = () => {
|
|
2601
|
+
if (finished || cancelled || activeTurn !== turn) {
|
|
2602
|
+
return Promise.reject(new Error("no prompt is running"));
|
|
2603
|
+
}
|
|
2604
|
+
if (closing)
|
|
2605
|
+
return Promise.reject(coreExitError ?? new Error("fx agent is closing"));
|
|
2606
|
+
try {
|
|
2607
|
+
if (typeof runtime.steer === "function") {
|
|
2608
|
+
runtime.steer(text);
|
|
2609
|
+
turn.push({
|
|
2610
|
+
sessionUpdate: "user_message_chunk",
|
|
2611
|
+
content: { type: "text", text }
|
|
2612
|
+
});
|
|
2613
|
+
return Promise.resolve();
|
|
2614
|
+
}
|
|
2615
|
+
return request("libfx/steer", { sessionId, text });
|
|
2616
|
+
} catch (error) {
|
|
2617
|
+
return Promise.reject(error);
|
|
2269
2618
|
}
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2619
|
+
};
|
|
2620
|
+
if (!resolvePromptStart)
|
|
2621
|
+
return apply();
|
|
2622
|
+
const bytes = encoder.encode(text).length;
|
|
2623
|
+
if (pendingSteeringCount >= maxSteeringMessages || bytes > maxSteeringQueueBytes - pendingSteeringBytes) {
|
|
2624
|
+
return Promise.reject(new Error("steering queue is full"));
|
|
2272
2625
|
}
|
|
2273
|
-
|
|
2626
|
+
pendingSteeringCount++;
|
|
2627
|
+
pendingSteeringBytes += bytes;
|
|
2628
|
+
return promptStarted.then((started) => {
|
|
2629
|
+
if (coreExitError && !cancelled)
|
|
2630
|
+
throw coreExitError;
|
|
2631
|
+
return started === true ? apply() : Promise.reject(started instanceof Error ? started : new Error("no prompt is running"));
|
|
2632
|
+
}).finally(() => {
|
|
2633
|
+
pendingSteeringCount--;
|
|
2634
|
+
pendingSteeringBytes -= bytes;
|
|
2635
|
+
});
|
|
2274
2636
|
},
|
|
2275
2637
|
cancel() {
|
|
2276
2638
|
if (finished || cancelled)
|
|
2277
2639
|
return;
|
|
2278
2640
|
cancelled = true;
|
|
2641
|
+
cancelImagePrep?.();
|
|
2642
|
+
cancelImagePrep = null;
|
|
2643
|
+
rejectImagePrep = null;
|
|
2644
|
+
resolvePromptStart?.(false);
|
|
2645
|
+
resolvePromptStart = null;
|
|
2279
2646
|
runtime.closeSteering?.();
|
|
2280
2647
|
resumeOutput?.();
|
|
2281
2648
|
resumeOutput = null;
|
|
2282
|
-
|
|
2649
|
+
if (!closing)
|
|
2650
|
+
send({ jsonrpc: "2.0", method: "session/cancel", params: { sessionId } });
|
|
2283
2651
|
for (const controller of toolControllers)
|
|
2284
2652
|
controller.abort();
|
|
2285
2653
|
runtime.abortHostEffects();
|
|
@@ -2311,6 +2679,8 @@ async function createFxAgent(options = {}) {
|
|
|
2311
2679
|
}
|
|
2312
2680
|
};
|
|
2313
2681
|
if (signal?.aborted) {
|
|
2682
|
+
resolvePromptStart?.(false);
|
|
2683
|
+
resolvePromptStart = null;
|
|
2314
2684
|
finished = true;
|
|
2315
2685
|
turn.result = Promise.resolve({ stopReason: "cancelled" });
|
|
2316
2686
|
return turn;
|
|
@@ -2319,8 +2689,40 @@ async function createFxAgent(options = {}) {
|
|
|
2319
2689
|
runtime.openSteering?.();
|
|
2320
2690
|
const abort = () => turn.cancel();
|
|
2321
2691
|
signal?.addEventListener("abort", abort, { once: true });
|
|
2322
|
-
|
|
2323
|
-
|
|
2692
|
+
const imagePrepCancelled = asyncImages ? new Promise((resolve, reject) => {
|
|
2693
|
+
cancelImagePrep = () => resolve(null);
|
|
2694
|
+
rejectImagePrep = reject;
|
|
2695
|
+
}) : null;
|
|
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);
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
turn.result = response.then((value) => ({ stopReason: cancelled ? "cancelled" : value.stopReason, usage: value.usage })).catch((error) => {
|
|
2721
|
+
cancelImagePrep = null;
|
|
2722
|
+
rejectImagePrep = null;
|
|
2723
|
+
resolvePromptStart?.(error);
|
|
2724
|
+
resolvePromptStart = null;
|
|
2725
|
+
if (cancelled && error.message === "Cancelled")
|
|
2324
2726
|
return { stopReason: "cancelled" };
|
|
2325
2727
|
terminalError = error;
|
|
2326
2728
|
throw error;
|
|
@@ -2351,7 +2753,7 @@ async function createFxAgent(options = {}) {
|
|
|
2351
2753
|
|
|
2352
2754
|
// sdk/node.js
|
|
2353
2755
|
var libfxApiVersion = 2;
|
|
2354
|
-
var nativeCoreApiVersion =
|
|
2756
|
+
var nativeCoreApiVersion = 4;
|
|
2355
2757
|
var fetchOperationStale = 0;
|
|
2356
2758
|
var fetchOperationApplied = 1;
|
|
2357
2759
|
var fetchOperationBackpressure = 2;
|
|
@@ -2642,7 +3044,12 @@ async function getBackendInfo(value = {}) {
|
|
|
2642
3044
|
}
|
|
2643
3045
|
}
|
|
2644
3046
|
function createNativeCoreRuntime(addon, options) {
|
|
2645
|
-
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
|
+
}
|
|
2646
3053
|
const core = addon.createCore({
|
|
2647
3054
|
apiKey,
|
|
2648
3055
|
home: options.home ?? import_node_os.homedir(),
|
|
@@ -2650,6 +3057,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2650
3057
|
...model === undefined ? {} : { model },
|
|
2651
3058
|
...effort === undefined ? {} : { effort },
|
|
2652
3059
|
...fast === undefined ? {} : { fast },
|
|
3060
|
+
...ultrafast === undefined ? {} : { ultrafast },
|
|
2653
3061
|
...gatewayChatUrl === undefined ? {} : { gatewayChatUrl }
|
|
2654
3062
|
});
|
|
2655
3063
|
let readyFd;
|
|
@@ -2696,7 +3104,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2696
3104
|
readySocket.destroy();
|
|
2697
3105
|
readyClosed.then(() => exitedResolve(code));
|
|
2698
3106
|
};
|
|
2699
|
-
const pumpFetch = async (request) => {
|
|
3107
|
+
const pumpFetch = async (request, body) => {
|
|
2700
3108
|
const controller = new AbortController;
|
|
2701
3109
|
const state = { handle: request.handle, controller };
|
|
2702
3110
|
fetchState = state;
|
|
@@ -2704,7 +3112,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2704
3112
|
const response = await (options.fetch ?? globalThis.fetch)(request.url, {
|
|
2705
3113
|
method: request.method,
|
|
2706
3114
|
headers: new Headers(JSON.parse(request.headers).map(({ name, value }) => [name, value])),
|
|
2707
|
-
body:
|
|
3115
|
+
body: body.length ? body : undefined,
|
|
2708
3116
|
signal: controller.signal
|
|
2709
3117
|
});
|
|
2710
3118
|
const started = addon.startCoreFetchResponse(core, state.handle, response.status);
|
|
@@ -2761,7 +3169,7 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2761
3169
|
} else {
|
|
2762
3170
|
const fetchRequest = addon.takeCoreFetch(core);
|
|
2763
3171
|
if (fetchRequest)
|
|
2764
|
-
pumpFetch(JSON.parse(fetchRequest.toString("utf8")));
|
|
3172
|
+
pumpFetch(JSON.parse(fetchRequest.request.toString("utf8")), fetchRequest.body);
|
|
2765
3173
|
}
|
|
2766
3174
|
if (addon.coreExitCode(core) !== 0) {
|
|
2767
3175
|
finish(1, new Error("native output delivery failed"));
|
|
@@ -2822,6 +3230,15 @@ function createNativeCoreRuntime(addon, options) {
|
|
|
2822
3230
|
write(data) {
|
|
2823
3231
|
addon.writeCore(core, Buffer.from(data));
|
|
2824
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
|
+
},
|
|
2825
3242
|
closeStdin() {
|
|
2826
3243
|
addon.closeCore(core);
|
|
2827
3244
|
},
|
|
@@ -2848,10 +3265,11 @@ function createNativeAgent(addon, options) {
|
|
|
2848
3265
|
});
|
|
2849
3266
|
}
|
|
2850
3267
|
async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWasm, options) {
|
|
2851
|
-
const { nativeAddon, backend = "auto", ...
|
|
3268
|
+
const { nativeAddon, backend = "auto", ...unvalidatedOptions } = options ?? {};
|
|
2852
3269
|
if (!new Set(["auto", "native", "wasm"]).has(backend)) {
|
|
2853
3270
|
throw new TypeError('backend must be "auto", "native", or "wasm"');
|
|
2854
3271
|
}
|
|
3272
|
+
const runtimeOptions = surface === "agent" ? normalizeAgentOptions(unvalidatedOptions) : unvalidatedOptions;
|
|
2855
3273
|
let nativeError;
|
|
2856
3274
|
let nativeAttempted = false;
|
|
2857
3275
|
if (backend !== "wasm") {
|
|
@@ -2865,7 +3283,7 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
2865
3283
|
return await native.backend[nativeMethod](runtimeOptions);
|
|
2866
3284
|
} catch (error) {
|
|
2867
3285
|
nativeError = error;
|
|
2868
|
-
if (backend === "native")
|
|
3286
|
+
if (backend === "native" || error?.code === "LIBFX_MODEL_UNSUPPORTED_FAST" || error?.code === "LIBFX_MODEL_UNSUPPORTED_EFFORT")
|
|
2869
3287
|
throw error;
|
|
2870
3288
|
}
|
|
2871
3289
|
}
|
|
@@ -2884,9 +3302,6 @@ async function createWithFallback(surface, nativeMethod, wasmFactory, defaultWas
|
|
|
2884
3302
|
return wasmFactory({ ...runtimeOptions, wasm: await wasmInput(wasmSource) });
|
|
2885
3303
|
}
|
|
2886
3304
|
async function createFxAgent2(options = {}) {
|
|
2887
|
-
if (options != null && Object.hasOwn(Object(options), "env")) {
|
|
2888
|
-
throw new TypeError("createFxAgent() does not accept env; pass apiKey and model directly");
|
|
2889
|
-
}
|
|
2890
3305
|
return createWithFallback("agent", "createCore", createFxAgent, defaultCoreWasm, options);
|
|
2891
3306
|
}
|
|
2892
3307
|
function createFxTerminal2(options = {}) {
|