functionalscript 0.30.0 → 0.31.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 +18 -22
- package/fs/asn.1/proof.f.d.ts +5 -0
- package/fs/asn.1/proof.f.js +38 -2
- package/fs/base64/module.f.d.ts +9 -0
- package/fs/base64/module.f.js +60 -0
- package/fs/base64/proof.f.d.ts +11 -0
- package/fs/base64/proof.f.js +117 -0
- package/fs/base_n/module.f.d.ts +44 -0
- package/fs/base_n/module.f.js +54 -0
- package/fs/base_n/proof.f.d.ts +10 -0
- package/fs/base_n/proof.f.js +86 -0
- package/fs/bnf/data/module.f.d.ts +4 -9
- package/fs/bnf/data/module.f.js +4 -3
- package/fs/bnf/module.f.d.ts +3 -4
- package/fs/bnf/module.f.js +3 -2
- package/fs/bnf/proof.f.d.ts +9 -0
- package/fs/bnf/proof.f.js +98 -1
- package/fs/cas/mcp/module.f.d.ts +40 -0
- package/fs/cas/mcp/module.f.js +223 -0
- package/fs/cas/mcp/proof.f.d.ts +30 -0
- package/fs/cas/mcp/proof.f.js +310 -0
- package/fs/cas/module.f.d.ts +9 -3
- package/fs/cas/module.f.js +104 -69
- package/fs/cas/proof.f.d.ts +2 -0
- package/fs/cas/proof.f.js +30 -1
- package/fs/cbase32/module.f.d.ts +1 -1
- package/fs/cbase32/module.f.js +19 -40
- package/fs/cbase32/proof.f.d.ts +1 -0
- package/fs/cbase32/proof.f.js +15 -0
- package/fs/ci/config/module.f.d.ts +6 -6
- package/fs/ci/config/module.f.js +6 -6
- package/fs/ci/deno/module.f.js +2 -2
- package/fs/ci/module.f.d.ts +5 -0
- package/fs/ci/module.f.js +2 -3
- package/fs/ci/proof.f.d.ts +1 -0
- package/fs/ci/proof.f.js +9 -3
- package/fs/cli/module.f.d.ts +3 -1
- package/fs/cli/module.f.js +22 -12
- package/fs/cli/proof.f.d.ts +5 -0
- package/fs/cli/proof.f.js +76 -0
- package/fs/crypto/hmac/proof.f.d.ts +1 -0
- package/fs/crypto/hmac/proof.f.js +8 -0
- package/fs/crypto/secp/proof.f.d.ts +1 -0
- package/fs/crypto/secp/proof.f.js +10 -0
- package/fs/crypto/sign/module.f.js +1 -4
- package/fs/dev/module.f.d.ts +5 -3
- package/fs/dev/module.f.js +13 -2
- package/fs/dev/package_json/proof.f.d.ts +1 -0
- package/fs/dev/package_json/proof.f.js +5 -0
- package/fs/dev/proof.f.d.ts +1 -0
- package/fs/dev/proof.f.js +12 -1
- package/fs/djs/ast/module.f.d.ts +1 -1
- package/fs/djs/ast/proof.f.d.ts +5 -0
- package/fs/djs/ast/proof.f.js +17 -0
- package/fs/djs/module.f.d.ts +1 -1
- package/fs/djs/module.f.js +2 -3
- package/fs/djs/serializer/module.f.js +24 -11
- package/fs/djs/transpiler/module.f.js +3 -4
- package/fs/effects/memory/module.f.d.ts +37 -0
- package/fs/effects/memory/module.f.js +24 -0
- package/fs/effects/memory/proof.f.d.ts +6 -0
- package/fs/effects/memory/proof.f.js +64 -0
- package/fs/effects/mock/module.f.d.ts +7 -2
- package/fs/effects/mock/module.f.js +12 -8
- package/fs/effects/module.d.ts +1 -1
- package/fs/effects/module.f.d.ts +41 -0
- package/fs/effects/module.f.js +25 -0
- package/fs/effects/module.js +6 -7
- package/fs/effects/node/memory/module.d.ts +20 -0
- package/fs/effects/node/memory/module.js +44 -0
- package/fs/effects/node/memory/proof.d.ts +10 -0
- package/fs/effects/node/memory/proof.js +35 -0
- package/fs/effects/node/module.d.ts +1 -1
- package/fs/effects/node/module.f.d.ts +54 -8
- package/fs/effects/node/module.f.js +55 -4
- package/fs/effects/node/module.js +166 -135
- package/fs/effects/node/proof.f.d.ts +10 -0
- package/fs/effects/node/proof.f.js +88 -21
- package/fs/effects/node/virtual/module.f.d.ts +6 -0
- package/fs/effects/node/virtual/module.f.js +48 -16
- package/fs/effects/node/virtual/proof.f.d.ts +17 -0
- package/fs/effects/node/virtual/proof.f.js +79 -0
- package/fs/effects/proof.f.d.ts +5 -0
- package/fs/effects/proof.f.js +54 -34
- package/fs/emergent_testing/module.f.d.ts +6 -1
- package/fs/emergent_testing/module.f.js +8 -3
- package/fs/emergent_testing/proof.f.d.ts +11 -0
- package/fs/emergent_testing/proof.f.js +52 -16
- package/fs/fjs/module.f.js +2 -2
- package/fs/fjs/proof.f.d.ts +8 -0
- package/fs/fjs/proof.f.js +39 -0
- package/fs/fsc/module.f.d.ts +5 -0
- package/fs/fsc/module.f.js +17 -27
- package/fs/fsc/proof.f.d.ts +8 -0
- package/fs/fsc/proof.f.js +48 -2
- package/fs/fsm/module.f.d.ts +2 -3
- package/fs/fsm/module.f.js +1 -1
- package/fs/html/module.f.d.ts +2 -3
- package/fs/html/module.f.js +7 -6
- package/fs/html/proof.f.d.ts +1 -0
- package/fs/html/proof.f.js +7 -0
- package/fs/js/tokenizer/module.f.js +1 -1
- package/fs/js/tokenizer/proof.f.js +7 -0
- package/fs/json/module.f.d.ts +9 -2
- package/fs/json/module.f.js +9 -7
- package/fs/json/parser/module.f.js +4 -6
- package/fs/json/parser/proof.f.js +19 -10
- package/fs/json/proof.f.d.ts +2 -1
- package/fs/json/proof.f.js +33 -6
- package/fs/json/rpc/module.f.d.ts +5 -0
- package/fs/json/rpc/module.f.js +16 -11
- package/fs/json/schema/module.f.d.ts +15 -14
- package/fs/json/schema/proof.f.d.ts +3 -0
- package/fs/json/schema/proof.f.js +16 -1
- package/fs/mcp/module.f.d.ts +103 -5
- package/fs/mcp/module.f.js +139 -2
- package/fs/mcp/proof.f.d.ts +34 -0
- package/fs/mcp/proof.f.js +207 -0
- package/fs/mcp/stdio/module.f.d.ts +39 -0
- package/fs/mcp/stdio/module.f.js +54 -0
- package/fs/mcp/stdio/proof.f.d.ts +10 -0
- package/fs/mcp/stdio/proof.f.js +92 -0
- package/fs/mime/module.f.d.ts +39 -0
- package/fs/mime/module.f.js +78 -0
- package/fs/mime/proof.f.d.ts +15 -0
- package/fs/mime/proof.f.js +30 -0
- package/fs/path/module.f.d.ts +8 -0
- package/fs/path/module.f.js +10 -3
- package/fs/path/proof.f.d.ts +1 -0
- package/fs/path/proof.f.js +40 -2
- package/fs/text/sgr/proof.f.js +30 -2
- package/fs/text/utf8/module.f.d.ts +7 -0
- package/fs/text/utf8/module.f.js +85 -31
- package/fs/text/utf8/proof.f.d.ts +1 -0
- package/fs/text/utf8/proof.f.js +60 -1
- package/fs/types/bigfloat/module.f.d.ts +3 -0
- package/fs/types/bigfloat/module.f.js +30 -31
- package/fs/types/bigint/module.f.d.ts +24 -2
- package/fs/types/bigint/module.f.js +30 -2
- package/fs/types/bigint/proof.f.d.ts +7 -0
- package/fs/types/bigint/proof.f.js +47 -16
- package/fs/types/bit_vec/module.f.d.ts +18 -0
- package/fs/types/bit_vec/module.f.js +18 -0
- package/fs/types/bit_vec/proof.f.d.ts +1 -0
- package/fs/types/bit_vec/proof.f.js +13 -1
- package/fs/types/function/operator/proof.f.d.ts +2 -0
- package/fs/types/function/operator/proof.f.js +20 -2
- package/fs/types/list/module.f.js +1 -4
- package/fs/types/list/proof.f.d.ts +1 -1
- package/fs/types/list/proof.f.js +14 -6
- package/fs/types/map/proof.f.d.ts +1 -0
- package/fs/types/map/proof.f.js +10 -1
- package/fs/types/nibble_set/module.f.d.ts +13 -0
- package/fs/types/nibble_set/module.f.js +16 -0
- package/fs/types/object/module.f.d.ts +12 -8
- package/fs/types/object/module.f.js +9 -2
- package/fs/types/object/proof.f.d.ts +14 -0
- package/fs/types/object/proof.f.js +4 -0
- package/fs/types/ordered_map/proof.f.d.ts +1 -0
- package/fs/types/ordered_map/proof.f.js +39 -79
- package/fs/types/phantom/module.f.d.ts +22 -0
- package/fs/types/phantom/module.f.js +11 -0
- package/fs/types/prime_field/proof.f.d.ts +7 -0
- package/fs/types/prime_field/proof.f.js +63 -122
- package/fs/types/rtti/common/module.f.d.ts +4 -4
- package/fs/types/rtti/common/module.f.js +1 -1
- package/fs/types/rtti/module.f.d.ts +2 -3
- package/fs/types/rtti/parse/module.f.js +0 -1
- package/fs/types/rtti/ts/module.f.d.ts +26 -32
- package/fs/types/rtti/ts/module.f.js +2 -2
- package/fs/types/rtti/ts/proof.f.js +3 -3
- package/fs/types/rtti/validate/module.f.js +0 -1
- package/fs/types/sorted_list/module.f.d.ts +6 -0
- package/fs/types/sorted_list/module.f.js +11 -0
- package/fs/types/sorted_set/module.f.js +2 -8
- package/fs/types/ts/module.f.d.ts +1 -1
- package/fs/types/ts/module.f.js +3 -1
- package/fs/types/ts/proof.f.js +3 -2
- package/fs/website/proof.f.d.ts +1 -0
- package/fs/website/proof.f.js +8 -1
- package/package.json +3 -3
package/fs/bnf/proof.f.js
CHANGED
|
@@ -1,7 +1,104 @@
|
|
|
1
1
|
import { classic, deterministic } from "./testlib.f.js";
|
|
2
|
+
import { rangeEncode, str, set, range, commaJoin0Plus, isEmpty, oneEncode, repeat1Plus, } from "./module.f.js";
|
|
2
3
|
export const proof = {
|
|
3
4
|
test: () => {
|
|
4
5
|
classic();
|
|
5
6
|
deterministic();
|
|
6
|
-
}
|
|
7
|
+
},
|
|
8
|
+
throw: {
|
|
9
|
+
rangeEncodeInvalid: [
|
|
10
|
+
() => { rangeEncode(-1, 0); },
|
|
11
|
+
() => { rangeEncode(0, -1); },
|
|
12
|
+
() => { rangeEncode(5, 3); },
|
|
13
|
+
],
|
|
14
|
+
rangeInvalid: [
|
|
15
|
+
() => { range('a'); },
|
|
16
|
+
() => { range('abc'); },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
str: [
|
|
20
|
+
() => {
|
|
21
|
+
const result = str('a');
|
|
22
|
+
if (typeof result !== 'number') {
|
|
23
|
+
throw result;
|
|
24
|
+
}
|
|
25
|
+
if (result !== oneEncode(0x61)) {
|
|
26
|
+
throw result;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
() => {
|
|
30
|
+
const result = str('ab');
|
|
31
|
+
if (!Array.isArray(result)) {
|
|
32
|
+
throw result;
|
|
33
|
+
}
|
|
34
|
+
if (result.length !== 2) {
|
|
35
|
+
throw result;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
set: () => {
|
|
40
|
+
const result = set('abc');
|
|
41
|
+
if (typeof result !== 'object' || result === null) {
|
|
42
|
+
throw result;
|
|
43
|
+
}
|
|
44
|
+
if (!('a' in result)) {
|
|
45
|
+
throw result;
|
|
46
|
+
}
|
|
47
|
+
if (!('b' in result)) {
|
|
48
|
+
throw result;
|
|
49
|
+
}
|
|
50
|
+
if (!('c' in result)) {
|
|
51
|
+
throw result;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
commaJoin0Plus: () => {
|
|
55
|
+
const ws = '';
|
|
56
|
+
const item = 'x';
|
|
57
|
+
const result = commaJoin0Plus(ws)('[]', item);
|
|
58
|
+
if (!Array.isArray(result)) {
|
|
59
|
+
throw result;
|
|
60
|
+
}
|
|
61
|
+
if (result[0] !== '[') {
|
|
62
|
+
throw result;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
isEmpty: [
|
|
66
|
+
() => { if (!isEmpty('')) {
|
|
67
|
+
throw 'empty string should be empty';
|
|
68
|
+
} },
|
|
69
|
+
() => { if (!isEmpty([])) {
|
|
70
|
+
throw 'empty array should be empty';
|
|
71
|
+
} },
|
|
72
|
+
() => { if (isEmpty('a')) {
|
|
73
|
+
throw 'non-empty string should not be empty';
|
|
74
|
+
} },
|
|
75
|
+
() => {
|
|
76
|
+
const f = () => '';
|
|
77
|
+
if (!isEmpty(f)) {
|
|
78
|
+
throw 'function returning empty string should be empty';
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
repeat1Plus: [
|
|
83
|
+
() => {
|
|
84
|
+
const result = repeat1Plus('x');
|
|
85
|
+
if (result[0] !== 'x') {
|
|
86
|
+
throw result[0];
|
|
87
|
+
}
|
|
88
|
+
if (typeof result[1] !== 'function') {
|
|
89
|
+
throw 'expected repeat0Plus function';
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
() => {
|
|
93
|
+
const rule = 'ab';
|
|
94
|
+
const result = repeat1Plus(rule);
|
|
95
|
+
if (result[0] !== rule) {
|
|
96
|
+
throw result[0];
|
|
97
|
+
}
|
|
98
|
+
const inner = result[1]();
|
|
99
|
+
if (!('some' in inner) || !('none' in inner)) {
|
|
100
|
+
throw 'expected Option shape';
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
],
|
|
7
104
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Effect, type Operation } from '../../effects/module.f.ts';
|
|
2
|
+
import { type MemOp } from '../../effects/memory/module.f.ts';
|
|
3
|
+
import { type Vec } from '../../types/bit_vec/module.f.ts';
|
|
4
|
+
import { type Read, type ReadFile, type Write } from '../../effects/node/module.f.ts';
|
|
5
|
+
import { type McpConfig, type McpHandlers } from '../../mcp/module.f.ts';
|
|
6
|
+
import type { Cas } from '../module.f.ts';
|
|
7
|
+
/** Arguments for `cas_add`: content to store, with optional encoding type. */
|
|
8
|
+
export declare const casAddArgs: {
|
|
9
|
+
readonly content: import("../../types/rtti/module.f.ts").String;
|
|
10
|
+
readonly type: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Or<[import("../../types/rtti/module.f.ts").Or<["text", "base64"]>, "url"]>, undefined]>;
|
|
11
|
+
};
|
|
12
|
+
/** Arguments for `cas_get`: the cBase32 hash to look up; optionally request inline content. */
|
|
13
|
+
export declare const casGetArgs: {
|
|
14
|
+
readonly hash: import("../../types/rtti/module.f.ts").String;
|
|
15
|
+
readonly content: import("../../types/rtti/module.f.ts").Or<readonly [import("../../types/rtti/module.f.ts").Boolean, undefined]>;
|
|
16
|
+
};
|
|
17
|
+
/** Arguments for `cas_list`: none. */
|
|
18
|
+
export declare const casListArgs: {};
|
|
19
|
+
/**
|
|
20
|
+
* MCP handlers for an injected `Cas<O>` — generic in `O` exactly like `Cas`
|
|
21
|
+
* itself, so the same handlers run over `Fs` (production) or memory (tests).
|
|
22
|
+
*
|
|
23
|
+
* When `toUrl` is provided, `cas_get` includes the `url` field pointing to
|
|
24
|
+
* the blob on the local filesystem. When absent (e.g. memory-backed tests),
|
|
25
|
+
* `url` is omitted.
|
|
26
|
+
*/
|
|
27
|
+
export declare const casMcpHandlers: <O extends Operation>(c: Cas<O>, toUrl?: (hash: Vec) => string) => McpHandlers<ReadFile | O>;
|
|
28
|
+
/**
|
|
29
|
+
* Static MCP configuration for the CAS server: advertises the `tools`
|
|
30
|
+
* capability, identifies the server, and pins the protocol version.
|
|
31
|
+
*/
|
|
32
|
+
export declare const casConfig: McpConfig;
|
|
33
|
+
/**
|
|
34
|
+
* Runs the CAS MCP server over stdio: allocates the session-state slot, builds
|
|
35
|
+
* the `mcpStep` for `c`, and drives the read → parse → dispatch → write loop
|
|
36
|
+
* until stdin EOF. Generic in `O` so it composes with any `Cas<O>` backing.
|
|
37
|
+
*
|
|
38
|
+
* When `toUrl` is provided, `cas_get` includes the blob's filesystem URL.
|
|
39
|
+
*/
|
|
40
|
+
export declare const casMcpServer: <O extends Operation>(c: Cas<O>, toUrl?: (hash: Vec) => string) => Effect<Read | Write | MemOp | ReadFile | O, void>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP adapter for the content-addressable store.
|
|
3
|
+
*
|
|
4
|
+
* Maps `Cas<O>` operations onto MCP tools, so an agent that speaks MCP can
|
|
5
|
+
* store a blob and get back its hash, fetch a blob by hash, and enumerate
|
|
6
|
+
* what is stored — without shelling out to the `cas` CLI. The store itself
|
|
7
|
+
* (`fs/cas/module.f.ts`) stays transport-agnostic; this is an additional
|
|
8
|
+
* front end alongside the CLI `main`.
|
|
9
|
+
*
|
|
10
|
+
* ## Tools
|
|
11
|
+
*
|
|
12
|
+
* | Tool | args | CAS call | result |
|
|
13
|
+
* |------------|-------------------------------|------------------|-------------------------------------|
|
|
14
|
+
* | `cas_add` | `{ content, type? }` | `c.write(value)` | hash (cBase32) |
|
|
15
|
+
* | `cas_get` | `{ hash, content?: boolean }` | `c.read(key)` | JSON `{length,mime_type,type[,content]}` |
|
|
16
|
+
* | `cas_list` | `{}` | `c.list()` | hashes, one per line |
|
|
17
|
+
*
|
|
18
|
+
* ## `cas_add` input encoding
|
|
19
|
+
*
|
|
20
|
+
* The optional `type` field controls how `content` is interpreted:
|
|
21
|
+
* - `'text'` (default, or omitted): `content` is a UTF-8 string stored as raw
|
|
22
|
+
* bytes. Most agent-generated content (scripts, JSON, prompts) can be stored
|
|
23
|
+
* without any encoding step.
|
|
24
|
+
* - `'base64'`: `content` is RFC 4648 base64, decoded to bytes before storage.
|
|
25
|
+
* Use this for pre-encoded binary payloads.
|
|
26
|
+
* - `'url'`: `content` is a filesystem path; the server reads the file at that
|
|
27
|
+
* path and stores its raw bytes.
|
|
28
|
+
*
|
|
29
|
+
* ## `cas_get` output
|
|
30
|
+
*
|
|
31
|
+
* Always returns a JSON object `{ length, mime_type, type[, url][, content] }`.
|
|
32
|
+
* `type` is always present (`'text'` or `'base64'`). When `content: true` is
|
|
33
|
+
* requested, the inline payload is also included. Two-phase MIME detection
|
|
34
|
+
* determines the encoding:
|
|
35
|
+
*
|
|
36
|
+
* 1. **Magic-byte sniffing** (`fs/mime` `detect`): PNG/JPEG/GIF/WebP/PDF/ZIP →
|
|
37
|
+
* `type: 'base64'` with the detected `mime_type`.
|
|
38
|
+
* 2. **UTF-8 validation** (`fs/text/utf8` `fromVec`): valid UTF-8 →
|
|
39
|
+
* `type: 'text'`, `mime_type: 'text/plain'`.
|
|
40
|
+
* 3. **Fallback**: `type: 'base64'`, `mime_type: 'application/octet-stream'`.
|
|
41
|
+
*
|
|
42
|
+
* When `content: false` (default), only `{ length, mime_type, type[, url] }` is
|
|
43
|
+
* returned — no content transfer.
|
|
44
|
+
*
|
|
45
|
+
* ## Encoding split: hashes vs. content
|
|
46
|
+
*
|
|
47
|
+
* `Cas<O>` deals in `Vec` (bit vectors); MCP models only `textContent` today.
|
|
48
|
+
* **Hashes** travel as cBase32 (`fs/cbase32`) — the canonical CAS hash format,
|
|
49
|
+
* shared with the CLI and the on-disk store layout. **Content** encoding is
|
|
50
|
+
* determined at read time as described above.
|
|
51
|
+
*
|
|
52
|
+
* ## Error convention
|
|
53
|
+
*
|
|
54
|
+
* MCP draws a line the dispatcher already respects: *protocol* failures
|
|
55
|
+
* (unknown method, malformed JSON-RPC params) are JSON-RPC errors handled by
|
|
56
|
+
* `mcpStep`. *Tool* failures come back as a normal `tools/call` result with
|
|
57
|
+
* `isError: true` and a text explanation, so the model can read and react:
|
|
58
|
+
* - `type: 'base64'` with malformed content (base64 `decode` → `null`) → `isError`
|
|
59
|
+
* - malformed `hash` (`cBase32ToVec` → `null`) → `isError`
|
|
60
|
+
* - `cas_get` on an absent hash (`c.read` → `undefined`) → `isError`
|
|
61
|
+
* - unknown tool `name` → `isError`
|
|
62
|
+
*
|
|
63
|
+
* @module
|
|
64
|
+
*/
|
|
65
|
+
import { string, option, or, boolean } from "../../types/rtti/module.f.js";
|
|
66
|
+
import { validate } from "../../types/rtti/validate/module.f.js";
|
|
67
|
+
import { toJsonSchema } from "../../json/schema/module.f.js";
|
|
68
|
+
import { pure } from "../../effects/module.f.js";
|
|
69
|
+
import { create } from "../../effects/memory/module.f.js";
|
|
70
|
+
import { cBase32ToVec, vecToCBase32 } from "../../cbase32/module.f.js";
|
|
71
|
+
import { decode as base64Decode, encode as base64Encode } from "../../base64/module.f.js";
|
|
72
|
+
import { utf8 } from "../../text/module.f.js";
|
|
73
|
+
import { detect } from "../../mime/module.f.js";
|
|
74
|
+
import { length as bitVecLength } from "../../types/bit_vec/module.f.js";
|
|
75
|
+
import { readFile } from "../../effects/node/module.f.js";
|
|
76
|
+
import { stdioTransport } from "../../mcp/stdio/module.f.js";
|
|
77
|
+
import { mcpStep, uninitializedState, } from "../../mcp/module.f.js";
|
|
78
|
+
import { fromVec } from "../../text/utf8/module.f.js";
|
|
79
|
+
// ── Argument schemas (declared once, used for both inputSchema and validate) ─────
|
|
80
|
+
/** Arguments for `cas_add`: content to store, with optional encoding type. */
|
|
81
|
+
export const casAddArgs = { content: string, type: option(or(or('text', 'base64'), 'url')) };
|
|
82
|
+
/** Arguments for `cas_get`: the cBase32 hash to look up; optionally request inline content. */
|
|
83
|
+
export const casGetArgs = { hash: string, content: option(boolean) };
|
|
84
|
+
/** Arguments for `cas_list`: none. */
|
|
85
|
+
export const casListArgs = {};
|
|
86
|
+
// ── Tool descriptors ────────────────────────────────────────────────────────────
|
|
87
|
+
const casAddTool = {
|
|
88
|
+
name: 'cas_add',
|
|
89
|
+
description: 'Store content and return its hash (cBase32). Pass type:"base64" for binary; type:"url" to read from a filesystem path; omit or pass type:"text" for UTF-8 text (default).',
|
|
90
|
+
inputSchema: toJsonSchema(casAddArgs),
|
|
91
|
+
};
|
|
92
|
+
const casGetTool = {
|
|
93
|
+
name: 'cas_get',
|
|
94
|
+
description: '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.',
|
|
95
|
+
inputSchema: toJsonSchema(casGetArgs),
|
|
96
|
+
};
|
|
97
|
+
const casListTool = {
|
|
98
|
+
name: 'cas_list',
|
|
99
|
+
description: 'List all stored content hashes (cBase32), one per line.',
|
|
100
|
+
inputSchema: toJsonSchema(casListArgs),
|
|
101
|
+
};
|
|
102
|
+
// ── Result helpers ──────────────────────────────────────────────────────────────
|
|
103
|
+
/** A successful single-text-block tool result. */
|
|
104
|
+
const okResult = (text) => ({ content: [{ type: 'text', text }] });
|
|
105
|
+
/** A tool-level failure: in-band `isError` result with a text explanation. */
|
|
106
|
+
const errorResult = (text) => ({ content: [{ type: 'text', text }], isError: true });
|
|
107
|
+
// ── Handlers ────────────────────────────────────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* MCP handlers for an injected `Cas<O>` — generic in `O` exactly like `Cas`
|
|
110
|
+
* itself, so the same handlers run over `Fs` (production) or memory (tests).
|
|
111
|
+
*
|
|
112
|
+
* When `toUrl` is provided, `cas_get` includes the `url` field pointing to
|
|
113
|
+
* the blob on the local filesystem. When absent (e.g. memory-backed tests),
|
|
114
|
+
* `url` is omitted.
|
|
115
|
+
*/
|
|
116
|
+
export const casMcpHandlers = (c, toUrl) => ({
|
|
117
|
+
toolsList: () => pure({ tools: [casAddTool, casGetTool, casListTool] }),
|
|
118
|
+
toolsCall: ({ name, arguments: args }) => {
|
|
119
|
+
const a = args === undefined ? {} : args;
|
|
120
|
+
switch (name) {
|
|
121
|
+
case 'cas_add': {
|
|
122
|
+
const [t, r] = validate(casAddArgs)(a);
|
|
123
|
+
if (t === 'error') {
|
|
124
|
+
return pure(errorResult(`invalid arguments: ${r.message}`));
|
|
125
|
+
}
|
|
126
|
+
const encoding = r.type ?? 'text';
|
|
127
|
+
if (encoding === 'url') {
|
|
128
|
+
return readFile(r.content).step(result => {
|
|
129
|
+
if (result[0] === 'error') {
|
|
130
|
+
return pure(errorResult(`cannot read file: ${r.content}: ${result[1]}`));
|
|
131
|
+
}
|
|
132
|
+
return c.write(result[1]).step(hash => pure(okResult(vecToCBase32(hash))));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
let value;
|
|
136
|
+
if (encoding === 'base64') {
|
|
137
|
+
value = base64Decode(r.content);
|
|
138
|
+
if (value === null) {
|
|
139
|
+
return pure(errorResult(`invalid base64 content: ${r.content}`));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
value = utf8(r.content);
|
|
144
|
+
}
|
|
145
|
+
return c.write(value).step(hash => pure(okResult(vecToCBase32(hash))));
|
|
146
|
+
}
|
|
147
|
+
case 'cas_get': {
|
|
148
|
+
const [t, r] = validate(casGetArgs)(a);
|
|
149
|
+
if (t === 'error') {
|
|
150
|
+
return pure(errorResult(`invalid arguments: ${r.message}`));
|
|
151
|
+
}
|
|
152
|
+
const key = cBase32ToVec(r.hash);
|
|
153
|
+
if (key === null) {
|
|
154
|
+
return pure(errorResult(`invalid cBase32 hash: ${r.hash}`));
|
|
155
|
+
}
|
|
156
|
+
return c.read(key).step(value => {
|
|
157
|
+
if (value === undefined) {
|
|
158
|
+
return pure(errorResult(`no such hash: ${r.hash}`));
|
|
159
|
+
}
|
|
160
|
+
const byteLength = Number(bitVecLength(value) / 8n);
|
|
161
|
+
// Phase 1: magic-byte sniffing for known binary formats.
|
|
162
|
+
const detectedMime = detect(value);
|
|
163
|
+
if (detectedMime !== null) {
|
|
164
|
+
const url = toUrl?.(key);
|
|
165
|
+
const meta = { length: byteLength, mime_type: detectedMime, type: 'base64', ...(url !== undefined && { url }) };
|
|
166
|
+
if (r.content === true) {
|
|
167
|
+
const blob = base64Encode(value);
|
|
168
|
+
if (blob === null) {
|
|
169
|
+
return pure(errorResult(`content is not byte-aligned: ${r.hash}`));
|
|
170
|
+
}
|
|
171
|
+
return pure(okResult(JSON.stringify({ ...meta, content: blob })));
|
|
172
|
+
}
|
|
173
|
+
return pure(okResult(JSON.stringify(meta)));
|
|
174
|
+
}
|
|
175
|
+
// Phase 2: UTF-8 validation — text if valid, octet-stream otherwise.
|
|
176
|
+
const str = fromVec(value);
|
|
177
|
+
const url = toUrl?.(key);
|
|
178
|
+
if (str !== null) {
|
|
179
|
+
const meta = { length: byteLength, mime_type: 'text/plain', type: 'text', ...(url !== undefined && { url }) };
|
|
180
|
+
if (r.content === true) {
|
|
181
|
+
return pure(okResult(JSON.stringify({ ...meta, content: str })));
|
|
182
|
+
}
|
|
183
|
+
return pure(okResult(JSON.stringify(meta)));
|
|
184
|
+
}
|
|
185
|
+
const meta = { length: byteLength, mime_type: 'application/octet-stream', type: 'base64', ...(url !== undefined && { url }) };
|
|
186
|
+
if (r.content === true) {
|
|
187
|
+
const blob = base64Encode(value);
|
|
188
|
+
if (blob === null) {
|
|
189
|
+
return pure(errorResult(`content is not byte-aligned: ${r.hash}`));
|
|
190
|
+
}
|
|
191
|
+
return pure(okResult(JSON.stringify({ ...meta, content: blob })));
|
|
192
|
+
}
|
|
193
|
+
return pure(okResult(JSON.stringify(meta)));
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
case 'cas_list': {
|
|
197
|
+
return c.list().step(hashes => pure(okResult(hashes.map(vecToCBase32).join('\n'))));
|
|
198
|
+
}
|
|
199
|
+
default: {
|
|
200
|
+
return pure(errorResult(`unknown tool: ${name}`));
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
// ── Session configuration ───────────────────────────────────────────────────────
|
|
206
|
+
/**
|
|
207
|
+
* Static MCP configuration for the CAS server: advertises the `tools`
|
|
208
|
+
* capability, identifies the server, and pins the protocol version.
|
|
209
|
+
*/
|
|
210
|
+
export const casConfig = {
|
|
211
|
+
serverInfo: { name: 'functionalscript-cas', version: '0.30.0' },
|
|
212
|
+
capabilities: { tools: {} },
|
|
213
|
+
protocolVersion: '2024-11-05',
|
|
214
|
+
};
|
|
215
|
+
// ── Server ──────────────────────────────────────────────────────────────────────
|
|
216
|
+
/**
|
|
217
|
+
* Runs the CAS MCP server over stdio: allocates the session-state slot, builds
|
|
218
|
+
* the `mcpStep` for `c`, and drives the read → parse → dispatch → write loop
|
|
219
|
+
* until stdin EOF. Generic in `O` so it composes with any `Cas<O>` backing.
|
|
220
|
+
*
|
|
221
|
+
* When `toUrl` is provided, `cas_get` includes the blob's filesystem URL.
|
|
222
|
+
*/
|
|
223
|
+
export const casMcpServer = (c, toUrl) => create(uninitializedState).step(key => stdioTransport(mcpStep(casConfig)(casMcpHandlers(c, toUrl))(key)));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const proof: {
|
|
2
|
+
toolsListAdvertisesThreeTools: () => void;
|
|
3
|
+
addReturnsHash: () => void;
|
|
4
|
+
textAddGetMetaRoundTrips: () => void;
|
|
5
|
+
textAddGetContentRoundTrips: () => void;
|
|
6
|
+
binaryAddGetMetaRoundTrips: () => void;
|
|
7
|
+
binaryAddGetContentRoundTrips: () => void;
|
|
8
|
+
addGetRoundTrips: () => void;
|
|
9
|
+
getUntypedReturnsText: () => void;
|
|
10
|
+
getTypedReturnsBinaryJson: () => void;
|
|
11
|
+
listEnumeratesStoredHashes: () => void;
|
|
12
|
+
addInvalidContentIsError: () => void;
|
|
13
|
+
addBadLengthContentIsError: () => void;
|
|
14
|
+
getUnterminatedHashIsError: () => void;
|
|
15
|
+
addMissingContentIsError: () => void;
|
|
16
|
+
getMissingHashArgumentIsError: () => void;
|
|
17
|
+
getInvalidHashIsError: () => void;
|
|
18
|
+
getMissingHashIsError: () => void;
|
|
19
|
+
unknownToolIsError: () => void;
|
|
20
|
+
toolErrorIsNotJsonRpcError: () => void;
|
|
21
|
+
addUrlStoresFileAndReturnsHash: () => void;
|
|
22
|
+
addUrlRoundTrips: () => void;
|
|
23
|
+
addUrlMissingFileIsError: () => void;
|
|
24
|
+
getMetaReturnsLengthAndMimeType: () => void;
|
|
25
|
+
getMetaBinaryBlob: () => void;
|
|
26
|
+
getMetaOctetStreamForUnknownBinary: () => void;
|
|
27
|
+
getMetaNoUrlWhenToUrlAbsent: () => void;
|
|
28
|
+
getMetaMissingHashIsError: () => void;
|
|
29
|
+
getMetaInvalidHashIsError: () => void;
|
|
30
|
+
};
|