functionalscript 0.35.2 → 0.37.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/fs/asn.1/module.f.js +1 -1
- package/fs/asserts/proof.f.js +1 -1
- package/fs/base_n/module.f.js +3 -3
- package/fs/base_n/proof.f.js +3 -3
- package/fs/{base128 → basen/base128}/module.f.d.ts +1 -1
- package/fs/{base128 → basen/base128}/module.f.js +1 -1
- package/fs/{base128 → basen/base128}/proof.f.js +3 -3
- package/fs/{base64 → basen/base64}/module.f.d.ts +2 -2
- package/fs/basen/base64/module.f.js +71 -0
- package/fs/{base64 → basen/base64}/proof.f.d.ts +2 -0
- package/fs/{base64 → basen/base64}/proof.f.js +17 -2
- package/fs/{cbase32 → basen/cbase32}/module.f.d.ts +2 -2
- package/fs/{cbase32 → basen/cbase32}/module.f.js +2 -2
- package/fs/{cbase32 → basen/cbase32}/proof.f.js +1 -1
- package/fs/bnf/data/module.f.js +4 -4
- package/fs/bnf/data/proof.f.js +6 -6
- package/fs/bnf/descent/module.f.js +6 -6
- package/fs/bnf/descent/proof.f.js +6 -6
- package/fs/bnf/ll1/module.f.js +9 -9
- package/fs/bnf/ll1/proof.f.js +6 -6
- package/fs/bnf/module.f.js +4 -4
- package/fs/bnf/proof.f.js +2 -2
- package/fs/bnf/testlib.f.js +1 -1
- package/fs/cas/cli/module.f.js +7 -7
- package/fs/cas/cli/proof.f.js +8 -8
- package/fs/cas/mcp/module.f.d.ts +8 -4
- package/fs/cas/mcp/module.f.js +124 -72
- package/fs/cas/mcp/proof.f.d.ts +3 -8
- package/fs/cas/mcp/proof.f.js +122 -186
- package/fs/cas/module.f.js +12 -19
- package/fs/cas/proof.f.d.ts +4 -2
- package/fs/cas/proof.f.js +10 -10
- package/fs/ci/bun/module.f.js +2 -2
- package/fs/ci/common/module.f.d.ts +2 -2
- package/fs/ci/common/module.f.js +4 -4
- package/fs/ci/config/module.f.d.ts +20 -21
- package/fs/ci/config/module.f.js +8 -10
- package/fs/ci/deno/module.f.js +8 -4
- package/fs/ci/module.f.js +9 -9
- package/fs/ci/node/module.f.d.ts +1 -1
- package/fs/ci/node/module.f.js +3 -5
- package/fs/ci/playwright/module.f.js +3 -3
- package/fs/ci/proof.f.js +14 -14
- package/fs/ci/rust/module.f.js +2 -2
- package/fs/cli/module.f.js +3 -3
- package/fs/cli/proof.f.js +3 -3
- package/fs/{types → common}/monoid/module.f.d.ts +1 -1
- package/fs/crypto/hmac/module.f.js +2 -2
- package/fs/crypto/hmac/proof.f.js +5 -5
- package/fs/crypto/pow/module.f.js +3 -3
- package/fs/crypto/pow/proof.f.js +4 -4
- package/fs/crypto/secp/module.f.js +2 -2
- package/fs/crypto/secp/proof.f.js +3 -3
- package/fs/crypto/sha2/module.f.js +2 -2
- package/fs/crypto/sha2/proof.f.js +6 -6
- package/fs/crypto/sign/module.f.js +4 -4
- package/fs/crypto/vdf/module.f.js +1 -1
- package/fs/crypto/vdf/proof.f.js +1 -1
- package/fs/dev/module.f.js +6 -6
- package/fs/dev/package_json/module.f.js +6 -6
- package/fs/dev/package_json/proof.f.js +3 -3
- package/fs/dev/proof.f.js +2 -2
- package/fs/djs/ast/module.f.js +2 -2
- package/fs/djs/ast/proof.f.js +4 -4
- package/fs/djs/examples/m.f.d.ts +1 -1
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +5 -5
- package/fs/djs/parser/module.f.js +4 -4
- package/fs/djs/parser/proof.f.js +19 -7
- package/fs/djs/proof.f.js +3 -3
- package/fs/djs/serializer/module.f.js +6 -6
- package/fs/djs/serializer/proof.f.js +4 -4
- package/fs/djs/tokenizer/module.f.js +3 -3
- package/fs/djs/tokenizer/proof.f.js +5 -5
- package/fs/djs/tokenizer-new/proof.f.js +6 -6
- package/fs/djs/transpiler/module.f.d.ts +5 -0
- package/fs/djs/transpiler/module.f.js +21 -11
- package/fs/djs/transpiler/proof.f.js +5 -5
- package/fs/effects/memory/module.f.js +4 -4
- package/fs/effects/memory/proof.f.js +3 -3
- package/fs/effects/module.f.d.ts +15 -0
- package/fs/effects/module.f.js +15 -1
- package/fs/effects/node/memory/module.js +2 -2
- package/fs/effects/node/memory/proof.js +3 -3
- package/fs/effects/node/module.f.js +10 -20
- package/fs/effects/node/module.js +13 -11
- package/fs/effects/node/proof.f.d.ts +3 -0
- package/fs/effects/node/proof.f.js +19 -1
- package/fs/effects/node/virtual/module.f.js +7 -8
- package/fs/effects/node/virtual/proof.f.d.ts +1 -0
- package/fs/effects/node/virtual/proof.f.js +11 -4
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +22 -1
- package/fs/emergent_testing/all.test.js +2 -2
- package/fs/emergent_testing/example.f.d.ts +1 -1
- package/fs/emergent_testing/module.f.js +6 -6
- package/fs/emergent_testing/proof.f.d.ts +19 -17
- package/fs/emergent_testing/proof.f.js +7 -7
- package/fs/emergent_testing/scenarios/all.js +1 -1
- package/fs/fsc/bnf.f.js +2 -2
- package/fs/fsc/json.f.js +1 -1
- package/fs/fsc/module.f.d.ts +1 -0
- package/fs/fsc/module.f.js +11 -7
- package/fs/fsc/proof.f.js +4 -4
- package/fs/fsm/module.f.js +9 -9
- package/fs/fsm/proof.f.js +7 -7
- package/fs/js/tokenizer/module.f.d.ts +1 -1
- package/fs/js/tokenizer/module.f.js +14 -8
- package/fs/js/tokenizer/proof.f.js +64 -5
- package/fs/mcp/module.f.d.ts +13 -13
- package/fs/mcp/module.f.js +8 -8
- package/fs/mcp/proof.f.js +5 -5
- package/fs/mcp/stdio/module.f.d.ts +2 -2
- package/fs/mcp/stdio/module.f.js +10 -10
- package/fs/mcp/stdio/proof.f.js +10 -10
- package/fs/{html → media/html}/module.f.d.ts +3 -3
- package/fs/{html → media/html}/module.f.js +9 -9
- package/fs/media/json/module.f.d.ts +50 -0
- package/fs/{json → media/json}/module.f.js +7 -7
- package/fs/{json → media/json}/parser/module.f.d.ts +2 -2
- package/fs/{json → media/json}/parser/module.f.js +7 -7
- package/fs/{json → media/json}/parser/proof.f.js +24 -6
- package/fs/{json → media/json}/proof.f.js +4 -4
- package/fs/media/json/rpc/module.f.d.ts +114 -0
- package/fs/{json → media/json}/rpc/module.f.js +3 -3
- package/fs/{json → media/json}/rpc/proof.f.js +4 -4
- package/fs/media/json/schema/module.f.d.ts +78 -0
- package/fs/{json → media/json}/schema/module.f.js +2 -2
- package/fs/{json → media/json}/schema/proof.f.js +3 -3
- package/fs/{json → media/json}/serializer/module.f.d.ts +1 -1
- package/fs/{json → media/json}/serializer/module.f.js +2 -2
- package/fs/{json → media/json}/serializer/proof.f.js +2 -2
- package/fs/{json → media/json}/tokenizer/module.f.d.ts +2 -2
- package/fs/{json → media/json}/tokenizer/module.f.js +4 -4
- package/fs/{json → media/json}/tokenizer/proof.f.js +5 -5
- package/fs/media/module.f.d.ts +37 -0
- package/fs/media/module.f.js +24 -0
- package/fs/media/proof.f.d.ts +9 -0
- package/fs/media/proof.f.js +57 -0
- package/fs/media/revision/module.f.d.ts +51 -0
- package/fs/media/revision/module.f.js +110 -0
- package/fs/media/revision/proof.f.d.ts +29 -0
- package/fs/media/revision/proof.f.js +119 -0
- package/fs/{mime → media/type}/module.f.d.ts +6 -6
- package/fs/{mime → media/type}/module.f.js +6 -6
- package/fs/{mime → media/type}/proof.f.js +6 -6
- package/fs/module.f.d.ts +2 -0
- package/fs/{fjs/module.f.js → module.f.js} +8 -8
- package/fs/module.js +4 -0
- package/fs/path/module.f.d.ts +6 -0
- package/fs/path/module.f.js +8 -2
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/{fjs/proof.f.d.ts → proof.f.d.ts} +1 -0
- package/fs/{fjs/proof.f.js → proof.f.js} +10 -4
- package/fs/sul/id/module.f.js +9 -9
- package/fs/sul/id/proof.f.js +5 -5
- package/fs/sul/level/hash/module.f.js +3 -3
- package/fs/sul/level/hash/proof.f.js +3 -3
- package/fs/sul/level/literal/module.f.js +5 -5
- package/fs/sul/level/literal/proof.f.js +3 -3
- package/fs/sul/module.f.js +3 -3
- package/fs/sul/proof.f.js +3 -3
- package/fs/text/ascii/proof.f.js +3 -3
- package/fs/text/code_point/module.f.js +2 -2
- package/fs/text/code_point/proof.f.js +1 -1
- package/fs/text/module.f.js +5 -5
- package/fs/text/proof.f.js +4 -4
- package/fs/text/sgr/module.f.js +2 -2
- package/fs/text/sgr/proof.f.js +2 -2
- package/fs/text/utf16/module.f.js +5 -5
- package/fs/text/utf16/proof.f.js +4 -4
- package/fs/text/utf8/module.f.d.ts +6 -0
- package/fs/text/utf8/module.f.js +37 -19
- package/fs/text/utf8/proof.f.js +58 -5
- package/fs/types/array/module.f.js +1 -1
- package/fs/types/array/proof.f.js +3 -3
- package/fs/types/bigfloat/module.f.js +1 -1
- package/fs/types/bigfloat/proof.f.js +1 -1
- package/fs/types/bigint/module.f.js +2 -2
- package/fs/types/bigint/proof.f.js +3 -3
- package/fs/types/bit_vec/module.f.js +8 -8
- package/fs/types/bit_vec/proof.f.js +5 -5
- package/fs/types/btree/find/module.f.js +1 -1
- package/fs/types/btree/find/proof.f.d.ts +3 -1
- package/fs/types/btree/find/proof.f.js +7 -7
- package/fs/types/btree/module.f.js +2 -2
- package/fs/types/btree/proof.f.d.ts +11 -5
- package/fs/types/btree/proof.f.js +7 -7
- package/fs/types/btree/remove/module.f.js +4 -4
- package/fs/types/btree/remove/proof.f.d.ts +5 -2
- package/fs/types/btree/remove/proof.f.js +5 -5
- package/fs/types/btree/set/module.f.js +3 -3
- package/fs/types/btree/set/proof.f.js +4 -4
- package/fs/types/byte_set/module.f.js +2 -2
- package/fs/types/byte_set/proof.f.js +4 -4
- package/fs/types/function/compare/proof.f.js +1 -1
- package/fs/types/function/operator/proof.f.d.ts +21 -10
- package/fs/types/function/operator/proof.f.js +1 -1
- package/fs/types/function/proof.f.js +1 -1
- package/fs/types/list/module.f.js +2 -2
- package/fs/types/list/proof.f.d.ts +15 -7
- package/fs/types/list/proof.f.js +4 -4
- package/fs/types/map/proof.f.js +2 -2
- package/fs/types/nibble_set/proof.f.js +2 -2
- package/fs/types/nullable/module.f.js +2 -2
- package/fs/types/nullable/proof.f.js +1 -1
- package/fs/types/number/module.f.js +5 -5
- package/fs/types/number/proof.f.js +1 -1
- package/fs/types/object/module.f.js +4 -4
- package/fs/types/object/proof.f.js +1 -1
- package/fs/types/ordered_map/module.f.js +6 -6
- package/fs/types/ordered_map/proof.f.js +3 -3
- package/fs/types/patricia_trie/proof.f.d.ts +11 -5
- package/fs/types/patricia_trie/proof.f.js +2 -2
- package/fs/types/prime_field/module.f.js +2 -2
- package/fs/types/prime_field/proof.f.js +2 -2
- package/fs/types/range/proof.f.js +1 -1
- package/fs/types/range_map/module.f.js +4 -4
- package/fs/types/range_map/proof.f.js +7 -7
- package/fs/types/result/module.f.d.ts +4 -0
- package/fs/types/result/module.f.js +4 -0
- package/fs/types/result/module.js +1 -1
- package/fs/types/result/proof.f.d.ts +9 -3
- package/fs/types/result/proof.f.js +12 -2
- package/fs/types/rtti/common/module.f.js +4 -4
- package/fs/types/rtti/module.f.d.ts +2 -2
- package/fs/types/rtti/module.f.js +1 -1
- package/fs/types/rtti/parse/module.f.js +5 -5
- package/fs/types/rtti/parse/proof.f.js +2 -2
- package/fs/types/rtti/ts/module.f.js +1 -1
- package/fs/types/rtti/ts/proof.f.js +2 -2
- package/fs/types/rtti/validate/module.f.js +4 -4
- package/fs/types/rtti/validate/proof.f.js +2 -2
- package/fs/types/sorted_list/module.f.js +3 -3
- package/fs/types/sorted_list/proof.f.js +6 -6
- package/fs/types/sorted_set/module.f.js +1 -1
- package/fs/types/sorted_set/proof.f.js +6 -6
- package/fs/types/string/module.f.js +4 -4
- package/fs/types/string/proof.f.js +2 -2
- package/fs/types/string_set/module.f.js +6 -6
- package/fs/types/string_set/proof.f.js +1 -1
- package/fs/types/ts/proof.f.d.ts +18 -18
- package/fs/types/ts/proof.f.js +1 -1
- package/fs/types/uint8array/module.f.js +4 -4
- package/fs/types/uint8array/proof.f.js +4 -4
- package/fs/website/module.f.js +3 -3
- package/fs/website/proof.f.js +3 -3
- package/package.json +8 -8
- package/fs/base64/module.f.js +0 -60
- package/fs/fjs/module.f.d.ts +0 -2
- package/fs/fjs/module.js +0 -4
- package/fs/json/module.f.d.ts +0 -50
- package/fs/json/rpc/module.f.d.ts +0 -114
- package/fs/json/schema/module.f.d.ts +0 -77
- /package/fs/{base128 → basen/base128}/proof.f.d.ts +0 -0
- /package/fs/{cbase32 → basen/cbase32}/proof.f.d.ts +0 -0
- /package/fs/{types → common}/monoid/module.f.js +0 -0
- /package/fs/{types → common}/monoid/proof.f.d.ts +0 -0
- /package/fs/{types → common}/monoid/proof.f.js +0 -0
- /package/fs/{html → media/html}/proof.f.d.ts +0 -0
- /package/fs/{html → media/html}/proof.f.js +0 -0
- /package/fs/{json → media/json}/parser/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/rpc/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/schema/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/serializer/proof.f.d.ts +0 -0
- /package/fs/{json → media/json}/tokenizer/proof.f.d.ts +0 -0
- /package/fs/{mime → media/type}/proof.f.d.ts +0 -0
- /package/fs/{fjs/module.d.ts → module.d.ts} +0 -0
package/fs/cas/mcp/module.f.js
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
* | Tool | args | action | result |
|
|
13
13
|
* |------------|-------------------------------|------------------|-------------------------------------|
|
|
14
|
-
* | `cas_add` | `{ content, type? }` | write
|
|
15
|
-
* | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,
|
|
14
|
+
* | `cas_add` | `{ content, type? }` | `c.write(...)` | hash (cBase32) |
|
|
15
|
+
* | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,mimeType,type[,uri][,text\|blob]}` |
|
|
16
16
|
* | `cas_list` | `{}` | `c.list()` | hashes, one per line |
|
|
17
17
|
*
|
|
18
18
|
* ## `cas_add` input encoding
|
|
@@ -23,43 +23,64 @@
|
|
|
23
23
|
* without any encoding step.
|
|
24
24
|
* - `'base64'`: `content` is RFC 4648 base64, decoded to bytes before storage.
|
|
25
25
|
* Use this for pre-encoded binary payloads.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
*
|
|
27
|
+
* Inline content (either encoding) is capped at 128 KiB (`maxLength`). There is
|
|
28
|
+
* no MCP route for larger content — the server never opens a local path named by
|
|
29
|
+
* the client (see the invariant in `fs/cas/mcp/README.md`); large files go
|
|
30
|
+
* through the `cas` CLI (`cas add <path>`) instead, run directly by the user.
|
|
29
31
|
*
|
|
30
32
|
* ## `cas_get` output
|
|
31
33
|
*
|
|
32
|
-
* Always returns a JSON object `{ length,
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Always returns a JSON object `{ length, mimeType, type[, uri][, text | blob] }`.
|
|
35
|
+
* These field names mirror the MCP resource-contents shape (`resources/read`
|
|
36
|
+
* results), so the tool view and the future resource view of a blob use the same
|
|
37
|
+
* vocabulary: `mimeType` (not `mime_type`), `uri` (not `url`) — the resource URI
|
|
38
|
+
* under which the same blob will be readable via `resources/read` — and `text` /
|
|
39
|
+
* `blob` (not a `type`-tagged `content`) for inline payloads. `type` is always
|
|
40
|
+
* present (`'text'` or `'base64'`) as the discriminator for which of `text` /
|
|
41
|
+
* `blob` a later `content: true` fetch would populate — MCP allows extra fields,
|
|
42
|
+
* so `type` and `length` simply stay alongside the required resource-contents
|
|
43
|
+
* fields. A single classifier — the `fs/media/type` detector state machine (length ×
|
|
44
|
+
* magic-byte eliminator × UTF-8 DFA) — produces the same three-way verdict on
|
|
45
|
+
* both paths, so there is no second, divergent copy of the rules:
|
|
37
46
|
*
|
|
38
47
|
* 1. **Magic-byte hit** (PNG/JPEG/GIF/WebP/PDF/ZIP) → `type: 'base64'` with the
|
|
39
|
-
* detected `
|
|
40
|
-
* 2. **Whole-blob-valid UTF-8** → `type: 'text'`, `
|
|
41
|
-
* 3. **Fallback** → `type: 'base64'`, `
|
|
42
|
-
*
|
|
43
|
-
* **Metadata-only (`content: false`, the default) is size-independent
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* `
|
|
54
|
-
*
|
|
55
|
-
* is
|
|
56
|
-
*
|
|
57
|
-
*
|
|
48
|
+
* detected `mimeType`.
|
|
49
|
+
* 2. **Whole-blob-valid UTF-8** → `type: 'text'`, `mimeType: 'text/plain'`.
|
|
50
|
+
* 3. **Fallback** → `type: 'base64'`, `mimeType: 'application/octet-stream'`.
|
|
51
|
+
*
|
|
52
|
+
* **Metadata-only (`content: false`, the default) is size-independent for
|
|
53
|
+
* non-text and oversized blobs.** It folds the read stream through
|
|
54
|
+
* `fs/media/type` `detectStream` and never buffers the blob. Because a single
|
|
55
|
+
* `Vec` caps at `maxLength` bits (128 KiB), the old drain-into-one-`Vec`
|
|
56
|
+
* approach failed on any blob larger than one chunk even when only metadata
|
|
57
|
+
* was asked for; streaming detection returns correct
|
|
58
|
+
* `{ length, mimeType, type[, uri] }` regardless of size. A dialect (e.g.
|
|
59
|
+
* `vnd.fjs.revision`, see `fs/media/module.f.ts`) can only be recognized by
|
|
60
|
+
* parsing the whole blob as JSON, so when the streaming verdict is
|
|
61
|
+
* whole-blob-valid text within the same bounded inline cap as `content: true`
|
|
62
|
+
* (`maxLengthBytes`), one extra bounded read materializes the blob purely to
|
|
63
|
+
* refine `mimeType` through the dialect-aware detector; a larger or non-text
|
|
64
|
+
* blob is never a dialect match and is returned from the streaming verdict
|
|
65
|
+
* alone, unbuffered.
|
|
66
|
+
*
|
|
67
|
+
* **`content: true`** first derives `{ length, mimeType, type }` with the same
|
|
68
|
+
* size-independent `fs/media/type` `detectStream` machine (to decide whether the
|
|
69
|
+
* blob even fits inline), then materializes the bytes (via `collectRead`, bounded
|
|
70
|
+
* by `maxLength`) and re-derives the verdict from the dialect-aware detector
|
|
71
|
+
* (`fs/media/module.f.ts` `detect`) — now that the whole blob is in hand, a
|
|
72
|
+
* dialect match is reflected in the result — before encoding the inline payload
|
|
73
|
+
* by `type` — a `fs/text/utf8` `fromVec` string on `text` (for `type: 'text'`),
|
|
74
|
+
* base64 on `blob` (for `type: 'base64'`). A blob larger than `maxLength` (128
|
|
75
|
+
* KiB) cannot be buffered into one `Vec`, so it is rejected here with a
|
|
76
|
+
* descriptive *"too large"* error (carrying the byte size and `uri`) rather than
|
|
77
|
+
* being misreported as absent; it should be fetched via `uri` or inspected with
|
|
78
|
+
* metadata-only `cas_get`.
|
|
58
79
|
*
|
|
59
80
|
* ## Encoding split: hashes vs. content
|
|
60
81
|
*
|
|
61
82
|
* `Cas<O>` deals in `Vec` (bit vectors); MCP models only `textContent` today.
|
|
62
|
-
* **Hashes** travel as cBase32 (`fs/cbase32`) — the canonical CAS hash format,
|
|
83
|
+
* **Hashes** travel as cBase32 (`fs/basen/cbase32`) — the canonical CAS hash format,
|
|
63
84
|
* shared with the CLI and the on-disk store layout. **Content** encoding is
|
|
64
85
|
* determined at read time as described above.
|
|
65
86
|
*
|
|
@@ -78,29 +99,30 @@
|
|
|
78
99
|
*
|
|
79
100
|
* @module
|
|
80
101
|
*/
|
|
81
|
-
import { string, option, or, boolean } from
|
|
82
|
-
import { stringify } from
|
|
83
|
-
import { pure } from
|
|
84
|
-
import { create } from
|
|
85
|
-
import { cBase32ToVec, vecToCBase32 } from
|
|
86
|
-
import { decode as base64Decode, encode as base64Encode } from
|
|
87
|
-
import { tryUtf8 } from
|
|
88
|
-
import { detectStream } from
|
|
89
|
-
import {
|
|
90
|
-
import {
|
|
91
|
-
import {
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
94
|
-
import {
|
|
95
|
-
import {
|
|
96
|
-
import {
|
|
97
|
-
import {
|
|
98
|
-
import {
|
|
102
|
+
import { string, option, or, boolean } from '../../types/rtti/module.f.js';
|
|
103
|
+
import { stringify } from '../../media/json/module.f.js';
|
|
104
|
+
import { pure, decode } from '../../effects/module.f.js';
|
|
105
|
+
import { create } from '../../effects/memory/module.f.js';
|
|
106
|
+
import { cBase32ToVec, vecToCBase32 } from '../../basen/cbase32/module.f.js';
|
|
107
|
+
import { decode as base64Decode, encode as base64Encode } from '../../basen/base64/module.f.js';
|
|
108
|
+
import { tryUtf8 } from '../../text/module.f.js';
|
|
109
|
+
import { detectStream } from '../../media/type/module.f.js';
|
|
110
|
+
import { detect as detectDialect } from '../../media/module.f.js';
|
|
111
|
+
import { empty, length as bitVecLength, maxLength, maxLengthBytes, msb, vec } from '../../types/bit_vec/module.f.js';
|
|
112
|
+
import { ok, error } from '../../types/result/module.f.js';
|
|
113
|
+
import {} from '../../effects/node/module.f.js';
|
|
114
|
+
import { stdioTransport } from '../../mcp/stdio/module.f.js';
|
|
115
|
+
import { mcpStep, uninitializedState, toolEntry, fromRegistry, errorResult, } from '../../mcp/module.f.js';
|
|
116
|
+
import { fileCas } from '../module.f.js';
|
|
117
|
+
import { fromVec } from '../../text/utf8/module.f.js';
|
|
118
|
+
import { identity } from '../../types/function/module.f.js';
|
|
119
|
+
import { sha256 } from '../../crypto/sha2/module.f.js';
|
|
120
|
+
import { nonEmpty, empty as elEmpty } from '../../effects/list/module.f.js';
|
|
99
121
|
// ── Argument schemas (declared once, used for both inputSchema and validate) ─────
|
|
100
122
|
/** Arguments for `cas_add`: content to store, with optional encoding type. */
|
|
101
123
|
export const casAddArgs = {
|
|
102
124
|
content: string,
|
|
103
|
-
type: or('text', 'base64',
|
|
125
|
+
type: or('text', 'base64', undefined)
|
|
104
126
|
};
|
|
105
127
|
/** Arguments for `cas_get`: the cBase32 hash to look up; optionally request inline content. */
|
|
106
128
|
export const casGetArgs = {
|
|
@@ -114,7 +136,7 @@ export const casListArgs = {};
|
|
|
114
136
|
* Drains a CAS read stream into a single `Vec`. Used only on the `content: true`
|
|
115
137
|
* path, where the whole blob is needed for inline base64 / UTF-8 transfer; the
|
|
116
138
|
* chunk stream is concatenated and an error item is surfaced as the result.
|
|
117
|
-
* Metadata-only `cas_get` avoids this entirely via `fs/
|
|
139
|
+
* Metadata-only `cas_get` avoids this entirely via `fs/media/type` `detectStream`,
|
|
118
140
|
* which is why it is not bound by the `maxLength` ceiling below.
|
|
119
141
|
*/
|
|
120
142
|
const collectRead = (stream) => {
|
|
@@ -142,68 +164,78 @@ const toJson = stringify(identity);
|
|
|
142
164
|
/** Registry of all CAS tools. */
|
|
143
165
|
const casToolRegistry = (home) => {
|
|
144
166
|
const c = fileCas(sha256)(home);
|
|
145
|
-
const casUploadDir = `${home}/cas_upload`;
|
|
146
167
|
return [
|
|
147
|
-
toolEntry('cas_add', 'Store content and return its hash (cBase32). Pass type:"base64" for binary; omit or pass type:"text" for UTF-8 text (default). Inline content
|
|
148
|
-
// type:'url' — stream the file into cas, then delete the source on success
|
|
149
|
-
if (type === 'url') {
|
|
150
|
-
if (!content.startsWith(`${casUploadDir}/`) || content.includes('..')) {
|
|
151
|
-
return pure(errorResult(`cas_add type:url paths must be within ${casUploadDir}/ — got: ${content}`));
|
|
152
|
-
}
|
|
153
|
-
return casAddFile(c)(content).step(([t, v]) => t === 'error'
|
|
154
|
-
? pure(errorResult(`upload failed: ${v}`))
|
|
155
|
-
: rm(content).step(() => pure(okResult(vecToCBase32(v)))));
|
|
156
|
-
}
|
|
168
|
+
toolEntry('cas_add', 'Store content and return its hash (cBase32). Pass type:"base64" for binary; omit or pass type:"text" for UTF-8 text (default). Inline content is capped at 128 KiB (131072 bytes) — larger content is rejected. For larger content, store the file with the `cas` CLI instead: run `npx functionalscript cas add <path>` yourself if you have shell access, or give the user that exact command to run — it prints the resulting hash on stdout.', casAddArgs, ({ type, content }) => {
|
|
157
169
|
// type:'text' or 'base64' — resolve content to Vec, store via c.write()
|
|
158
170
|
let x = type === 'base64'
|
|
159
171
|
? base64Decode(content)
|
|
160
172
|
: tryUtf8(content);
|
|
161
173
|
return x === null
|
|
162
|
-
? pure(errorResult('too large or malformed —
|
|
174
|
+
? pure(errorResult('too large or malformed — for large content, run `npx functionalscript cas add <path>` (or have the user run it) instead'))
|
|
163
175
|
// The resolved content fits in one chunk; feed it as a single-item stream.
|
|
164
176
|
: c.write(nonEmpty(ok(x), elEmpty())).step(([tag, hash]) => pure(tag === 'error'
|
|
165
177
|
? errorResult('write')
|
|
166
178
|
: okResult(vecToCBase32(hash))));
|
|
167
179
|
}),
|
|
168
|
-
toolEntry('cas_get', 'Inspect a blob by hash. Always returns JSON {length,
|
|
180
|
+
toolEntry('cas_get', 'Inspect a blob by hash. Always returns JSON {length,mimeType,type[,uri]} where type is "text" or "base64". Pass content:true to also include the inline payload as text (type:"text") or blob (type:"base64"), but content is capped at 128 KiB (131072 bytes) — a larger blob is rejected with an error. To download a blob, prefer the uri field returned in the result instead of requesting inline content.', casGetArgs, r => {
|
|
169
181
|
const key = cBase32ToVec(r.hash);
|
|
170
182
|
if (key === null) {
|
|
171
183
|
return pure(errorResult(`invalid cBase32 hash: ${r.hash}`));
|
|
172
184
|
}
|
|
173
|
-
const
|
|
185
|
+
const uri = c.url(key);
|
|
174
186
|
return detectStream(c.read(key)).step(([tag, detected]) => {
|
|
175
187
|
if (tag === 'error') {
|
|
176
188
|
return pure(errorResult(`no such hash: ${r.hash}`));
|
|
177
189
|
}
|
|
178
|
-
const { length, mime_type, type } = detected;
|
|
179
|
-
const meta = { length: Number(length),
|
|
190
|
+
const { length, mime_type: mimeType, type } = detected;
|
|
191
|
+
const meta = { length: Number(length), mimeType, type, uri };
|
|
180
192
|
if (r.content !== true) {
|
|
181
|
-
//
|
|
182
|
-
|
|
193
|
+
// A dialect match can only be decided from the whole parsed blob;
|
|
194
|
+
// only attempt the extra bounded read when it stands a chance
|
|
195
|
+
// (whole-blob text within the same cap `content: true` allows).
|
|
196
|
+
if (type !== 'text' || length > maxLengthBytes) {
|
|
197
|
+
return pure(okResult(toJson(meta)));
|
|
198
|
+
}
|
|
199
|
+
return collectRead(c.read(key)).step(([collectTag, value]) => {
|
|
200
|
+
// Already known to fit from the streaming pass above, so an
|
|
201
|
+
// error here means the hash vanished between reads; fall back
|
|
202
|
+
// to the streaming verdict rather than fail the whole request.
|
|
203
|
+
if (collectTag === 'error') {
|
|
204
|
+
return pure(okResult(toJson(meta)));
|
|
205
|
+
}
|
|
206
|
+
const refined = detectDialect(value);
|
|
207
|
+
return pure(okResult(toJson({ ...meta, mimeType: refined.mime_type })));
|
|
208
|
+
});
|
|
183
209
|
}
|
|
184
210
|
// A single `Vec` caps at `maxLength` bits (`maxLengthBytes` bytes), so
|
|
185
211
|
// a larger blob cannot be buffered for inline transfer. Report the
|
|
186
212
|
// size and point at the size-independent alternatives instead of
|
|
187
213
|
// misreporting an existing blob as `no such hash`.
|
|
188
214
|
if (length > maxLengthBytes) {
|
|
189
|
-
return pure(errorResult(`blob too large to fetch inline (${length} bytes, limit ${maxLengthBytes} bytes); use the
|
|
215
|
+
return pure(errorResult(`blob too large to fetch inline (${length} bytes, limit ${maxLengthBytes} bytes); use the uri field (${uri}) or omit content for metadata`));
|
|
190
216
|
}
|
|
191
217
|
return collectRead(c.read(key)).step(([collectTag, value]) => {
|
|
192
218
|
if (collectTag === 'error') {
|
|
193
219
|
return pure(errorResult(`no such hash: ${r.hash}`));
|
|
194
220
|
}
|
|
195
|
-
|
|
221
|
+
// Re-derive the verdict from the now-materialized blob so a
|
|
222
|
+
// dialect match — only decidable with the whole parsed JSON in
|
|
223
|
+
// hand — is reflected in the inline result too, not just the
|
|
224
|
+
// streaming guess.
|
|
225
|
+
const refined = detectDialect(value);
|
|
226
|
+
const refinedMeta = { length: Number(refined.length), mimeType: refined.mime_type, type: refined.type, uri };
|
|
227
|
+
if (refined.type === 'text') {
|
|
196
228
|
// `type: 'text'` means the detector validated `value` as UTF-8,
|
|
197
229
|
// so `fromVec` is non-null here; guard defensively regardless.
|
|
198
230
|
const str = fromVec(value);
|
|
199
231
|
return pure(str === null
|
|
200
232
|
? errorResult(`content is not byte-aligned: ${r.hash}`)
|
|
201
|
-
: okResult(toJson({ ...
|
|
233
|
+
: okResult(toJson({ ...refinedMeta, text: str })));
|
|
202
234
|
}
|
|
203
235
|
const blob = base64Encode(value);
|
|
204
236
|
return pure(blob === null
|
|
205
237
|
? errorResult(`content is not byte-aligned: ${r.hash}`)
|
|
206
|
-
: okResult(toJson({ ...
|
|
238
|
+
: okResult(toJson({ ...refinedMeta, blob })));
|
|
207
239
|
});
|
|
208
240
|
});
|
|
209
241
|
}),
|
|
@@ -235,3 +267,23 @@ export const casConfig = {
|
|
|
235
267
|
* until stdin EOF.
|
|
236
268
|
*/
|
|
237
269
|
export const casMcpServer = (home) => create(uninitializedState).step(key => stdioTransport(mcpStep(casConfig)(casMcpHandlers(home))(key)));
|
|
270
|
+
// ── Tests ────────────────────────────────────────────────────────────────────
|
|
271
|
+
export const proof = {
|
|
272
|
+
// casMcpServer is never called in integration tests because it drives a
|
|
273
|
+
// real stdio server; call it here to cover its Effect-building body.
|
|
274
|
+
casMcpServer: () => { casMcpServer('/'); },
|
|
275
|
+
// The overflow guard in collectRead (lines 125-126) is only reached when
|
|
276
|
+
// the running total of two stream chunks would exceed maxLength. Feed a
|
|
277
|
+
// pure stream whose second chunk pushes it just over the limit so the
|
|
278
|
+
// error branch executes without any real I/O.
|
|
279
|
+
collectReadOverflow: () => {
|
|
280
|
+
const half = maxLength / 2n;
|
|
281
|
+
const v1 = vec(half)(0n);
|
|
282
|
+
const v2 = vec(half + 1n)(0n);
|
|
283
|
+
const stream = nonEmpty(ok(v1), nonEmpty(ok(v2), elEmpty()));
|
|
284
|
+
const d = decode(collectRead(stream));
|
|
285
|
+
if (!d.done || d.result[0] !== 'error') {
|
|
286
|
+
throw 'expected overflow error';
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
};
|
package/fs/cas/mcp/proof.f.d.ts
CHANGED
|
@@ -16,13 +16,15 @@ export declare const proof: {
|
|
|
16
16
|
addGetRoundTrips: () => void;
|
|
17
17
|
getUntypedReturnsText: () => void;
|
|
18
18
|
getTypedReturnsBinaryJson: () => void;
|
|
19
|
+
getMetaRecognizesRevisionDialect: () => void;
|
|
20
|
+
getContentRecognizesRevisionDialect: () => void;
|
|
19
21
|
listEnumeratesStoredHashes: () => void;
|
|
20
22
|
addInvalidContentIsError: () => void;
|
|
21
23
|
addBadLengthContentIsError: () => void;
|
|
22
24
|
addBase64OverLimitIsError: () => void;
|
|
23
25
|
addTextOverLimitIsError: () => void;
|
|
24
26
|
addTextAtLimitSucceeds: () => void;
|
|
25
|
-
|
|
27
|
+
addBase64AtLimitSucceeds: () => void;
|
|
26
28
|
getUnterminatedHashIsError: () => void;
|
|
27
29
|
addMissingContentIsError: () => void;
|
|
28
30
|
getMissingHashArgumentIsError: () => void;
|
|
@@ -30,18 +32,11 @@ export declare const proof: {
|
|
|
30
32
|
getMissingHashIsError: () => void;
|
|
31
33
|
unknownToolIsError: () => void;
|
|
32
34
|
toolErrorIsNotJsonRpcError: () => void;
|
|
33
|
-
addUrlStoresFileAndReturnsHash: () => void;
|
|
34
|
-
addUrlRoundTrips: () => void;
|
|
35
|
-
addUrlMissingFileIsError: () => void;
|
|
36
35
|
getMetaReturnsLengthAndMimeType: () => void;
|
|
37
36
|
getMetaBinaryBlob: () => void;
|
|
38
37
|
getMetaOctetStreamForUnknownBinary: () => void;
|
|
39
38
|
getMetaOctetStreamForNulBlob: () => void;
|
|
40
39
|
getMetaMissingHashIsError: () => void;
|
|
41
40
|
getMetaInvalidHashIsError: () => void;
|
|
42
|
-
addUrlFromSubdirectorySucceeds: () => void;
|
|
43
|
-
addUrlFromApprovedDirectorySucceeds: () => void;
|
|
44
|
-
addUrlFromRandomDirectoryIsRejected: () => void;
|
|
45
|
-
addUrlWithPathTraversalIsRejected: () => void;
|
|
46
41
|
getOctetStreamWithContentIncludesBase64: () => void;
|
|
47
42
|
};
|