@lamplitisles/dsh-nanocodex 0.1.0-beta.0
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/LICENSE +65 -0
- package/NOTICE +2 -0
- package/README.md +182 -0
- package/THIRD_PARTY_NOTICES.md +29 -0
- package/cordis.patch.yml +52 -0
- package/dist/index.d.ts +435 -0
- package/dist/index.js +3405 -0
- package/dist/standard-preset.d.ts +7 -0
- package/dist/standard-preset.js +25 -0
- package/engine-release.json +14 -0
- package/package.json +119 -0
- package/presets/standard/agent.cordis.yml +6 -0
- package/presets/standard/preset.yml +2 -0
- package/vendor/nanocodex/README.md +1344 -0
- package/vendor/nanocodex/actions/events.d.mts +10 -0
- package/vendor/nanocodex/actions/events.mjs +163 -0
- package/vendor/nanocodex/actions/index.d.mts +13 -0
- package/vendor/nanocodex/actions/index.mjs +35 -0
- package/vendor/nanocodex/actions/session.d.mts +66 -0
- package/vendor/nanocodex/actions/session.mjs +59 -0
- package/vendor/nanocodex/actions/turn.d.mts +59 -0
- package/vendor/nanocodex/actions/turn.mjs +37 -0
- package/vendor/nanocodex/actions/voice.d.mts +22 -0
- package/vendor/nanocodex/actions/voice.mjs +50 -0
- package/vendor/nanocodex/browser/Agent.d.mts +55 -0
- package/vendor/nanocodex/browser/Agent.mjs +11 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.d.mts +7 -0
- package/vendor/nanocodex/browser/ChatGptSubscription.mjs +19 -0
- package/vendor/nanocodex/browser/InlineAgent.mjs +266 -0
- package/vendor/nanocodex/browser/Transport.d.mts +74 -0
- package/vendor/nanocodex/browser/Transport.mjs +61 -0
- package/vendor/nanocodex/browser/Voice.d.mts +71 -0
- package/vendor/nanocodex/browser/Voice.mjs +291 -0
- package/vendor/nanocodex/browser/VoiceSession.mjs +688 -0
- package/vendor/nanocodex/browser/WorkerAgent.d.mts +65 -0
- package/vendor/nanocodex/browser/WorkerAgent.mjs +1472 -0
- package/vendor/nanocodex/browser/agent.worker.mjs +3 -0
- package/vendor/nanocodex/browser/config.d.mts +36 -0
- package/vendor/nanocodex/browser/config.mjs +439 -0
- package/vendor/nanocodex/browser/engine.mjs +13 -0
- package/vendor/nanocodex/browser/harness.mjs +55 -0
- package/vendor/nanocodex/browser/host.d.mts +83 -0
- package/vendor/nanocodex/browser/host.mjs +555 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.d.mts +14 -0
- package/vendor/nanocodex/browser/hostManagedWebSocket.mjs +110 -0
- package/vendor/nanocodex/browser/index.d.mts +92 -0
- package/vendor/nanocodex/browser/index.mjs +23 -0
- package/vendor/nanocodex/browser/indexeddb-durability-store.mjs +197 -0
- package/vendor/nanocodex/browser/workspace.d.mts +16 -0
- package/vendor/nanocodex/browser/workspace.mjs +107 -0
- package/vendor/nanocodex/cloud/Client.d.mts +83 -0
- package/vendor/nanocodex/cloud/Client.mjs +231 -0
- package/vendor/nanocodex/cloud/Decorator.d.mts +31 -0
- package/vendor/nanocodex/cloud/Decorator.mjs +37 -0
- package/vendor/nanocodex/cloud/Dialog.d.mts +113 -0
- package/vendor/nanocodex/cloud/Dialog.mjs +366 -0
- package/vendor/nanocodex/cloud/Errors.d.mts +22 -0
- package/vendor/nanocodex/cloud/Errors.mjs +39 -0
- package/vendor/nanocodex/cloud/Principal.d.mts +26 -0
- package/vendor/nanocodex/cloud/Principal.mjs +88 -0
- package/vendor/nanocodex/cloud/RemoteProvider.mjs +88 -0
- package/vendor/nanocodex/cloud/Transport.d.mts +47 -0
- package/vendor/nanocodex/cloud/Transport.mjs +454 -0
- package/vendor/nanocodex/cloud/actions/account.d.mts +9 -0
- package/vendor/nanocodex/cloud/actions/account.mjs +19 -0
- package/vendor/nanocodex/cloud/actions/agent.d.mts +18 -0
- package/vendor/nanocodex/cloud/actions/agent.mjs +428 -0
- package/vendor/nanocodex/cloud/actions/connection.d.mts +133 -0
- package/vendor/nanocodex/cloud/actions/connection.mjs +580 -0
- package/vendor/nanocodex/cloud/actions/grant.d.mts +10 -0
- package/vendor/nanocodex/cloud/actions/grant.mjs +12 -0
- package/vendor/nanocodex/cloud/actions/index.d.mts +7 -0
- package/vendor/nanocodex/cloud/actions/index.mjs +7 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.d.mts +23 -0
- package/vendor/nanocodex/cloud/actions/machineUsd.mjs +37 -0
- package/vendor/nanocodex/cloud/actions/model.d.mts +12 -0
- package/vendor/nanocodex/cloud/actions/model.mjs +50 -0
- package/vendor/nanocodex/cloud/actions/mpp.d.mts +28 -0
- package/vendor/nanocodex/cloud/actions/mpp.mjs +39 -0
- package/vendor/nanocodex/cloud/index.d.mts +30 -0
- package/vendor/nanocodex/cloud/index.mjs +9 -0
- package/vendor/nanocodex/cloud/internal.mjs +533 -0
- package/vendor/nanocodex/cloud/mercator.mjs +108 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.d.mts +34 -0
- package/vendor/nanocodex/cloud/server/HostPrincipal.mjs +202 -0
- package/vendor/nanocodex/cloud/server/index.d.mts +1 -0
- package/vendor/nanocodex/cloud/server/index.mjs +1 -0
- package/vendor/nanocodex/cloud/types.d.mts +211 -0
- package/vendor/nanocodex/cloudflare/Agent.d.mts +133 -0
- package/vendor/nanocodex/cloudflare/Agent.mjs +811 -0
- package/vendor/nanocodex/cloudflare/egress-subject.mjs +19 -0
- package/vendor/nanocodex/cloudflare/egress.d.mts +41 -0
- package/vendor/nanocodex/cloudflare/egress.mjs +119 -0
- package/vendor/nanocodex/cloudflare/event-socket.mjs +316 -0
- package/vendor/nanocodex/cloudflare/index.d.mts +7 -0
- package/vendor/nanocodex/cloudflare/index.mjs +6 -0
- package/vendor/nanocodex/host/Agent.d.mts +55 -0
- package/vendor/nanocodex/host/Agent.mjs +1 -0
- package/vendor/nanocodex/host/index.d.mts +82 -0
- package/vendor/nanocodex/host/index.mjs +10 -0
- package/vendor/nanocodex/index.d.mts +81 -0
- package/vendor/nanocodex/index.mjs +12 -0
- package/vendor/nanocodex/internal.mjs +1275 -0
- package/vendor/nanocodex/managed/Agent.d.mts +380 -0
- package/vendor/nanocodex/managed/Agent.mjs +1694 -0
- package/vendor/nanocodex/managed/ManagedError.d.mts +9 -0
- package/vendor/nanocodex/managed/ManagedError.mjs +8 -0
- package/vendor/nanocodex/managed/README.md +241 -0
- package/vendor/nanocodex/managed/Voice.mjs +202 -0
- package/vendor/nanocodex/managed/index.d.mts +20 -0
- package/vendor/nanocodex/managed/index.mjs +2 -0
- package/vendor/nanocodex/managed/internal.mjs +119 -0
- package/vendor/nanocodex/node/Agent.d.mts +50 -0
- package/vendor/nanocodex/node/Agent.mjs +203 -0
- package/vendor/nanocodex/node/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/node/ChatGptSubscription.mjs +17 -0
- package/vendor/nanocodex/node/Transport.d.mts +44 -0
- package/vendor/nanocodex/node/Transport.mjs +46 -0
- package/vendor/nanocodex/node/host.mjs +731 -0
- package/vendor/nanocodex/node/index.d.mts +73 -0
- package/vendor/nanocodex/node/index.mjs +17 -0
- package/vendor/nanocodex/node/workspace.d.mts +10 -0
- package/vendor/nanocodex/node/workspace.mjs +198 -0
- package/vendor/nanocodex/package.json +200 -0
- package/vendor/nanocodex/pkg-node/nanocodex.d.ts +675 -0
- package/vendor/nanocodex/pkg-node/nanocodex.js +2628 -0
- package/vendor/nanocodex/pkg-node/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-node/package.json +1 -0
- package/vendor/nanocodex/pkg-web/.nanocodex-bindgen-stamp +6 -0
- package/vendor/nanocodex/pkg-web/nanocodex-build.json +1 -0
- package/vendor/nanocodex/pkg-web/nanocodex.d.ts +805 -0
- package/vendor/nanocodex/pkg-web/nanocodex.js +2714 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.js +2615 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm +0 -0
- package/vendor/nanocodex/pkg-web/nanocodex_bg.wasm.d.ts +105 -0
- package/vendor/nanocodex/pkg-web/nanocodex_worker.js +9 -0
- package/vendor/nanocodex/pkg-web/package.json +1 -0
- package/vendor/nanocodex/runtime/chatgpt-subscription.mjs +212 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.d.mts +26 -0
- package/vendor/nanocodex/runtime/cloudflare-durability-store.mjs +168 -0
- package/vendor/nanocodex/runtime/code-evaluator.worker.mjs +137 -0
- package/vendor/nanocodex/runtime/code-runtime.mjs +1 -0
- package/vendor/nanocodex/runtime/durability-store.d.mts +85 -0
- package/vendor/nanocodex/runtime/durability-store.mjs +631 -0
- package/vendor/nanocodex/runtime/durability.mjs +153 -0
- package/vendor/nanocodex/runtime/managed-transport.mjs +460 -0
- package/vendor/nanocodex/runtime/mcp-runtime.mjs +534 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.d.mts +64 -0
- package/vendor/nanocodex/runtime/postgres-durability-store.mjs +532 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.d.mts +17 -0
- package/vendor/nanocodex/runtime/quickjs-evaluator.mjs +236 -0
- package/vendor/nanocodex/runtime/responses-transport.mjs +22 -0
- package/vendor/nanocodex/runtime/subagents.d.mts +107 -0
- package/vendor/nanocodex/runtime/subagents.mjs +54 -0
- package/vendor/nanocodex/runtime/subscription-store.d.mts +8 -0
- package/vendor/nanocodex/runtime/subscription-store.mjs +42 -0
- package/vendor/nanocodex/runtime/tempo-provider.d.mts +125 -0
- package/vendor/nanocodex/runtime/tempo-provider.mjs +264 -0
- package/vendor/nanocodex/runtime/tool-configuration.mjs +1 -0
- package/vendor/nanocodex/runtime/tool-router.mjs +1 -0
- package/vendor/nanocodex/runtime/utf8.mjs +1 -0
- package/vendor/nanocodex/runtime/worker-evaluator.mjs +162 -0
- package/vendor/nanocodex/runtime/workspace.d.mts +1 -0
- package/vendor/nanocodex/runtime/workspace.mjs +1 -0
- package/vendor/nanocodex/tools/Tools.d.mts +56 -0
- package/vendor/nanocodex/tools/Tools.mjs +136 -0
- package/vendor/nanocodex/tools/artifact.d.mts +1 -0
- package/vendor/nanocodex/tools/artifact.mjs +1 -0
- package/vendor/nanocodex/tools/attachment.mjs +1 -0
- package/vendor/nanocodex/tools/bash.d.mts +1 -0
- package/vendor/nanocodex/tools/bash.mjs +1 -0
- package/vendor/nanocodex/tools/browser/accountInfo.mjs +795 -0
- package/vendor/nanocodex/tools/browser/browserBuffer.mjs +18 -0
- package/vendor/nanocodex/tools/browser/browserCompiler.mjs +155 -0
- package/vendor/nanocodex/tools/browser/browserEgress.mjs +161 -0
- package/vendor/nanocodex/tools/browser/browserPython.mjs +133 -0
- package/vendor/nanocodex/tools/browser/browserShell.mjs +1075 -0
- package/vendor/nanocodex/tools/browser/browserSprintf.mjs +4 -0
- package/vendor/nanocodex/tools/browser/browserSsh.mjs +201 -0
- package/vendor/nanocodex/tools/browser/browserZlib.mjs +136 -0
- package/vendor/nanocodex/tools/browser/compiler.worker.mjs +160 -0
- package/vendor/nanocodex/tools/browser/devTunnelsSshBrowser.mjs +13 -0
- package/vendor/nanocodex/tools/browser/index.d.mts +209 -0
- package/vendor/nanocodex/tools/browser/index.mjs +174 -0
- package/vendor/nanocodex/tools/browser/opfsGit.mjs +248 -0
- package/vendor/nanocodex/tools/browser/python.worker.mjs +114 -0
- package/vendor/nanocodex/tools/browser/threadGit.mjs +197 -0
- package/vendor/nanocodex/tools/browser/unsupportedNodeRsa.mjs +5 -0
- package/vendor/nanocodex/tools/browser/workspace.mjs +83 -0
- package/vendor/nanocodex/tools/dataset.d.mts +1 -0
- package/vendor/nanocodex/tools/dataset.mjs +1 -0
- package/vendor/nanocodex/tools/datasetContract.mjs +1 -0
- package/vendor/nanocodex/tools/datasetEngine.mjs +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.d.mts +1 -0
- package/vendor/nanocodex/tools/hostedCatalog.mjs +1 -0
- package/vendor/nanocodex/tools/index.d.mts +49 -0
- package/vendor/nanocodex/tools/index.mjs +19 -0
- package/vendor/nanocodex/tools/namedTool.mjs +1 -0
- package/vendor/nanocodex/tools/repository-workspace.d.mts +1 -0
- package/vendor/nanocodex/tools/repository-workspace.mjs +1 -0
- package/vendor/nanocodex/tools/ssh.d.mts +1 -0
- package/vendor/nanocodex/tools/ssh.mjs +1 -0
- package/vendor/nanocodex/tools/standard.mjs +1 -0
- package/vendor/nanocodex/tools/standardDescriptions.mjs +1 -0
- package/vendor/nanocodex/types.d.mts +663 -0
- package/vendor/nanocodex/wasm.d.mts +4 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.d.mts +9 -0
- package/vendor/nanocodex/worker/ChatGptSubscription.mjs +29 -0
- package/vendor/nanocodex/worker/index.d.mts +18 -0
- package/vendor/nanocodex/worker/index.mjs +5 -0
- package/vendor/nanocodex-tools/README.md +65 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.d.ts +7 -0
- package/vendor/nanocodex-tools/dist/hosted/app-tool-catalog.js +14 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.d.ts +172 -0
- package/vendor/nanocodex-tools/dist/hosted/broker-core.js +1278 -0
- package/vendor/nanocodex-tools/dist/hosted/index.d.ts +3 -0
- package/vendor/nanocodex-tools/dist/hosted/index.js +3 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.d.ts +120 -0
- package/vendor/nanocodex-tools/dist/hosted/protocol.js +415 -0
- package/vendor/nanocodex-tools/dist/index.d.ts +19 -0
- package/vendor/nanocodex-tools/dist/index.js +14 -0
- package/vendor/nanocodex-tools/dist/memory.d.ts +204 -0
- package/vendor/nanocodex-tools/dist/memory.js +411 -0
- package/vendor/nanocodex-tools/dist/namespace.d.ts +87 -0
- package/vendor/nanocodex-tools/dist/namespace.js +338 -0
- package/vendor/nanocodex-tools/dist/repository-archive.d.ts +4 -0
- package/vendor/nanocodex-tools/dist/repository-archive.js +104 -0
- package/vendor/nanocodex-tools/dist/runtime.d.ts +30 -0
- package/vendor/nanocodex-tools/dist/runtime.js +37 -0
- package/vendor/nanocodex-tools/dist/session.d.ts +167 -0
- package/vendor/nanocodex-tools/dist/session.js +286 -0
- package/vendor/nanocodex-tools/dist/shell.d.ts +51 -0
- package/vendor/nanocodex-tools/dist/shell.js +553 -0
- package/vendor/nanocodex-tools/dist/ssh.d.ts +29 -0
- package/vendor/nanocodex-tools/dist/ssh.js +66 -0
- package/vendor/nanocodex-tools/dist/workspace.d.ts +45 -0
- package/vendor/nanocodex-tools/dist/workspace.js +237 -0
- package/vendor/nanocodex-tools/package.json +122 -0
- package/vendor/nanocodex-tools/runtime/code-runtime.mjs +817 -0
- package/vendor/nanocodex-tools/runtime/tool-configuration.mjs +50 -0
- package/vendor/nanocodex-tools/runtime/tool-router.mjs +730 -0
- package/vendor/nanocodex-tools/runtime/utf8.d.mts +2 -0
- package/vendor/nanocodex-tools/runtime/utf8.mjs +25 -0
- package/vendor/nanocodex-tools/tools/artifact.d.mts +69 -0
- package/vendor/nanocodex-tools/tools/artifact.mjs +270 -0
- package/vendor/nanocodex-tools/tools/attachment.mjs +603 -0
- package/vendor/nanocodex-tools/tools/bash.d.mts +90 -0
- package/vendor/nanocodex-tools/tools/bash.mjs +742 -0
- package/vendor/nanocodex-tools/tools/dataset.d.mts +9 -0
- package/vendor/nanocodex-tools/tools/dataset.mjs +47 -0
- package/vendor/nanocodex-tools/tools/datasetContract.mjs +54 -0
- package/vendor/nanocodex-tools/tools/datasetEngine.mjs +1167 -0
- package/vendor/nanocodex-tools/tools/execution-contract.d.mts +5 -0
- package/vendor/nanocodex-tools/tools/execution-contract.mjs +63 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.d.mts +20 -0
- package/vendor/nanocodex-tools/tools/hostedCatalog.mjs +67 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/hostedMachine.mjs +56 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.d.mts +1 -0
- package/vendor/nanocodex-tools/tools/just-bash-browser.mjs +1 -0
- package/vendor/nanocodex-tools/tools/namedTool.d.mts +3 -0
- package/vendor/nanocodex-tools/tools/namedTool.mjs +3 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.d.mts +10 -0
- package/vendor/nanocodex-tools/tools/nodeProcess.mjs +314 -0
- package/vendor/nanocodex-tools/tools/processOutput.mjs +56 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.d.mts +55 -0
- package/vendor/nanocodex-tools/tools/repository-workspace.mjs +397 -0
- package/vendor/nanocodex-tools/tools/ssh.d.mts +56 -0
- package/vendor/nanocodex-tools/tools/ssh.mjs +503 -0
- package/vendor/nanocodex-tools/tools/standard.mjs +458 -0
- package/vendor/nanocodex-tools/tools/standardDescriptions.mjs +3 -0
- package/vendor/nanocodex-tools/tools/types.d.mts +83 -0
- package/vendor/nanocodex-tools/tools/workspace.d.mts +25 -0
- package/vendor/nanocodex-tools/tools/workspace.mjs +282 -0
- package/vendor/nanocodex-tools/tools/x.d.mts +30 -0
- package/vendor/nanocodex-tools/tools/x.mjs +91 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DATASET_DESCRIPTION,
|
|
3
|
+
MAX_QUERY_BYTES,
|
|
4
|
+
datasetParameters
|
|
5
|
+
} from "./datasetContract.mjs";
|
|
6
|
+
const MAX_OPEN_DATASETS = 8;
|
|
7
|
+
const MAX_TOTAL_OPEN_DATASETS = 16;
|
|
8
|
+
const DEFAULT_QUERY_LIMIT = 20;
|
|
9
|
+
const DEFAULT_QUERY_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
const OPEN_METADATA_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
const JSONL_SCHEMA_BYTES = 1024 * 1024;
|
|
12
|
+
const JSONL_SCHEMA_ROWS = 32;
|
|
13
|
+
const MAX_JSONL_LINE_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
const MAX_API_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
15
|
+
const MAX_TOOL_OUTPUT_BYTES = 256 * 1024;
|
|
16
|
+
const MAX_CELL_STRING_LENGTH = 16 * 1024;
|
|
17
|
+
const FETCH_TIMEOUT_MS = 2e4;
|
|
18
|
+
const RANGE_CACHE_BYTES = 32 * 1024 * 1024;
|
|
19
|
+
const MAX_CACHED_RANGE_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const QUERY_CACHE_BYTES = 2 * 1024 * 1024;
|
|
21
|
+
const MAX_CACHED_QUERIES = 16;
|
|
22
|
+
const MAX_BLOOM_FILTER_GROUPS = 8;
|
|
23
|
+
const PARQUET_SCAN_ROWS = 2048;
|
|
24
|
+
const CURSOR_VERSION = 1;
|
|
25
|
+
function createDatasetTool(options = {}) {
|
|
26
|
+
const providedFetch = options.fetch;
|
|
27
|
+
const fetchImpl = providedFetch === undefined
|
|
28
|
+
? globalThis.fetch.bind(globalThis)
|
|
29
|
+
: (input, init) => providedFetch(input, init);
|
|
30
|
+
const randomId = options.randomId ?? (() => crypto.randomUUID());
|
|
31
|
+
const loadParquet = options.loadParquet ?? (() => import("hyparquet"));
|
|
32
|
+
const loadCompressors = options.loadCompressors ?? (() => import("hyparquet-compressors"));
|
|
33
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
34
|
+
const rangeCache = new RangeCache(RANGE_CACHE_BYTES, MAX_CACHED_RANGE_BYTES);
|
|
35
|
+
const pendingOpens = /* @__PURE__ */ new Map();
|
|
36
|
+
return {
|
|
37
|
+
description: DATASET_DESCRIPTION,
|
|
38
|
+
parameters: datasetParameters,
|
|
39
|
+
async handler(input, context) {
|
|
40
|
+
context.signal?.throwIfAborted();
|
|
41
|
+
const args = requireObject(input, "dataset");
|
|
42
|
+
const operation = requireString(args.operation, "dataset.operation");
|
|
43
|
+
const runtime = { fetch: fetchImpl, rangeCache, signal: context.signal };
|
|
44
|
+
if (operation === "open") {
|
|
45
|
+
const sessionPending = pendingOpens.get(context.sessionId) ?? 0;
|
|
46
|
+
if ((sessions.get(context.sessionId)?.size ?? 0) + sessionPending >= MAX_OPEN_DATASETS) {
|
|
47
|
+
throw new Error(`dataset session already has ${MAX_OPEN_DATASETS} open datasets; close one first`);
|
|
48
|
+
}
|
|
49
|
+
if (openDatasetCount(sessions) + pendingOpenCount(pendingOpens) >= MAX_TOTAL_OPEN_DATASETS) {
|
|
50
|
+
throw new Error(`dataset tool already has ${MAX_TOTAL_OPEN_DATASETS} open datasets; close one first`);
|
|
51
|
+
}
|
|
52
|
+
pendingOpens.set(context.sessionId, sessionPending + 1);
|
|
53
|
+
let dataset;
|
|
54
|
+
try {
|
|
55
|
+
dataset = await openDataset({
|
|
56
|
+
args,
|
|
57
|
+
id: randomId(),
|
|
58
|
+
loadParquet,
|
|
59
|
+
runtime
|
|
60
|
+
});
|
|
61
|
+
} finally {
|
|
62
|
+
const remaining = (pendingOpens.get(context.sessionId) ?? 1) - 1;
|
|
63
|
+
if (remaining > 0) pendingOpens.set(context.sessionId, remaining);
|
|
64
|
+
else pendingOpens.delete(context.sessionId);
|
|
65
|
+
}
|
|
66
|
+
const datasets = sessionDatasets(sessions, context.sessionId);
|
|
67
|
+
if (datasets.size >= MAX_OPEN_DATASETS) {
|
|
68
|
+
throw new Error(`dataset session already has ${MAX_OPEN_DATASETS} open datasets; close one first`);
|
|
69
|
+
}
|
|
70
|
+
if (openDatasetCount(sessions) >= MAX_TOTAL_OPEN_DATASETS) {
|
|
71
|
+
throw new Error(`dataset tool already has ${MAX_TOTAL_OPEN_DATASETS} open datasets; close one first`);
|
|
72
|
+
}
|
|
73
|
+
datasets.set(dataset.id, dataset);
|
|
74
|
+
return publicDataset(dataset);
|
|
75
|
+
}
|
|
76
|
+
if (operation === "query") {
|
|
77
|
+
const dataset = requireDataset(sessions, context.sessionId, args.dataset_id);
|
|
78
|
+
const query = parseQuery(args, dataset);
|
|
79
|
+
const cacheKey = queryCacheKey(query);
|
|
80
|
+
const cached = dataset.queryCache.get(cacheKey);
|
|
81
|
+
if (cached) return { ...cached, bytesRead: 0, cacheHit: true };
|
|
82
|
+
const budget = new ByteBudget(query.maxBytes);
|
|
83
|
+
const progress = dataset.format === "parquet" ? await queryParquet(dataset, query, budget, runtime, loadParquet, loadCompressors) : await queryJsonl(dataset, query, budget, runtime);
|
|
84
|
+
const result = queryResult(dataset, query, budget, progress);
|
|
85
|
+
dataset.queryCache.set(cacheKey, result);
|
|
86
|
+
return { ...result, cacheHit: false };
|
|
87
|
+
}
|
|
88
|
+
if (operation === "close") {
|
|
89
|
+
const id = requireString(args.dataset_id, "dataset.dataset_id");
|
|
90
|
+
const removed = sessions.get(context.sessionId)?.delete(id) ?? false;
|
|
91
|
+
if (sessions.get(context.sessionId)?.size === 0) sessions.delete(context.sessionId);
|
|
92
|
+
if (removed) rangeCache.deleteDataset(id);
|
|
93
|
+
return { datasetId: id, closed: removed };
|
|
94
|
+
}
|
|
95
|
+
throw new Error("dataset.operation must be open, query, or close");
|
|
96
|
+
},
|
|
97
|
+
releaseSession(sessionId) {
|
|
98
|
+
for (const dataset of sessions.get(sessionId)?.values() ?? []) {
|
|
99
|
+
rangeCache.deleteDataset(dataset.id);
|
|
100
|
+
}
|
|
101
|
+
sessions.delete(sessionId);
|
|
102
|
+
pendingOpens.delete(sessionId);
|
|
103
|
+
},
|
|
104
|
+
dispose() {
|
|
105
|
+
sessions.clear();
|
|
106
|
+
pendingOpens.clear();
|
|
107
|
+
rangeCache.clear();
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function openDataset({
|
|
112
|
+
args,
|
|
113
|
+
id,
|
|
114
|
+
loadParquet,
|
|
115
|
+
runtime
|
|
116
|
+
}) {
|
|
117
|
+
const source = requireObject(args.source, "dataset.source");
|
|
118
|
+
const kind = requireString(source.kind, "dataset.source.kind");
|
|
119
|
+
if (kind === "huggingface") {
|
|
120
|
+
return openHuggingFace(source, id, runtime, loadParquet);
|
|
121
|
+
}
|
|
122
|
+
if (kind !== "url") throw new Error("dataset.source.kind must be url or huggingface");
|
|
123
|
+
const url = publicHttpsUrl(requireString(source.url, "dataset.source.url"));
|
|
124
|
+
const format = parseFormat(source.format, url);
|
|
125
|
+
if (format === "parquet") {
|
|
126
|
+
const parquetPromise = loadParquet();
|
|
127
|
+
const byteLength = await remoteByteLength(url, runtime);
|
|
128
|
+
if (byteLength == null) {
|
|
129
|
+
throw new Error("Parquet URL must expose Content-Length or support a one-byte range request");
|
|
130
|
+
}
|
|
131
|
+
const shard = {
|
|
132
|
+
url: url.href,
|
|
133
|
+
byteLength,
|
|
134
|
+
cacheKey: id,
|
|
135
|
+
allowRedirects: false
|
|
136
|
+
};
|
|
137
|
+
const budget2 = new ByteBudget(OPEN_METADATA_BYTES);
|
|
138
|
+
const parquet = await parquetPromise;
|
|
139
|
+
shard.metadata = await parquet.parquetMetadataAsync(remoteBuffer(shard, budget2, runtime));
|
|
140
|
+
const schema = parquet.parquetSchema(shard.metadata);
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
format,
|
|
144
|
+
source: { kind: "url", url: url.href },
|
|
145
|
+
byteLength,
|
|
146
|
+
rowCount: safeCount(shard.metadata.num_rows),
|
|
147
|
+
schema: parquetColumns(schema),
|
|
148
|
+
queryCache: new QueryResultCache(QUERY_CACHE_BYTES, MAX_CACHED_QUERIES),
|
|
149
|
+
parquet: { shards: [shard], filterKinds: parquetFilterKinds(schema) }
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const dataset = {
|
|
153
|
+
id,
|
|
154
|
+
format,
|
|
155
|
+
source: { kind: "url", url: url.href },
|
|
156
|
+
byteLength: null,
|
|
157
|
+
rowCount: null,
|
|
158
|
+
schema: [],
|
|
159
|
+
queryCache: new QueryResultCache(QUERY_CACHE_BYTES, MAX_CACHED_QUERIES),
|
|
160
|
+
jsonl: {
|
|
161
|
+
object: { url: url.href, byteLength: null, cacheKey: id, allowRedirects: false },
|
|
162
|
+
preview: []
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const budget = new ByteBudget(JSONL_SCHEMA_BYTES);
|
|
166
|
+
const inferred = await scanJsonl(dataset, {
|
|
167
|
+
datasetId: id,
|
|
168
|
+
filters: [],
|
|
169
|
+
offset: 0,
|
|
170
|
+
limit: JSONL_SCHEMA_ROWS,
|
|
171
|
+
maxBytes: JSONL_SCHEMA_BYTES
|
|
172
|
+
}, budget, runtime);
|
|
173
|
+
dataset.schema = inferJsonlSchema(inferred.rows);
|
|
174
|
+
dataset.jsonl.preview = inferred.rows.slice(0, 10);
|
|
175
|
+
return dataset;
|
|
176
|
+
}
|
|
177
|
+
async function openHuggingFace(source, id, runtime, loadParquet) {
|
|
178
|
+
const datasetName = requireString(source.dataset, "dataset.source.dataset");
|
|
179
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(datasetName) || datasetName.length > 200) {
|
|
180
|
+
throw new Error("Hugging Face dataset must be owner/name");
|
|
181
|
+
}
|
|
182
|
+
const encoded = encodeURIComponent(datasetName);
|
|
183
|
+
const parquetPromise = loadParquet();
|
|
184
|
+
const [parquetResponse, sizeResponse] = await Promise.all([
|
|
185
|
+
fetchJson(
|
|
186
|
+
`https://datasets-server.huggingface.co/parquet?dataset=${encoded}`,
|
|
187
|
+
runtime
|
|
188
|
+
),
|
|
189
|
+
fetchJson(
|
|
190
|
+
`https://datasets-server.huggingface.co/size?dataset=${encoded}`,
|
|
191
|
+
runtime
|
|
192
|
+
).catch(() => ({}))
|
|
193
|
+
]);
|
|
194
|
+
const allFiles = parquetResponse.parquet_files ?? [];
|
|
195
|
+
if (!allFiles.length) {
|
|
196
|
+
const pending = parquetResponse.pending?.length ?? 0;
|
|
197
|
+
const failed = parquetResponse.failed?.length ?? 0;
|
|
198
|
+
throw new Error(`Hugging Face has no Parquet shards for ${datasetName} (${pending} pending, ${failed} failed)`);
|
|
199
|
+
}
|
|
200
|
+
const requestedConfig = optionalString(source.config, "dataset.source.config");
|
|
201
|
+
const configs = unique(allFiles.map((file) => file.config));
|
|
202
|
+
const config = chooseDatasetPart(requestedConfig, configs, "default", "config");
|
|
203
|
+
const configFiles = allFiles.filter((file) => file.config === config);
|
|
204
|
+
const requestedSplit = optionalString(source.split, "dataset.source.split");
|
|
205
|
+
const splits = unique(configFiles.map((file) => file.split));
|
|
206
|
+
const split = chooseDatasetPart(requestedSplit, splits, "train", "split");
|
|
207
|
+
const selected = configFiles.filter((file) => file.split === split);
|
|
208
|
+
const shards = selected.map((file, index) => {
|
|
209
|
+
const url = publicHttpsUrl(file.url);
|
|
210
|
+
return {
|
|
211
|
+
url: url.href,
|
|
212
|
+
byteLength: boundedNonnegativeInteger(file.size, "Hugging Face shard size"),
|
|
213
|
+
cacheKey: `${id}:${index}`,
|
|
214
|
+
allowRedirects: huggingFaceRedirectUrl(url)
|
|
215
|
+
};
|
|
216
|
+
});
|
|
217
|
+
const budget = new ByteBudget(OPEN_METADATA_BYTES);
|
|
218
|
+
const parquet = await parquetPromise;
|
|
219
|
+
shards[0].metadata = await parquet.parquetMetadataAsync(remoteBuffer(shards[0], budget, runtime));
|
|
220
|
+
const schema = parquet.parquetSchema(shards[0].metadata);
|
|
221
|
+
const splitSize = sizeResponse.size?.splits?.find(
|
|
222
|
+
(entry) => entry.config === config && entry.split === split
|
|
223
|
+
);
|
|
224
|
+
const byteLength = shards.reduce((total, shard) => total + (shard.byteLength ?? 0), 0);
|
|
225
|
+
return {
|
|
226
|
+
id,
|
|
227
|
+
format: "parquet",
|
|
228
|
+
source: {
|
|
229
|
+
kind: "huggingface",
|
|
230
|
+
dataset: datasetName,
|
|
231
|
+
config,
|
|
232
|
+
split,
|
|
233
|
+
partial: parquetResponse.partial === true
|
|
234
|
+
},
|
|
235
|
+
byteLength: splitSize?.num_bytes_parquet_files ?? byteLength,
|
|
236
|
+
rowCount: splitSize?.num_rows ?? null,
|
|
237
|
+
schema: parquetColumns(schema),
|
|
238
|
+
queryCache: new QueryResultCache(QUERY_CACHE_BYTES, MAX_CACHED_QUERIES),
|
|
239
|
+
parquet: { shards, filterKinds: parquetFilterKinds(schema) }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function queryParquet(dataset, query, budget, runtime, loadParquet, loadCompressors) {
|
|
243
|
+
const parquet = await loadParquet();
|
|
244
|
+
const shards = dataset.parquet?.shards;
|
|
245
|
+
if (!shards) throw new Error("Parquet dataset is unavailable");
|
|
246
|
+
const pushdownFilters = query.filters.filter((filter) => filter.op !== "contains");
|
|
247
|
+
const postFilters = query.filters.filter((filter) => filter.op === "contains");
|
|
248
|
+
const filtered = pushdownFilters.length > 0 || postFilters.length > 0;
|
|
249
|
+
const rows = new QueryRows(query.limit);
|
|
250
|
+
let position = query.position ?? { format: "parquet", shard: 0, row: 0, match: 0 };
|
|
251
|
+
let skip = query.skip;
|
|
252
|
+
let rowsScanned = 0;
|
|
253
|
+
let compressors;
|
|
254
|
+
try {
|
|
255
|
+
for (let shardIndex = position.shard; shardIndex < shards.length; shardIndex++) {
|
|
256
|
+
const shard = shards[shardIndex];
|
|
257
|
+
const metadata = await parquetMetadata(shard, budget, runtime, parquet);
|
|
258
|
+
const file = remoteBuffer(shard, budget, runtime);
|
|
259
|
+
if (!compressors && needsCustomCompressors(metadata, query)) {
|
|
260
|
+
compressors = (await loadCompressors()).compressors;
|
|
261
|
+
}
|
|
262
|
+
const useBloomFilters = pushdownFilters.length > 0 && metadata.row_groups.length <= MAX_BLOOM_FILTER_GROUPS;
|
|
263
|
+
const shardRows = safeCount(metadata.num_rows);
|
|
264
|
+
let rowStart = shardIndex === position.shard ? position.row : 0;
|
|
265
|
+
let matchStart = shardIndex === position.shard ? position.match : 0;
|
|
266
|
+
if (!filtered && skip > 0) {
|
|
267
|
+
const available = shardRows - rowStart;
|
|
268
|
+
if (skip >= available) {
|
|
269
|
+
skip -= available;
|
|
270
|
+
position = { format: "parquet", shard: shardIndex + 1, row: 0, match: 0 };
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
rowStart += skip;
|
|
274
|
+
skip = 0;
|
|
275
|
+
}
|
|
276
|
+
while (rowStart < shardRows) {
|
|
277
|
+
const rowEnd = Math.min(shardRows, rowStart + PARQUET_SCAN_ROWS);
|
|
278
|
+
position = { format: "parquet", shard: shardIndex, row: rowStart, match: matchStart };
|
|
279
|
+
const found = await parquet.parquetReadObjects({
|
|
280
|
+
file,
|
|
281
|
+
metadata,
|
|
282
|
+
columns: postFilters.length ? columnsForPostFilter(query.columns, postFilters) : query.columns,
|
|
283
|
+
filter: pushdownFilters.length ? parquetFilter(pushdownFilters) : void 0,
|
|
284
|
+
rowStart,
|
|
285
|
+
rowEnd,
|
|
286
|
+
compressors,
|
|
287
|
+
useBloomFilters,
|
|
288
|
+
usePageIndex: pushdownFilters.length > 0,
|
|
289
|
+
useOffsetIndex: true
|
|
290
|
+
});
|
|
291
|
+
rowsScanned += rowEnd - rowStart;
|
|
292
|
+
const candidates = postFilters.length
|
|
293
|
+
? found.filter((candidate) => matchesFilters(candidate, postFilters)).map((candidate) => projectRow(candidate, query.columns))
|
|
294
|
+
: found;
|
|
295
|
+
for (let match = matchStart; match < candidates.length; match++) {
|
|
296
|
+
const nextPosition = match + 1 < candidates.length
|
|
297
|
+
? { format: "parquet", shard: shardIndex, row: rowStart, match: match + 1 }
|
|
298
|
+
: { format: "parquet", shard: shardIndex, row: rowEnd, match: 0 };
|
|
299
|
+
if (skip > 0) {
|
|
300
|
+
skip--;
|
|
301
|
+
position = nextPosition;
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const status = rows.add(candidates[match]);
|
|
305
|
+
if (status === "output_limit") {
|
|
306
|
+
return partialQuery(rows, rowsScanned, "output_limit", position, skip);
|
|
307
|
+
}
|
|
308
|
+
position = nextPosition;
|
|
309
|
+
if (status === "limit") {
|
|
310
|
+
return partialQuery(rows, rowsScanned, "limit", position, skip);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
rowStart = rowEnd;
|
|
314
|
+
matchStart = 0;
|
|
315
|
+
position = { format: "parquet", shard: shardIndex, row: rowStart, match: 0 };
|
|
316
|
+
}
|
|
317
|
+
position = { format: "parquet", shard: shardIndex + 1, row: 0, match: 0 };
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (!(error instanceof ByteLimitError)) throw error;
|
|
321
|
+
return partialQuery(rows, rowsScanned, "byte_limit", position, skip);
|
|
322
|
+
}
|
|
323
|
+
if (dataset.source.kind === "huggingface" && dataset.source.partial) {
|
|
324
|
+
return { rows: rows.rows, rowsScanned, complete: false, truncatedReason: "source_partial" };
|
|
325
|
+
}
|
|
326
|
+
return { rows: rows.rows, rowsScanned, complete: true };
|
|
327
|
+
}
|
|
328
|
+
async function queryJsonl(dataset, query, budget, runtime) {
|
|
329
|
+
return scanJsonl(dataset, query, budget, runtime);
|
|
330
|
+
}
|
|
331
|
+
function needsCustomCompressors(metadata, query) {
|
|
332
|
+
const selected = query.columns ? /* @__PURE__ */ new Set([...query.columns, ...query.filters.map((filter) => filter.path[0])]) : void 0;
|
|
333
|
+
return metadata.row_groups.some((group) => group.columns.some((column) => {
|
|
334
|
+
const meta = column.meta_data;
|
|
335
|
+
const name = meta?.path_in_schema[0];
|
|
336
|
+
return name && (!selected || selected.has(name)) && meta.codec !== "UNCOMPRESSED" && meta.codec !== "SNAPPY";
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
async function scanJsonl(dataset, query, budget, runtime) {
|
|
340
|
+
const object = dataset.jsonl?.object;
|
|
341
|
+
if (!object) throw new Error("JSONL dataset is unavailable");
|
|
342
|
+
const rows = new QueryRows(query.limit);
|
|
343
|
+
const startByte = query.position?.byte ?? 0;
|
|
344
|
+
if (object.byteLength != null && startByte >= object.byteLength) {
|
|
345
|
+
return { rows: rows.rows, rowsScanned: 0, complete: true };
|
|
346
|
+
}
|
|
347
|
+
const headers = startByte > 0
|
|
348
|
+
? { range: `bytes=${startByte}-${object.byteLength == null ? "" : object.byteLength - 1}` }
|
|
349
|
+
: void 0;
|
|
350
|
+
const response = await safeFetch(object.url, runtime, { method: "GET", headers }, object.allowRedirects);
|
|
351
|
+
if (!response.ok || !response.body) {
|
|
352
|
+
await response.body?.cancel().catch(() => void 0);
|
|
353
|
+
throw new Error(`JSONL fetch failed with HTTP ${response.status}`);
|
|
354
|
+
}
|
|
355
|
+
if (startByte > 0 && response.status !== 206) {
|
|
356
|
+
await response.body.cancel().catch(() => void 0);
|
|
357
|
+
throw new Error("JSONL server does not support byte-range continuation");
|
|
358
|
+
}
|
|
359
|
+
if (response.status === 206) {
|
|
360
|
+
const range = parseContentRange(response.headers.get("content-range"));
|
|
361
|
+
if (!range || range.start !== startByte) {
|
|
362
|
+
await response.body.cancel().catch(() => void 0);
|
|
363
|
+
throw new Error("JSONL server returned an invalid Content-Range");
|
|
364
|
+
}
|
|
365
|
+
if (range.total != null) {
|
|
366
|
+
object.byteLength = range.total;
|
|
367
|
+
dataset.byteLength = range.total;
|
|
368
|
+
}
|
|
369
|
+
} else if (object.byteLength == null) {
|
|
370
|
+
object.byteLength = contentLength(response.headers.get("content-length"));
|
|
371
|
+
dataset.byteLength = object.byteLength;
|
|
372
|
+
}
|
|
373
|
+
const reader = response.body.getReader();
|
|
374
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
375
|
+
const encoder = new TextEncoder();
|
|
376
|
+
let buffer = "";
|
|
377
|
+
let networkOffset = startByte;
|
|
378
|
+
let position = { format: "jsonl", byte: startByte };
|
|
379
|
+
let rowsScanned = 0;
|
|
380
|
+
let skip = query.skip;
|
|
381
|
+
const processLine = (line, newlineBytes) => {
|
|
382
|
+
const lineStart = position.byte;
|
|
383
|
+
const lineEnd = lineStart + encoder.encode(line).byteLength + newlineBytes;
|
|
384
|
+
position = { format: "jsonl", byte: lineEnd };
|
|
385
|
+
const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
386
|
+
if (!trimmed) return "accepted";
|
|
387
|
+
if (trimmed.length > MAX_JSONL_LINE_BYTES) {
|
|
388
|
+
throw new Error(`JSONL row exceeds ${MAX_JSONL_LINE_BYTES} characters`);
|
|
389
|
+
}
|
|
390
|
+
let value;
|
|
391
|
+
try {
|
|
392
|
+
value = JSON.parse(trimmed);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
throw new Error(`JSONL row ${rowsScanned + 1} is invalid: ${errorMessage(error)}`);
|
|
395
|
+
}
|
|
396
|
+
rowsScanned++;
|
|
397
|
+
if (!isRecord(value) || !matchesFilters(value, query.filters)) return "accepted";
|
|
398
|
+
if (skip > 0) {
|
|
399
|
+
skip--;
|
|
400
|
+
return "accepted";
|
|
401
|
+
}
|
|
402
|
+
const status = rows.add(projectRow(value, query.columns));
|
|
403
|
+
if (status === "output_limit") {
|
|
404
|
+
position = { format: "jsonl", byte: lineStart };
|
|
405
|
+
}
|
|
406
|
+
return status;
|
|
407
|
+
};
|
|
408
|
+
try {
|
|
409
|
+
while (true) {
|
|
410
|
+
const chunk = await reader.read();
|
|
411
|
+
if (chunk.done) break;
|
|
412
|
+
const acceptedBytes = Math.min(chunk.value.byteLength, budget.remaining);
|
|
413
|
+
if (acceptedBytes === 0) throw new ByteLimitError("dataset byte limit reached");
|
|
414
|
+
budget.consume(acceptedBytes);
|
|
415
|
+
buffer += decoder.decode(chunk.value.subarray(0, acceptedBytes), { stream: true });
|
|
416
|
+
networkOffset += acceptedBytes;
|
|
417
|
+
if (buffer.length > MAX_JSONL_LINE_BYTES && !buffer.includes("\n")) {
|
|
418
|
+
throw new Error(`JSONL row exceeds ${MAX_JSONL_LINE_BYTES} characters`);
|
|
419
|
+
}
|
|
420
|
+
for (let newline = buffer.indexOf("\n"); newline >= 0; newline = buffer.indexOf("\n")) {
|
|
421
|
+
const status = processLine(buffer.slice(0, newline), 1);
|
|
422
|
+
buffer = buffer.slice(newline + 1);
|
|
423
|
+
if (status === "limit" || status === "output_limit") {
|
|
424
|
+
return partialQuery(rows, rowsScanned, status, position, skip);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (acceptedBytes < chunk.value.byteLength || budget.remaining === 0 && (object.byteLength == null || networkOffset < object.byteLength)) {
|
|
428
|
+
return partialQuery(rows, rowsScanned, "byte_limit", position, skip);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
buffer += decoder.decode();
|
|
432
|
+
if (buffer) {
|
|
433
|
+
const status = processLine(buffer, 0);
|
|
434
|
+
if (status === "limit" || status === "output_limit") {
|
|
435
|
+
return partialQuery(rows, rowsScanned, status, position, skip);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch (error) {
|
|
439
|
+
if (!(error instanceof ByteLimitError)) throw error;
|
|
440
|
+
return partialQuery(rows, rowsScanned, "byte_limit", position, skip);
|
|
441
|
+
} finally {
|
|
442
|
+
await reader.cancel().catch(() => void 0);
|
|
443
|
+
}
|
|
444
|
+
return { rows: rows.rows, rowsScanned, complete: true };
|
|
445
|
+
}
|
|
446
|
+
async function parquetMetadata(shard, budget, runtime, parquet) {
|
|
447
|
+
shard.metadata ??= await parquet.parquetMetadataAsync(remoteBuffer(shard, budget, runtime));
|
|
448
|
+
return shard.metadata;
|
|
449
|
+
}
|
|
450
|
+
function remoteBuffer(object, budget, runtime) {
|
|
451
|
+
if (object.byteLength == null) throw new Error("remote object size is unknown");
|
|
452
|
+
return {
|
|
453
|
+
byteLength: object.byteLength,
|
|
454
|
+
async slice(start, end = object.byteLength) {
|
|
455
|
+
const normalizedStart = Math.max(0, start);
|
|
456
|
+
const normalizedEnd = Math.min(object.byteLength, end);
|
|
457
|
+
if (!Number.isSafeInteger(normalizedStart) || !Number.isSafeInteger(normalizedEnd) || normalizedStart > normalizedEnd) {
|
|
458
|
+
throw new Error(`invalid dataset byte range ${start}-${end}`);
|
|
459
|
+
}
|
|
460
|
+
const length = normalizedEnd - normalizedStart;
|
|
461
|
+
budget.consume(length);
|
|
462
|
+
if (length === 0) return new ArrayBuffer(0);
|
|
463
|
+
const key = `${object.cacheKey}
|
|
464
|
+
${normalizedStart}-${normalizedEnd}`;
|
|
465
|
+
return runtime.rangeCache.read(key, length, async () => {
|
|
466
|
+
const response = await safeFetch(object.url, runtime, {
|
|
467
|
+
method: "GET",
|
|
468
|
+
headers: { range: `bytes=${normalizedStart}-${normalizedEnd - 1}` }
|
|
469
|
+
}, object.allowRedirects);
|
|
470
|
+
if (response.status !== 206) {
|
|
471
|
+
await response.body?.cancel().catch(() => void 0);
|
|
472
|
+
throw new Error(`dataset server ignored byte range and returned HTTP ${response.status}`);
|
|
473
|
+
}
|
|
474
|
+
const contentRange = response.headers.get("content-range");
|
|
475
|
+
if (contentRange !== `bytes ${normalizedStart}-${normalizedEnd - 1}/${object.byteLength}`) {
|
|
476
|
+
await response.body?.cancel().catch(() => void 0);
|
|
477
|
+
throw new Error("dataset server returned an invalid Content-Range");
|
|
478
|
+
}
|
|
479
|
+
return readExactResponse(response, length);
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function remoteByteLength(url, runtime) {
|
|
485
|
+
const head = await safeFetch(url.href, runtime, { method: "HEAD" }).catch(() => void 0);
|
|
486
|
+
if (head?.ok) {
|
|
487
|
+
const length = contentLength(head.headers.get("content-length"));
|
|
488
|
+
if (length != null) return length;
|
|
489
|
+
}
|
|
490
|
+
const response = await safeFetch(url.href, runtime, {
|
|
491
|
+
method: "GET",
|
|
492
|
+
headers: { range: "bytes=0-0" }
|
|
493
|
+
});
|
|
494
|
+
const match = response.status === 206 ? response.headers.get("content-range")?.match(/^bytes 0-0\/(\d+)$/) : void 0;
|
|
495
|
+
await response.body?.cancel().catch(() => void 0);
|
|
496
|
+
return match ? contentLength(match[1]) : null;
|
|
497
|
+
}
|
|
498
|
+
async function fetchJson(url, runtime) {
|
|
499
|
+
const response = await safeFetch(url, runtime, { method: "GET" });
|
|
500
|
+
if (!response.ok) {
|
|
501
|
+
await response.body?.cancel().catch(() => void 0);
|
|
502
|
+
throw new Error(`dataset metadata request failed with HTTP ${response.status}`);
|
|
503
|
+
}
|
|
504
|
+
const declared = contentLength(response.headers.get("content-length"));
|
|
505
|
+
if (declared != null && declared > MAX_API_RESPONSE_BYTES) {
|
|
506
|
+
throw new Error("dataset metadata response is too large");
|
|
507
|
+
}
|
|
508
|
+
const body = await readBoundedText(response, MAX_API_RESPONSE_BYTES);
|
|
509
|
+
try {
|
|
510
|
+
return JSON.parse(body);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
throw new Error(`dataset metadata response is invalid JSON: ${errorMessage(error)}`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async function readExactResponse(response, expectedBytes) {
|
|
516
|
+
const declared = contentLength(response.headers.get("content-length"));
|
|
517
|
+
if (declared != null && declared !== expectedBytes) {
|
|
518
|
+
await response.body?.cancel().catch(() => void 0);
|
|
519
|
+
throw new Error(`dataset byte range declared ${declared} bytes; expected ${expectedBytes}`);
|
|
520
|
+
}
|
|
521
|
+
if (!response.body) throw new Error("dataset byte range has no response body");
|
|
522
|
+
const reader = response.body.getReader();
|
|
523
|
+
const output = new Uint8Array(expectedBytes);
|
|
524
|
+
let offset = 0;
|
|
525
|
+
let finished = false;
|
|
526
|
+
try {
|
|
527
|
+
while (true) {
|
|
528
|
+
const chunk = await reader.read();
|
|
529
|
+
if (chunk.done) break;
|
|
530
|
+
if (offset + chunk.value.byteLength > expectedBytes) {
|
|
531
|
+
throw new Error(`dataset byte range exceeds expected ${expectedBytes} bytes`);
|
|
532
|
+
}
|
|
533
|
+
output.set(chunk.value, offset);
|
|
534
|
+
offset += chunk.value.byteLength;
|
|
535
|
+
}
|
|
536
|
+
finished = offset === expectedBytes;
|
|
537
|
+
} finally {
|
|
538
|
+
if (!finished) await reader.cancel().catch(() => void 0);
|
|
539
|
+
}
|
|
540
|
+
if (offset !== expectedBytes) {
|
|
541
|
+
throw new Error(`dataset byte range returned ${offset} bytes; expected ${expectedBytes}`);
|
|
542
|
+
}
|
|
543
|
+
return output.buffer;
|
|
544
|
+
}
|
|
545
|
+
async function readBoundedText(response, maxBytes) {
|
|
546
|
+
if (!response.body) return "";
|
|
547
|
+
const reader = response.body.getReader();
|
|
548
|
+
const decoder = new TextDecoder();
|
|
549
|
+
let bytes = 0;
|
|
550
|
+
let text = "";
|
|
551
|
+
try {
|
|
552
|
+
while (true) {
|
|
553
|
+
const chunk = await reader.read();
|
|
554
|
+
if (chunk.done) break;
|
|
555
|
+
bytes += chunk.value.byteLength;
|
|
556
|
+
if (bytes > maxBytes) throw new Error("dataset metadata response is too large");
|
|
557
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
558
|
+
}
|
|
559
|
+
return text + decoder.decode();
|
|
560
|
+
} finally {
|
|
561
|
+
await reader.cancel().catch(() => void 0);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
async function safeFetch(input, runtime, init, allowRedirects = false) {
|
|
565
|
+
publicHttpsUrl(input);
|
|
566
|
+
const timeout = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
567
|
+
const signal = runtime.signal ? AbortSignal.any([runtime.signal, timeout]) : timeout;
|
|
568
|
+
const response = await runtime.fetch(input, {
|
|
569
|
+
...init,
|
|
570
|
+
credentials: "omit",
|
|
571
|
+
mode: "cors",
|
|
572
|
+
redirect: allowRedirects ? "follow" : "error",
|
|
573
|
+
referrerPolicy: "no-referrer",
|
|
574
|
+
signal
|
|
575
|
+
});
|
|
576
|
+
if (response.url) publicHttpsUrl(response.url);
|
|
577
|
+
return response;
|
|
578
|
+
}
|
|
579
|
+
function parseQuery(args, dataset) {
|
|
580
|
+
const datasetId = requireString(args.dataset_id, "dataset.dataset_id");
|
|
581
|
+
const cursor = args.cursor === void 0 ? void 0 : requireString(args.cursor, "dataset.cursor");
|
|
582
|
+
let source = args;
|
|
583
|
+
let position;
|
|
584
|
+
let offset;
|
|
585
|
+
let skip;
|
|
586
|
+
if (cursor !== void 0) {
|
|
587
|
+
if (args.columns !== void 0 || args.filters !== void 0 || args.offset !== void 0) {
|
|
588
|
+
throw new Error("dataset.cursor retains columns, filters, and position; do not combine them with cursor");
|
|
589
|
+
}
|
|
590
|
+
const state = decodeQueryCursor(cursor);
|
|
591
|
+
if (state.v !== CURSOR_VERSION || state.d !== dataset.id || !isRecord(state.q)) {
|
|
592
|
+
throw new Error("dataset.cursor is unavailable for this dataset handle");
|
|
593
|
+
}
|
|
594
|
+
source = state.q;
|
|
595
|
+
position = parseCursorPosition(state.p, dataset.format);
|
|
596
|
+
offset = nonnegativeSafeInteger(state.o, "dataset.cursor offset");
|
|
597
|
+
skip = nonnegativeSafeInteger(state.s, "dataset.cursor remaining offset");
|
|
598
|
+
} else {
|
|
599
|
+
offset = optionalIntegerAtLeast(args.offset, 0, 0, "dataset.offset");
|
|
600
|
+
skip = offset;
|
|
601
|
+
}
|
|
602
|
+
const columns = optionalStringArray(source.columns, "dataset.columns");
|
|
603
|
+
if (columns && dataset.format === "parquet") {
|
|
604
|
+
const known = new Set(dataset.schema.map((column) => column.name));
|
|
605
|
+
const unknown = columns.filter((column) => !known.has(column));
|
|
606
|
+
if (unknown.length) throw new Error(`dataset columns not found: ${unknown.join(", ")}`);
|
|
607
|
+
}
|
|
608
|
+
let filters = parseFilters(source.filters);
|
|
609
|
+
if (dataset.format === "parquet") {
|
|
610
|
+
const known = new Set(dataset.schema.map((column) => column.name));
|
|
611
|
+
const unknown = filters.map((filter) => filter.column.split(".")[0]).filter((column) => !known.has(column));
|
|
612
|
+
if (unknown.length) throw new Error(`dataset filter columns not found: ${unique(unknown).join(", ")}`);
|
|
613
|
+
filters = coerceParquetFilters(filters, dataset.parquet?.filterKinds ?? /* @__PURE__ */ new Map());
|
|
614
|
+
}
|
|
615
|
+
const limit = optionalIntegerAtLeast(args.limit, 1, DEFAULT_QUERY_LIMIT, "dataset.limit");
|
|
616
|
+
const maxBytes = optionalBoundedInteger(
|
|
617
|
+
args.max_bytes,
|
|
618
|
+
1024,
|
|
619
|
+
MAX_QUERY_BYTES,
|
|
620
|
+
DEFAULT_QUERY_BYTES,
|
|
621
|
+
"dataset.max_bytes"
|
|
622
|
+
);
|
|
623
|
+
return {
|
|
624
|
+
datasetId,
|
|
625
|
+
columns,
|
|
626
|
+
filters,
|
|
627
|
+
offset,
|
|
628
|
+
limit,
|
|
629
|
+
maxBytes,
|
|
630
|
+
position,
|
|
631
|
+
skip,
|
|
632
|
+
cursorQuery: {
|
|
633
|
+
...(columns === void 0 ? {} : { columns }),
|
|
634
|
+
...(source.filters === void 0 ? {} : { filters: source.filters })
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function coerceParquetFilters(filters, kinds) {
|
|
639
|
+
return filters.map((filter) => {
|
|
640
|
+
const kind = kinds.get(filter.column);
|
|
641
|
+
if (!kind) return filter;
|
|
642
|
+
if (filter.op === "contains") {
|
|
643
|
+
throw new Error(`dataset filter ${filter.column} does not support contains`);
|
|
644
|
+
}
|
|
645
|
+
const value = filter.op === "in" ? filter.value.map((entry) => coerceParquetFilterValue(entry, kind, filter.column)) : coerceParquetFilterValue(filter.value, kind, filter.column);
|
|
646
|
+
return { ...filter, value };
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
function coerceParquetFilterValue(value, kind, column) {
|
|
650
|
+
if (value === null) return null;
|
|
651
|
+
if (kind === "bigint") {
|
|
652
|
+
if (typeof value === "bigint") return value;
|
|
653
|
+
if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
|
|
654
|
+
if (typeof value === "string" && /^-?\d+$/.test(value)) return BigInt(value);
|
|
655
|
+
throw new Error(`dataset filter ${column} requires a safe integer or integer string`);
|
|
656
|
+
}
|
|
657
|
+
if (value instanceof Date && Number.isFinite(value.getTime())) return value;
|
|
658
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
659
|
+
const date = new Date(value);
|
|
660
|
+
if (Number.isFinite(date.getTime())) return date;
|
|
661
|
+
}
|
|
662
|
+
throw new Error(`dataset filter ${column} requires an ISO date or timestamp`);
|
|
663
|
+
}
|
|
664
|
+
function parseFilters(value) {
|
|
665
|
+
if (value === void 0) return [];
|
|
666
|
+
if (!Array.isArray(value)) throw new Error("dataset.filters must be an array");
|
|
667
|
+
if (value.length > 8) throw new Error("dataset.filters supports at most 8 filters");
|
|
668
|
+
return value.map((entry, index) => {
|
|
669
|
+
const filter = requireObject(entry, `dataset.filters[${index}]`);
|
|
670
|
+
const column = requireString(filter.column, `dataset.filters[${index}].column`);
|
|
671
|
+
const op = requireString(filter.op, `dataset.filters[${index}].op`);
|
|
672
|
+
if (!["eq", "ne", "gt", "gte", "lt", "lte", "in", "contains"].includes(op)) {
|
|
673
|
+
throw new Error(`dataset.filters[${index}].op is unsupported`);
|
|
674
|
+
}
|
|
675
|
+
if (op === "in" && (!Array.isArray(filter.value) || filter.value.length > 100)) {
|
|
676
|
+
throw new Error(`dataset.filters[${index}].value must be an array with at most 100 values`);
|
|
677
|
+
}
|
|
678
|
+
if (op === "contains" && typeof filter.value !== "string") {
|
|
679
|
+
throw new Error(`dataset.filters[${index}].value must be a string for contains`);
|
|
680
|
+
}
|
|
681
|
+
return { column, path: column.split("."), op, value: filter.value };
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
function parquetFilter(filters) {
|
|
685
|
+
if (!filters.length) return void 0;
|
|
686
|
+
const clauses = filters.map((filter) => ({
|
|
687
|
+
[filter.column]: { [`$${filter.op}`]: filter.value }
|
|
688
|
+
}));
|
|
689
|
+
return clauses.length === 1 ? clauses[0] : { $and: clauses };
|
|
690
|
+
}
|
|
691
|
+
function matchesFilters(row, filters) {
|
|
692
|
+
return filters.every((filter) => {
|
|
693
|
+
const actual = pathPartsValue(row, filter.path);
|
|
694
|
+
switch (filter.op) {
|
|
695
|
+
case "eq":
|
|
696
|
+
return actual === filter.value;
|
|
697
|
+
case "ne":
|
|
698
|
+
return actual !== filter.value;
|
|
699
|
+
case "gt":
|
|
700
|
+
return comparable(actual, filter.value) > 0;
|
|
701
|
+
case "gte":
|
|
702
|
+
return comparable(actual, filter.value) >= 0;
|
|
703
|
+
case "lt":
|
|
704
|
+
return comparable(actual, filter.value) < 0;
|
|
705
|
+
case "lte":
|
|
706
|
+
return comparable(actual, filter.value) <= 0;
|
|
707
|
+
case "in":
|
|
708
|
+
return filter.value.some((value) => value === actual);
|
|
709
|
+
case "contains":
|
|
710
|
+
return typeof actual === "string" && actual.includes(filter.value);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function comparable(left, right) {
|
|
715
|
+
if (typeof left === "number" && typeof right === "number") return left - right;
|
|
716
|
+
if (typeof left === "string" && typeof right === "string") return left.localeCompare(right);
|
|
717
|
+
return Number.NaN;
|
|
718
|
+
}
|
|
719
|
+
function pathValue(row, path) {
|
|
720
|
+
return pathPartsValue(row, path.split("."));
|
|
721
|
+
}
|
|
722
|
+
function pathPartsValue(row, path) {
|
|
723
|
+
let value = row;
|
|
724
|
+
for (const part of path) {
|
|
725
|
+
if (!isRecord(value)) return void 0;
|
|
726
|
+
value = value[part];
|
|
727
|
+
}
|
|
728
|
+
return value;
|
|
729
|
+
}
|
|
730
|
+
function projectRow(row, columns) {
|
|
731
|
+
if (!columns) return row;
|
|
732
|
+
return Object.fromEntries(columns.map((column) => [column, pathValue(row, column)]));
|
|
733
|
+
}
|
|
734
|
+
function columnsForPostFilter(columns, filters) {
|
|
735
|
+
if (!columns) return void 0;
|
|
736
|
+
return unique([...columns, ...filters.map((filter) => filter.column.split(".")[0])]);
|
|
737
|
+
}
|
|
738
|
+
function parquetColumns(schema) {
|
|
739
|
+
return schema.children.map((column) => ({
|
|
740
|
+
name: column.element.name,
|
|
741
|
+
type: column.element.logical_type?.type ?? column.element.converted_type ?? column.element.type ?? (column.children.length ? "STRUCT" : "UNKNOWN"),
|
|
742
|
+
nullable: column.element.repetition_type !== "REQUIRED"
|
|
743
|
+
}));
|
|
744
|
+
}
|
|
745
|
+
function parquetFilterKinds(schema) {
|
|
746
|
+
const kinds = /* @__PURE__ */ new Map();
|
|
747
|
+
const visit = (node, prefix) => {
|
|
748
|
+
for (const child of node.children) {
|
|
749
|
+
const path = [...prefix, child.element.name];
|
|
750
|
+
if (child.children.length) {
|
|
751
|
+
visit(child, path);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const element = child.element;
|
|
755
|
+
const logical = element.logical_type?.type;
|
|
756
|
+
const converted = element.converted_type;
|
|
757
|
+
if (logical === "DATE" || logical === "TIMESTAMP" || converted === "DATE" || converted === "TIMESTAMP_MILLIS" || converted === "TIMESTAMP_MICROS" || element.type === "INT96") {
|
|
758
|
+
kinds.set(path.join("."), "date");
|
|
759
|
+
} else if (element.type === "INT64" && logical !== "DECIMAL" && converted !== "DECIMAL") {
|
|
760
|
+
kinds.set(path.join("."), "bigint");
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
visit(schema, []);
|
|
765
|
+
return kinds;
|
|
766
|
+
}
|
|
767
|
+
function inferJsonlSchema(rows) {
|
|
768
|
+
const types = /* @__PURE__ */ new Map();
|
|
769
|
+
for (const row of rows) {
|
|
770
|
+
for (const [key, value] of Object.entries(row)) {
|
|
771
|
+
let seen = types.get(key);
|
|
772
|
+
if (!seen) {
|
|
773
|
+
seen = /* @__PURE__ */ new Set();
|
|
774
|
+
types.set(key, seen);
|
|
775
|
+
}
|
|
776
|
+
seen.add(valueType(value));
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return [...types].map(([name, seen]) => ({ name, type: [...seen].sort().join(" | ") }));
|
|
780
|
+
}
|
|
781
|
+
function valueType(value) {
|
|
782
|
+
if (value === null) return "null";
|
|
783
|
+
if (Array.isArray(value)) return "array";
|
|
784
|
+
return typeof value === "object" ? "object" : typeof value;
|
|
785
|
+
}
|
|
786
|
+
function queryResult(dataset, query, budget, progress) {
|
|
787
|
+
const nextCursor = progress.nextPosition === void 0 ? void 0 : encodeQueryCursor({
|
|
788
|
+
v: CURSOR_VERSION,
|
|
789
|
+
d: dataset.id,
|
|
790
|
+
q: query.cursorQuery,
|
|
791
|
+
p: progress.nextPosition,
|
|
792
|
+
o: safeIntegerSum(query.offset, progress.rows.length, "dataset cursor offset"),
|
|
793
|
+
s: progress.nextSkip
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
datasetId: dataset.id,
|
|
797
|
+
format: dataset.format,
|
|
798
|
+
rows: progress.rows,
|
|
799
|
+
returnedRows: progress.rows.length,
|
|
800
|
+
rowsScanned: progress.rowsScanned,
|
|
801
|
+
bytesRead: budget.used,
|
|
802
|
+
complete: progress.complete,
|
|
803
|
+
truncatedReason: progress.truncatedReason,
|
|
804
|
+
offset: query.offset,
|
|
805
|
+
limit: query.limit,
|
|
806
|
+
...(nextCursor === void 0 ? {} : { nextCursor })
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
function queryCacheKey(query) {
|
|
810
|
+
return JSON.stringify({
|
|
811
|
+
cursorQuery: query.cursorQuery,
|
|
812
|
+
position: query.position,
|
|
813
|
+
offset: query.offset,
|
|
814
|
+
skip: query.skip,
|
|
815
|
+
limit: query.limit,
|
|
816
|
+
maxBytes: query.maxBytes
|
|
817
|
+
}, (_key, value) => typeof value === "bigint" ? { $bigint: value.toString() } : value);
|
|
818
|
+
}
|
|
819
|
+
function partialQuery(rows, rowsScanned, truncatedReason, nextPosition, nextSkip) {
|
|
820
|
+
return {
|
|
821
|
+
rows: rows.rows,
|
|
822
|
+
rowsScanned,
|
|
823
|
+
complete: false,
|
|
824
|
+
truncatedReason,
|
|
825
|
+
nextPosition,
|
|
826
|
+
nextSkip
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function encodeQueryCursor(state) {
|
|
830
|
+
return encodeURIComponent(JSON.stringify(state));
|
|
831
|
+
}
|
|
832
|
+
function decodeQueryCursor(cursor) {
|
|
833
|
+
try {
|
|
834
|
+
const state = JSON.parse(decodeURIComponent(cursor));
|
|
835
|
+
if (!isRecord(state)) throw new Error("cursor state must be an object");
|
|
836
|
+
return state;
|
|
837
|
+
} catch {
|
|
838
|
+
throw new Error("dataset.cursor is invalid");
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function parseCursorPosition(value, format) {
|
|
842
|
+
if (!isRecord(value) || value.format !== format) {
|
|
843
|
+
throw new Error("dataset.cursor is unavailable for this dataset format");
|
|
844
|
+
}
|
|
845
|
+
if (format === "parquet") {
|
|
846
|
+
return {
|
|
847
|
+
format,
|
|
848
|
+
shard: nonnegativeSafeInteger(value.shard, "dataset.cursor shard"),
|
|
849
|
+
row: nonnegativeSafeInteger(value.row, "dataset.cursor row"),
|
|
850
|
+
match: nonnegativeSafeInteger(value.match, "dataset.cursor match")
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
format,
|
|
855
|
+
byte: nonnegativeSafeInteger(value.byte, "dataset.cursor byte")
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function parseContentRange(value) {
|
|
859
|
+
const match = value?.match(/^bytes (\d+)-(\d+)\/(\d+|\*)$/);
|
|
860
|
+
if (!match) return null;
|
|
861
|
+
const start = contentLength(match[1]);
|
|
862
|
+
const end = contentLength(match[2]);
|
|
863
|
+
const total = match[3] === "*" ? null : contentLength(match[3]);
|
|
864
|
+
if (start == null || end == null || end < start || total != null && end >= total) return null;
|
|
865
|
+
return { start, end, total };
|
|
866
|
+
}
|
|
867
|
+
function normalizeValue(value, seen = /* @__PURE__ */ new WeakSet()) {
|
|
868
|
+
if (value === void 0) return null;
|
|
869
|
+
if (typeof value === "bigint") {
|
|
870
|
+
const number = Number(value);
|
|
871
|
+
return Number.isSafeInteger(number) ? number : value.toString();
|
|
872
|
+
}
|
|
873
|
+
if (typeof value === "string") {
|
|
874
|
+
return value.length <= MAX_CELL_STRING_LENGTH ? value : `${value.slice(0, MAX_CELL_STRING_LENGTH)}\u2026[truncated ${value.length - MAX_CELL_STRING_LENGTH} chars]`;
|
|
875
|
+
}
|
|
876
|
+
if (value instanceof Date) return value.toISOString();
|
|
877
|
+
if (value instanceof Uint8Array) {
|
|
878
|
+
return { binaryBytes: value.byteLength, previewHex: hex(value.subarray(0, 32)) };
|
|
879
|
+
}
|
|
880
|
+
if (Array.isArray(value)) return value.map((entry) => normalizeValue(entry, seen));
|
|
881
|
+
if (isRecord(value)) {
|
|
882
|
+
if (seen.has(value)) return "[circular]";
|
|
883
|
+
seen.add(value);
|
|
884
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, normalizeValue(entry, seen)]));
|
|
885
|
+
}
|
|
886
|
+
return value;
|
|
887
|
+
}
|
|
888
|
+
function publicDataset(dataset) {
|
|
889
|
+
return {
|
|
890
|
+
datasetId: dataset.id,
|
|
891
|
+
format: dataset.format,
|
|
892
|
+
source: dataset.source,
|
|
893
|
+
byteLength: dataset.byteLength,
|
|
894
|
+
rowCount: dataset.rowCount,
|
|
895
|
+
schema: dataset.schema,
|
|
896
|
+
shardCount: dataset.parquet?.shards.length ?? 1,
|
|
897
|
+
previewRows: dataset.jsonl?.preview.map((row) => normalizeValue(row))
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
function requireDataset(sessions, sessionId, value) {
|
|
901
|
+
const id = requireString(value, "dataset.dataset_id");
|
|
902
|
+
const dataset = sessions.get(sessionId)?.get(id);
|
|
903
|
+
if (!dataset) throw new Error("dataset handle is unavailable in this agent session");
|
|
904
|
+
return dataset;
|
|
905
|
+
}
|
|
906
|
+
function sessionDatasets(sessions, sessionId) {
|
|
907
|
+
let datasets = sessions.get(sessionId);
|
|
908
|
+
if (!datasets) {
|
|
909
|
+
datasets = /* @__PURE__ */ new Map();
|
|
910
|
+
sessions.set(sessionId, datasets);
|
|
911
|
+
}
|
|
912
|
+
return datasets;
|
|
913
|
+
}
|
|
914
|
+
function openDatasetCount(sessions) {
|
|
915
|
+
let count = 0;
|
|
916
|
+
for (const datasets of sessions.values()) count += datasets.size;
|
|
917
|
+
return count;
|
|
918
|
+
}
|
|
919
|
+
function pendingOpenCount(pending) {
|
|
920
|
+
let count = 0;
|
|
921
|
+
for (const value of pending.values()) count += value;
|
|
922
|
+
return count;
|
|
923
|
+
}
|
|
924
|
+
function parseFormat(value, url) {
|
|
925
|
+
if (value !== void 0) {
|
|
926
|
+
if (value === "parquet" || value === "jsonl") return value;
|
|
927
|
+
throw new Error("dataset.source.format must be parquet or jsonl");
|
|
928
|
+
}
|
|
929
|
+
const path = url.pathname.toLowerCase();
|
|
930
|
+
if (path.endsWith(".parquet")) return "parquet";
|
|
931
|
+
if (path.endsWith(".jsonl") || path.endsWith(".ndjson")) return "jsonl";
|
|
932
|
+
if (path.endsWith(".jsonl.gz") || path.endsWith(".ndjson.gz") || path.endsWith(".jsonl.zst")) {
|
|
933
|
+
throw new Error("compressed JSONL requires a converted Parquet source or an uncompressed streaming URL");
|
|
934
|
+
}
|
|
935
|
+
throw new Error("dataset.source.format is required when the URL extension is not .parquet, .jsonl, or .ndjson");
|
|
936
|
+
}
|
|
937
|
+
function publicHttpsUrl(value) {
|
|
938
|
+
let url;
|
|
939
|
+
try {
|
|
940
|
+
url = new URL(value);
|
|
941
|
+
} catch {
|
|
942
|
+
throw new Error("dataset URL is invalid");
|
|
943
|
+
}
|
|
944
|
+
if (url.protocol !== "https:" || url.username || url.password) {
|
|
945
|
+
throw new Error("dataset URL must be credential-free HTTPS");
|
|
946
|
+
}
|
|
947
|
+
const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
948
|
+
if (unsafeHostname(hostname)) throw new Error("dataset URL must use a public hostname");
|
|
949
|
+
return url;
|
|
950
|
+
}
|
|
951
|
+
function huggingFaceRedirectUrl(url) {
|
|
952
|
+
const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
|
|
953
|
+
return hostname === "huggingface.co" || hostname.endsWith(".huggingface.co");
|
|
954
|
+
}
|
|
955
|
+
function unsafeHostname(hostname) {
|
|
956
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname.endsWith(".internal") || hostname.includes(":")) return true;
|
|
957
|
+
const parts = hostname.split(".");
|
|
958
|
+
if (parts.length !== 4 || parts.some((part) => !/^\d+$/.test(part))) return false;
|
|
959
|
+
const octets = parts.map(Number);
|
|
960
|
+
if (octets.some((part) => part < 0 || part > 255)) return true;
|
|
961
|
+
const [a, b] = octets;
|
|
962
|
+
return a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
|
|
963
|
+
}
|
|
964
|
+
function chooseDatasetPart(requested, available, preferred, label) {
|
|
965
|
+
if (requested) {
|
|
966
|
+
if (!available.includes(requested)) {
|
|
967
|
+
throw new Error(`Hugging Face ${label} ${requested} was not found; available: ${available.join(", ")}`);
|
|
968
|
+
}
|
|
969
|
+
return requested;
|
|
970
|
+
}
|
|
971
|
+
if (available.includes(preferred)) return preferred;
|
|
972
|
+
if (available.length === 1) return available[0];
|
|
973
|
+
throw new Error(`Hugging Face ${label} is required; available: ${available.join(", ")}`);
|
|
974
|
+
}
|
|
975
|
+
function requireObject(value, name) {
|
|
976
|
+
if (!isRecord(value)) throw new Error(`${name} requires an object`);
|
|
977
|
+
return value;
|
|
978
|
+
}
|
|
979
|
+
function isRecord(value) {
|
|
980
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
981
|
+
}
|
|
982
|
+
function requireString(value, name) {
|
|
983
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`${name} must be a non-empty string`);
|
|
984
|
+
return value;
|
|
985
|
+
}
|
|
986
|
+
function optionalString(value, name) {
|
|
987
|
+
return value === void 0 ? void 0 : requireString(value, name);
|
|
988
|
+
}
|
|
989
|
+
function optionalStringArray(value, name) {
|
|
990
|
+
if (value === void 0) return void 0;
|
|
991
|
+
if (!Array.isArray(value) || value.length > 64 || value.some((entry) => typeof entry !== "string" || !entry)) {
|
|
992
|
+
throw new Error(`${name} must be an array of at most 64 non-empty strings`);
|
|
993
|
+
}
|
|
994
|
+
return unique(value);
|
|
995
|
+
}
|
|
996
|
+
function optionalBoundedInteger(value, minimum, maximum, fallback, name) {
|
|
997
|
+
if (value === void 0) return fallback;
|
|
998
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
|
|
999
|
+
throw new Error(`${name} must be an integer between ${minimum} and ${maximum}`);
|
|
1000
|
+
}
|
|
1001
|
+
return value;
|
|
1002
|
+
}
|
|
1003
|
+
function optionalIntegerAtLeast(value, minimum, fallback, name) {
|
|
1004
|
+
if (value === void 0) return fallback;
|
|
1005
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
1006
|
+
throw new Error(`${name} must be a safe integer greater than or equal to ${minimum}`);
|
|
1007
|
+
}
|
|
1008
|
+
return value;
|
|
1009
|
+
}
|
|
1010
|
+
function nonnegativeSafeInteger(value, name) {
|
|
1011
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
1012
|
+
throw new Error(`${name} must be a nonnegative safe integer`);
|
|
1013
|
+
}
|
|
1014
|
+
return value;
|
|
1015
|
+
}
|
|
1016
|
+
function safeIntegerSum(left, right, name) {
|
|
1017
|
+
const sum = left + right;
|
|
1018
|
+
if (!Number.isSafeInteger(sum)) throw new Error(`${name} exceeds JavaScript safe integers`);
|
|
1019
|
+
return sum;
|
|
1020
|
+
}
|
|
1021
|
+
function boundedNonnegativeInteger(value, name) {
|
|
1022
|
+
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`${name} is invalid`);
|
|
1023
|
+
return value;
|
|
1024
|
+
}
|
|
1025
|
+
function contentLength(value) {
|
|
1026
|
+
if (value == null || !/^\d+$/.test(value)) return null;
|
|
1027
|
+
const parsed = Number(value);
|
|
1028
|
+
return Number.isSafeInteger(parsed) ? parsed : null;
|
|
1029
|
+
}
|
|
1030
|
+
function safeCount(value) {
|
|
1031
|
+
const number = Number(value);
|
|
1032
|
+
if (!Number.isSafeInteger(number) || number < 0) throw new Error("dataset row count exceeds JavaScript safe integers");
|
|
1033
|
+
return number;
|
|
1034
|
+
}
|
|
1035
|
+
function unique(values) {
|
|
1036
|
+
return [...new Set(values)];
|
|
1037
|
+
}
|
|
1038
|
+
function hex(bytes) {
|
|
1039
|
+
return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1040
|
+
}
|
|
1041
|
+
function errorMessage(error) {
|
|
1042
|
+
return error instanceof Error ? error.message : String(error);
|
|
1043
|
+
}
|
|
1044
|
+
class ByteLimitError extends Error {
|
|
1045
|
+
}
|
|
1046
|
+
class ByteBudget {
|
|
1047
|
+
used = 0;
|
|
1048
|
+
limit;
|
|
1049
|
+
constructor(limit) {
|
|
1050
|
+
this.limit = limit;
|
|
1051
|
+
}
|
|
1052
|
+
get remaining() {
|
|
1053
|
+
return this.limit - this.used;
|
|
1054
|
+
}
|
|
1055
|
+
consume(bytes) {
|
|
1056
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0) throw new Error("dataset byte count is invalid");
|
|
1057
|
+
if (this.used + bytes > this.limit) throw new ByteLimitError("dataset byte limit reached");
|
|
1058
|
+
this.used += bytes;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
class QueryRows {
|
|
1062
|
+
rows = [];
|
|
1063
|
+
bytes = 0;
|
|
1064
|
+
limit;
|
|
1065
|
+
encoder = new TextEncoder();
|
|
1066
|
+
constructor(limit) {
|
|
1067
|
+
this.limit = limit;
|
|
1068
|
+
}
|
|
1069
|
+
add(value) {
|
|
1070
|
+
if (this.rows.length >= this.limit) return "limit";
|
|
1071
|
+
const normalized = normalizeValue(value);
|
|
1072
|
+
const bytes = this.encoder.encode(JSON.stringify(normalized)).byteLength;
|
|
1073
|
+
if (bytes > MAX_TOOL_OUTPUT_BYTES && this.rows.length === 0) {
|
|
1074
|
+
throw new Error("dataset row exceeds the tool output budget; select fewer columns");
|
|
1075
|
+
}
|
|
1076
|
+
if (this.bytes + bytes > MAX_TOOL_OUTPUT_BYTES) return "output_limit";
|
|
1077
|
+
this.rows.push(normalized);
|
|
1078
|
+
this.bytes += bytes;
|
|
1079
|
+
return this.rows.length >= this.limit ? "limit" : "accepted";
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
class RangeCache {
|
|
1083
|
+
entries = /* @__PURE__ */ new Map();
|
|
1084
|
+
maxBytes;
|
|
1085
|
+
maxEntryBytes;
|
|
1086
|
+
bytes = 0;
|
|
1087
|
+
constructor(maxBytes, maxEntryBytes) {
|
|
1088
|
+
this.maxBytes = maxBytes;
|
|
1089
|
+
this.maxEntryBytes = maxEntryBytes;
|
|
1090
|
+
}
|
|
1091
|
+
async read(key, length, load) {
|
|
1092
|
+
const cached = this.entries.get(key);
|
|
1093
|
+
if (cached) {
|
|
1094
|
+
this.entries.delete(key);
|
|
1095
|
+
this.entries.set(key, cached);
|
|
1096
|
+
return cached;
|
|
1097
|
+
}
|
|
1098
|
+
const buffer = await load();
|
|
1099
|
+
if (length <= this.maxEntryBytes) this.insert(key, buffer);
|
|
1100
|
+
return buffer;
|
|
1101
|
+
}
|
|
1102
|
+
insert(key, buffer) {
|
|
1103
|
+
const existing = this.entries.get(key);
|
|
1104
|
+
if (existing) {
|
|
1105
|
+
this.entries.delete(key);
|
|
1106
|
+
this.bytes -= existing.byteLength;
|
|
1107
|
+
}
|
|
1108
|
+
while (this.bytes + buffer.byteLength > this.maxBytes && this.entries.size) {
|
|
1109
|
+
const oldest = this.entries.keys().next().value;
|
|
1110
|
+
const removed = this.entries.get(oldest);
|
|
1111
|
+
this.entries.delete(oldest);
|
|
1112
|
+
this.bytes -= removed.byteLength;
|
|
1113
|
+
}
|
|
1114
|
+
if (buffer.byteLength > this.maxBytes) return;
|
|
1115
|
+
this.entries.set(key, buffer);
|
|
1116
|
+
this.bytes += buffer.byteLength;
|
|
1117
|
+
}
|
|
1118
|
+
deleteDataset(datasetId) {
|
|
1119
|
+
for (const [key, buffer] of this.entries) {
|
|
1120
|
+
if (!key.startsWith(`${datasetId}\n`) && !key.startsWith(`${datasetId}:`)) continue;
|
|
1121
|
+
this.entries.delete(key);
|
|
1122
|
+
this.bytes -= buffer.byteLength;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
clear() {
|
|
1126
|
+
this.entries.clear();
|
|
1127
|
+
this.bytes = 0;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
class QueryResultCache {
|
|
1131
|
+
entries = /* @__PURE__ */ new Map();
|
|
1132
|
+
maxBytes;
|
|
1133
|
+
maxEntries;
|
|
1134
|
+
bytes = 0;
|
|
1135
|
+
constructor(maxBytes, maxEntries) {
|
|
1136
|
+
this.maxBytes = maxBytes;
|
|
1137
|
+
this.maxEntries = maxEntries;
|
|
1138
|
+
}
|
|
1139
|
+
get(key) {
|
|
1140
|
+
const entry = this.entries.get(key);
|
|
1141
|
+
if (!entry) return void 0;
|
|
1142
|
+
this.entries.delete(key);
|
|
1143
|
+
this.entries.set(key, entry);
|
|
1144
|
+
return structuredClone(entry.result);
|
|
1145
|
+
}
|
|
1146
|
+
set(key, result) {
|
|
1147
|
+
const copy = structuredClone(result);
|
|
1148
|
+
const bytes = new TextEncoder().encode(JSON.stringify(copy)).byteLength;
|
|
1149
|
+
if (bytes > this.maxBytes) return;
|
|
1150
|
+
const existing = this.entries.get(key);
|
|
1151
|
+
if (existing) {
|
|
1152
|
+
this.entries.delete(key);
|
|
1153
|
+
this.bytes -= existing.bytes;
|
|
1154
|
+
}
|
|
1155
|
+
while ((this.bytes + bytes > this.maxBytes || this.entries.size >= this.maxEntries) && this.entries.size) {
|
|
1156
|
+
const oldest = this.entries.keys().next().value;
|
|
1157
|
+
const removed = this.entries.get(oldest);
|
|
1158
|
+
this.entries.delete(oldest);
|
|
1159
|
+
this.bytes -= removed.bytes;
|
|
1160
|
+
}
|
|
1161
|
+
this.entries.set(key, { bytes, result: copy });
|
|
1162
|
+
this.bytes += bytes;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
export {
|
|
1166
|
+
createDatasetTool
|
|
1167
|
+
};
|