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.
Files changed (270) hide show
  1. package/fs/asn.1/module.f.js +1 -1
  2. package/fs/asserts/proof.f.js +1 -1
  3. package/fs/base_n/module.f.js +3 -3
  4. package/fs/base_n/proof.f.js +3 -3
  5. package/fs/{base128 → basen/base128}/module.f.d.ts +1 -1
  6. package/fs/{base128 → basen/base128}/module.f.js +1 -1
  7. package/fs/{base128 → basen/base128}/proof.f.js +3 -3
  8. package/fs/{base64 → basen/base64}/module.f.d.ts +2 -2
  9. package/fs/basen/base64/module.f.js +71 -0
  10. package/fs/{base64 → basen/base64}/proof.f.d.ts +2 -0
  11. package/fs/{base64 → basen/base64}/proof.f.js +17 -2
  12. package/fs/{cbase32 → basen/cbase32}/module.f.d.ts +2 -2
  13. package/fs/{cbase32 → basen/cbase32}/module.f.js +2 -2
  14. package/fs/{cbase32 → basen/cbase32}/proof.f.js +1 -1
  15. package/fs/bnf/data/module.f.js +4 -4
  16. package/fs/bnf/data/proof.f.js +6 -6
  17. package/fs/bnf/descent/module.f.js +6 -6
  18. package/fs/bnf/descent/proof.f.js +6 -6
  19. package/fs/bnf/ll1/module.f.js +9 -9
  20. package/fs/bnf/ll1/proof.f.js +6 -6
  21. package/fs/bnf/module.f.js +4 -4
  22. package/fs/bnf/proof.f.js +2 -2
  23. package/fs/bnf/testlib.f.js +1 -1
  24. package/fs/cas/cli/module.f.js +7 -7
  25. package/fs/cas/cli/proof.f.js +8 -8
  26. package/fs/cas/mcp/module.f.d.ts +8 -4
  27. package/fs/cas/mcp/module.f.js +124 -72
  28. package/fs/cas/mcp/proof.f.d.ts +3 -8
  29. package/fs/cas/mcp/proof.f.js +122 -186
  30. package/fs/cas/module.f.js +12 -19
  31. package/fs/cas/proof.f.d.ts +4 -2
  32. package/fs/cas/proof.f.js +10 -10
  33. package/fs/ci/bun/module.f.js +2 -2
  34. package/fs/ci/common/module.f.d.ts +2 -2
  35. package/fs/ci/common/module.f.js +4 -4
  36. package/fs/ci/config/module.f.d.ts +20 -21
  37. package/fs/ci/config/module.f.js +8 -10
  38. package/fs/ci/deno/module.f.js +8 -4
  39. package/fs/ci/module.f.js +9 -9
  40. package/fs/ci/node/module.f.d.ts +1 -1
  41. package/fs/ci/node/module.f.js +3 -5
  42. package/fs/ci/playwright/module.f.js +3 -3
  43. package/fs/ci/proof.f.js +14 -14
  44. package/fs/ci/rust/module.f.js +2 -2
  45. package/fs/cli/module.f.js +3 -3
  46. package/fs/cli/proof.f.js +3 -3
  47. package/fs/{types → common}/monoid/module.f.d.ts +1 -1
  48. package/fs/crypto/hmac/module.f.js +2 -2
  49. package/fs/crypto/hmac/proof.f.js +5 -5
  50. package/fs/crypto/pow/module.f.js +3 -3
  51. package/fs/crypto/pow/proof.f.js +4 -4
  52. package/fs/crypto/secp/module.f.js +2 -2
  53. package/fs/crypto/secp/proof.f.js +3 -3
  54. package/fs/crypto/sha2/module.f.js +2 -2
  55. package/fs/crypto/sha2/proof.f.js +6 -6
  56. package/fs/crypto/sign/module.f.js +4 -4
  57. package/fs/crypto/vdf/module.f.js +1 -1
  58. package/fs/crypto/vdf/proof.f.js +1 -1
  59. package/fs/dev/module.f.js +6 -6
  60. package/fs/dev/package_json/module.f.js +6 -6
  61. package/fs/dev/package_json/proof.f.js +3 -3
  62. package/fs/dev/proof.f.js +2 -2
  63. package/fs/djs/ast/module.f.js +2 -2
  64. package/fs/djs/ast/proof.f.js +4 -4
  65. package/fs/djs/examples/m.f.d.ts +1 -1
  66. package/fs/djs/module.f.d.ts +1 -1
  67. package/fs/djs/module.f.js +5 -5
  68. package/fs/djs/parser/module.f.js +4 -4
  69. package/fs/djs/parser/proof.f.js +19 -7
  70. package/fs/djs/proof.f.js +3 -3
  71. package/fs/djs/serializer/module.f.js +6 -6
  72. package/fs/djs/serializer/proof.f.js +4 -4
  73. package/fs/djs/tokenizer/module.f.js +3 -3
  74. package/fs/djs/tokenizer/proof.f.js +5 -5
  75. package/fs/djs/tokenizer-new/proof.f.js +6 -6
  76. package/fs/djs/transpiler/module.f.d.ts +5 -0
  77. package/fs/djs/transpiler/module.f.js +21 -11
  78. package/fs/djs/transpiler/proof.f.js +5 -5
  79. package/fs/effects/memory/module.f.js +4 -4
  80. package/fs/effects/memory/proof.f.js +3 -3
  81. package/fs/effects/module.f.d.ts +15 -0
  82. package/fs/effects/module.f.js +15 -1
  83. package/fs/effects/node/memory/module.js +2 -2
  84. package/fs/effects/node/memory/proof.js +3 -3
  85. package/fs/effects/node/module.f.js +10 -20
  86. package/fs/effects/node/module.js +13 -11
  87. package/fs/effects/node/proof.f.d.ts +3 -0
  88. package/fs/effects/node/proof.f.js +19 -1
  89. package/fs/effects/node/virtual/module.f.js +7 -8
  90. package/fs/effects/node/virtual/proof.f.d.ts +1 -0
  91. package/fs/effects/node/virtual/proof.f.js +11 -4
  92. package/fs/effects/proof.f.d.ts +5 -0
  93. package/fs/effects/proof.f.js +22 -1
  94. package/fs/emergent_testing/all.test.js +2 -2
  95. package/fs/emergent_testing/example.f.d.ts +1 -1
  96. package/fs/emergent_testing/module.f.js +6 -6
  97. package/fs/emergent_testing/proof.f.d.ts +19 -17
  98. package/fs/emergent_testing/proof.f.js +7 -7
  99. package/fs/emergent_testing/scenarios/all.js +1 -1
  100. package/fs/fsc/bnf.f.js +2 -2
  101. package/fs/fsc/json.f.js +1 -1
  102. package/fs/fsc/module.f.d.ts +1 -0
  103. package/fs/fsc/module.f.js +11 -7
  104. package/fs/fsc/proof.f.js +4 -4
  105. package/fs/fsm/module.f.js +9 -9
  106. package/fs/fsm/proof.f.js +7 -7
  107. package/fs/js/tokenizer/module.f.d.ts +1 -1
  108. package/fs/js/tokenizer/module.f.js +14 -8
  109. package/fs/js/tokenizer/proof.f.js +64 -5
  110. package/fs/mcp/module.f.d.ts +13 -13
  111. package/fs/mcp/module.f.js +8 -8
  112. package/fs/mcp/proof.f.js +5 -5
  113. package/fs/mcp/stdio/module.f.d.ts +2 -2
  114. package/fs/mcp/stdio/module.f.js +10 -10
  115. package/fs/mcp/stdio/proof.f.js +10 -10
  116. package/fs/{html → media/html}/module.f.d.ts +3 -3
  117. package/fs/{html → media/html}/module.f.js +9 -9
  118. package/fs/media/json/module.f.d.ts +50 -0
  119. package/fs/{json → media/json}/module.f.js +7 -7
  120. package/fs/{json → media/json}/parser/module.f.d.ts +2 -2
  121. package/fs/{json → media/json}/parser/module.f.js +7 -7
  122. package/fs/{json → media/json}/parser/proof.f.js +24 -6
  123. package/fs/{json → media/json}/proof.f.js +4 -4
  124. package/fs/media/json/rpc/module.f.d.ts +114 -0
  125. package/fs/{json → media/json}/rpc/module.f.js +3 -3
  126. package/fs/{json → media/json}/rpc/proof.f.js +4 -4
  127. package/fs/media/json/schema/module.f.d.ts +78 -0
  128. package/fs/{json → media/json}/schema/module.f.js +2 -2
  129. package/fs/{json → media/json}/schema/proof.f.js +3 -3
  130. package/fs/{json → media/json}/serializer/module.f.d.ts +1 -1
  131. package/fs/{json → media/json}/serializer/module.f.js +2 -2
  132. package/fs/{json → media/json}/serializer/proof.f.js +2 -2
  133. package/fs/{json → media/json}/tokenizer/module.f.d.ts +2 -2
  134. package/fs/{json → media/json}/tokenizer/module.f.js +4 -4
  135. package/fs/{json → media/json}/tokenizer/proof.f.js +5 -5
  136. package/fs/media/module.f.d.ts +37 -0
  137. package/fs/media/module.f.js +24 -0
  138. package/fs/media/proof.f.d.ts +9 -0
  139. package/fs/media/proof.f.js +57 -0
  140. package/fs/media/revision/module.f.d.ts +51 -0
  141. package/fs/media/revision/module.f.js +110 -0
  142. package/fs/media/revision/proof.f.d.ts +29 -0
  143. package/fs/media/revision/proof.f.js +119 -0
  144. package/fs/{mime → media/type}/module.f.d.ts +6 -6
  145. package/fs/{mime → media/type}/module.f.js +6 -6
  146. package/fs/{mime → media/type}/proof.f.js +6 -6
  147. package/fs/module.f.d.ts +2 -0
  148. package/fs/{fjs/module.f.js → module.f.js} +8 -8
  149. package/fs/module.js +4 -0
  150. package/fs/path/module.f.d.ts +6 -0
  151. package/fs/path/module.f.js +8 -2
  152. package/fs/path/proof.f.d.ts +1 -0
  153. package/fs/path/proof.f.js +40 -2
  154. package/fs/{fjs/proof.f.d.ts → proof.f.d.ts} +1 -0
  155. package/fs/{fjs/proof.f.js → proof.f.js} +10 -4
  156. package/fs/sul/id/module.f.js +9 -9
  157. package/fs/sul/id/proof.f.js +5 -5
  158. package/fs/sul/level/hash/module.f.js +3 -3
  159. package/fs/sul/level/hash/proof.f.js +3 -3
  160. package/fs/sul/level/literal/module.f.js +5 -5
  161. package/fs/sul/level/literal/proof.f.js +3 -3
  162. package/fs/sul/module.f.js +3 -3
  163. package/fs/sul/proof.f.js +3 -3
  164. package/fs/text/ascii/proof.f.js +3 -3
  165. package/fs/text/code_point/module.f.js +2 -2
  166. package/fs/text/code_point/proof.f.js +1 -1
  167. package/fs/text/module.f.js +5 -5
  168. package/fs/text/proof.f.js +4 -4
  169. package/fs/text/sgr/module.f.js +2 -2
  170. package/fs/text/sgr/proof.f.js +2 -2
  171. package/fs/text/utf16/module.f.js +5 -5
  172. package/fs/text/utf16/proof.f.js +4 -4
  173. package/fs/text/utf8/module.f.d.ts +6 -0
  174. package/fs/text/utf8/module.f.js +37 -19
  175. package/fs/text/utf8/proof.f.js +58 -5
  176. package/fs/types/array/module.f.js +1 -1
  177. package/fs/types/array/proof.f.js +3 -3
  178. package/fs/types/bigfloat/module.f.js +1 -1
  179. package/fs/types/bigfloat/proof.f.js +1 -1
  180. package/fs/types/bigint/module.f.js +2 -2
  181. package/fs/types/bigint/proof.f.js +3 -3
  182. package/fs/types/bit_vec/module.f.js +8 -8
  183. package/fs/types/bit_vec/proof.f.js +5 -5
  184. package/fs/types/btree/find/module.f.js +1 -1
  185. package/fs/types/btree/find/proof.f.d.ts +3 -1
  186. package/fs/types/btree/find/proof.f.js +7 -7
  187. package/fs/types/btree/module.f.js +2 -2
  188. package/fs/types/btree/proof.f.d.ts +11 -5
  189. package/fs/types/btree/proof.f.js +7 -7
  190. package/fs/types/btree/remove/module.f.js +4 -4
  191. package/fs/types/btree/remove/proof.f.d.ts +5 -2
  192. package/fs/types/btree/remove/proof.f.js +5 -5
  193. package/fs/types/btree/set/module.f.js +3 -3
  194. package/fs/types/btree/set/proof.f.js +4 -4
  195. package/fs/types/byte_set/module.f.js +2 -2
  196. package/fs/types/byte_set/proof.f.js +4 -4
  197. package/fs/types/function/compare/proof.f.js +1 -1
  198. package/fs/types/function/operator/proof.f.d.ts +21 -10
  199. package/fs/types/function/operator/proof.f.js +1 -1
  200. package/fs/types/function/proof.f.js +1 -1
  201. package/fs/types/list/module.f.js +2 -2
  202. package/fs/types/list/proof.f.d.ts +15 -7
  203. package/fs/types/list/proof.f.js +4 -4
  204. package/fs/types/map/proof.f.js +2 -2
  205. package/fs/types/nibble_set/proof.f.js +2 -2
  206. package/fs/types/nullable/module.f.js +2 -2
  207. package/fs/types/nullable/proof.f.js +1 -1
  208. package/fs/types/number/module.f.js +5 -5
  209. package/fs/types/number/proof.f.js +1 -1
  210. package/fs/types/object/module.f.js +4 -4
  211. package/fs/types/object/proof.f.js +1 -1
  212. package/fs/types/ordered_map/module.f.js +6 -6
  213. package/fs/types/ordered_map/proof.f.js +3 -3
  214. package/fs/types/patricia_trie/proof.f.d.ts +11 -5
  215. package/fs/types/patricia_trie/proof.f.js +2 -2
  216. package/fs/types/prime_field/module.f.js +2 -2
  217. package/fs/types/prime_field/proof.f.js +2 -2
  218. package/fs/types/range/proof.f.js +1 -1
  219. package/fs/types/range_map/module.f.js +4 -4
  220. package/fs/types/range_map/proof.f.js +7 -7
  221. package/fs/types/result/module.f.d.ts +4 -0
  222. package/fs/types/result/module.f.js +4 -0
  223. package/fs/types/result/module.js +1 -1
  224. package/fs/types/result/proof.f.d.ts +9 -3
  225. package/fs/types/result/proof.f.js +12 -2
  226. package/fs/types/rtti/common/module.f.js +4 -4
  227. package/fs/types/rtti/module.f.d.ts +2 -2
  228. package/fs/types/rtti/module.f.js +1 -1
  229. package/fs/types/rtti/parse/module.f.js +5 -5
  230. package/fs/types/rtti/parse/proof.f.js +2 -2
  231. package/fs/types/rtti/ts/module.f.js +1 -1
  232. package/fs/types/rtti/ts/proof.f.js +2 -2
  233. package/fs/types/rtti/validate/module.f.js +4 -4
  234. package/fs/types/rtti/validate/proof.f.js +2 -2
  235. package/fs/types/sorted_list/module.f.js +3 -3
  236. package/fs/types/sorted_list/proof.f.js +6 -6
  237. package/fs/types/sorted_set/module.f.js +1 -1
  238. package/fs/types/sorted_set/proof.f.js +6 -6
  239. package/fs/types/string/module.f.js +4 -4
  240. package/fs/types/string/proof.f.js +2 -2
  241. package/fs/types/string_set/module.f.js +6 -6
  242. package/fs/types/string_set/proof.f.js +1 -1
  243. package/fs/types/ts/proof.f.d.ts +18 -18
  244. package/fs/types/ts/proof.f.js +1 -1
  245. package/fs/types/uint8array/module.f.js +4 -4
  246. package/fs/types/uint8array/proof.f.js +4 -4
  247. package/fs/website/module.f.js +3 -3
  248. package/fs/website/proof.f.js +3 -3
  249. package/package.json +8 -8
  250. package/fs/base64/module.f.js +0 -60
  251. package/fs/fjs/module.f.d.ts +0 -2
  252. package/fs/fjs/module.js +0 -4
  253. package/fs/json/module.f.d.ts +0 -50
  254. package/fs/json/rpc/module.f.d.ts +0 -114
  255. package/fs/json/schema/module.f.d.ts +0 -77
  256. /package/fs/{base128 → basen/base128}/proof.f.d.ts +0 -0
  257. /package/fs/{cbase32 → basen/cbase32}/proof.f.d.ts +0 -0
  258. /package/fs/{types → common}/monoid/module.f.js +0 -0
  259. /package/fs/{types → common}/monoid/proof.f.d.ts +0 -0
  260. /package/fs/{types → common}/monoid/proof.f.js +0 -0
  261. /package/fs/{html → media/html}/proof.f.d.ts +0 -0
  262. /package/fs/{html → media/html}/proof.f.js +0 -0
  263. /package/fs/{json → media/json}/parser/proof.f.d.ts +0 -0
  264. /package/fs/{json → media/json}/proof.f.d.ts +0 -0
  265. /package/fs/{json → media/json}/rpc/proof.f.d.ts +0 -0
  266. /package/fs/{json → media/json}/schema/proof.f.d.ts +0 -0
  267. /package/fs/{json → media/json}/serializer/proof.f.d.ts +0 -0
  268. /package/fs/{json → media/json}/tokenizer/proof.f.d.ts +0 -0
  269. /package/fs/{mime → media/type}/proof.f.d.ts +0 -0
  270. /package/fs/{fjs/module.d.ts → module.d.ts} +0 -0
@@ -11,8 +11,8 @@
11
11
  *
12
12
  * | Tool | args | action | result |
13
13
  * |------------|-------------------------------|------------------|-------------------------------------|
14
- * | `cas_add` | `{ content, type? }` | write/upload | hash (cBase32) |
15
- * | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,mime_type,type[,content]}` |
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
- * - `'url'`: `content` is a filesystem path within `$HOME/cas_upload/`; the server
27
- * moves the file via the streaming move-hash-move pipeline (no size limit).
28
- * Paths outside `$HOME/cas_upload/` or containing `..` are rejected for security.
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, mime_type, type[, url][, content] }`.
33
- * `type` is always present (`'text'` or `'base64'`). A single classifier — the
34
- * `fs/mime` detector state machine (length × magic-byte eliminator × UTF-8 DFA)
35
- * produces the same three-way verdict on both paths, so there is no second,
36
- * divergent copy of the rules:
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 `mime_type`.
40
- * 2. **Whole-blob-valid UTF-8** → `type: 'text'`, `mime_type: 'text/plain'`.
41
- * 3. **Fallback** → `type: 'base64'`, `mime_type: 'application/octet-stream'`.
42
- *
43
- * **Metadata-only (`content: false`, the default) is size-independent.** It folds
44
- * the read stream through `fs/mime` `detectStream` and never buffers the blob.
45
- * Because a single `Vec` caps at `maxLength` bits (128 KiB), the old
46
- * drain-into-one-`Vec` approach failed on any blob larger than one chunk even when
47
- * only metadata was asked for; streaming detection returns correct
48
- * `{ length, mime_type, type[, url] }` regardless of size.
49
- *
50
- * **`content: true`** first derives `{ length, mime_type, type }` with the same
51
- * size-independent `fs/mime` `detectStream` machine, then materializes the bytes
52
- * (via `collectRead`, bounded by `maxLength`) and encodes the inline payload by
53
- * `type` a `fs/text/utf8` `fromVec` string for `text`, base64 for `base64`. A
54
- * blob larger than `maxLength` (128 KiB) cannot be buffered into one `Vec`, so it
55
- * is rejected here with a descriptive *"too large"* error (carrying the byte size
56
- * and `url`) rather than being misreported as absent; it should be fetched via
57
- * `url` or inspected with metadata-only `cas_get`.
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 "../../types/rtti/module.f.js";
82
- import { stringify } from "../../json/module.f.js";
83
- import { pure } from "../../effects/module.f.js";
84
- import { create } from "../../effects/memory/module.f.js";
85
- import { cBase32ToVec, vecToCBase32 } from "../../cbase32/module.f.js";
86
- import { decode as base64Decode, encode as base64Encode } from "../../base64/module.f.js";
87
- import { tryUtf8 } from "../../text/module.f.js";
88
- import { detectStream } from "../../mime/module.f.js";
89
- import { empty, length as bitVecLength, maxLength, maxLengthBytes, msb } from "../../types/bit_vec/module.f.js";
90
- import { ok, error } from "../../types/result/module.f.js";
91
- import { rm } from "../../effects/node/module.f.js";
92
- import { stdioTransport } from "../../mcp/stdio/module.f.js";
93
- import { mcpStep, uninitializedState, toolEntry, fromRegistry, errorResult, } from "../../mcp/module.f.js";
94
- import { casAddFile, fileCas } from "../module.f.js";
95
- import { fromVec } from "../../text/utf8/module.f.js";
96
- import { identity } from "../../types/function/module.f.js";
97
- import { sha256 } from "../../crypto/sha2/module.f.js";
98
- import { nonEmpty, empty as elEmpty } from "../../effects/list/module.f.js";
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', 'url', undefined)
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/mime` `detectStream`,
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 (text/base64) is capped at 128 KiB (131072 bytes) — larger content is rejected. For larger content use type:"url" to stream a file from $HOME/cas_upload/ (no size limit).', casAddArgs, ({ type, 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 — use type:"url" for large content'))
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,mime_type,type[,url]} where type is "text" or "base64". Pass content:true to also include the inline content string, but content is capped at 128 KiB (131072 bytes) — a larger blob is rejected with an error. To download a blob, prefer the url field returned in the result instead of requesting inline content.', casGetArgs, r => {
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 url = c.url(key);
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), mime_type, type, url };
190
+ const { length, mime_type: mimeType, type } = detected;
191
+ const meta = { length: Number(length), mimeType, type, uri };
180
192
  if (r.content !== true) {
181
- // content:true path continues below; this is just the metadata step.
182
- return pure(okResult(toJson(meta)));
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 url field (${url}) or omit content for metadata`));
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
- if (type === 'text') {
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({ ...meta, content: str })));
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({ ...meta, content: blob })));
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
+ };
@@ -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
- addBase64AtLimitIsError: () => void;
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
  };