@meantjs/tools 0.0.0-bootstrap → 0.1.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/CHANGELOG.md +40 -0
- package/README.md +75 -4
- package/package.json +41 -4
- package/src/index.d.mts +164 -0
- package/src/index.mjs +6 -0
- package/src/input.mjs +612 -0
- package/src/layer.mjs +449 -0
- package/src/plan.mjs +459 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog: @meantjs/tools
|
|
2
|
+
|
|
3
|
+
The five Candid Profile library packages (`@meantjs/profile`, `/format`, `/consent`, `/tools`, `/reconcile`) release together under one version and pin each other exactly; a version appears here even when this package did not change. Release steps: `docs/releasing.md` in the repository.
|
|
4
|
+
|
|
5
|
+
## 0.1.0 — 2026-10-28
|
|
6
|
+
|
|
7
|
+
First published version.
|
|
8
|
+
|
|
9
|
+
Published under the `@meantjs` scope (D-051). Before any release, this package was `@candid-core/profile-tools`.
|
|
10
|
+
|
|
11
|
+
- Named inputs (spec §5.3, D-045): where a profile tags a position `named`, a tool takes a name of that set as well as
|
|
12
|
+
a value. The layer reads the set from the canister when the call is prepared (once per intent), resolves the name
|
|
13
|
+
by `resolveName` (`E_NAME_NOT_FOUND`, `E_NAME_AMBIGUOUS`, `E_NAMES_UNREAD`), treats a name and the value it stands
|
|
14
|
+
for as one intent, and shows the name beside the value in previews. `nameEntries` reads a set's entries from a reply of the method
|
|
15
|
+
that lists it, as the layer does.
|
|
16
|
+
- Agent tools from any resolved form: one tool per query method, `prepare_<method>` per update method and `commit`,
|
|
17
|
+
with MCP tool definitions (`toolDefinitions`) and exact input reading in human units (`readToolInput`).
|
|
18
|
+
- `createToolLayer`: a confirmation hook awaited before every send, an injected encoder, call and clock, idempotent
|
|
19
|
+
commits, classified outcomes and an optional reconcile hook after an unknown outcome.
|
|
20
|
+
- No Node built-in: runs in browsers. `displayValue` shows bytes from another realm as it shows this realm's.
|
|
21
|
+
- A `principal` position with no value tag is an input in its textual form (checksum checked, canonical text out),
|
|
22
|
+
so a method that takes a principal gets a tool instead of being skipped.
|
|
23
|
+
- Interfaces beyond the ICRC packs (B2 of `docs/proposals/w9-b3wallet-gaps.md`; the packs' tools do not change): an
|
|
24
|
+
integer position with a `describe` and no value tag is an input of decimal digits; a read-only update
|
|
25
|
+
(`replicated: true`) is a read tool sent as an update call; an update without `idempotency` gets a prepare tool and
|
|
26
|
+
is never sent again after an unknown outcome; outcome keys may pass through record fields; a method with more than
|
|
27
|
+
one argument takes each as an input (`arg0`, `arg1`, ...).
|
|
28
|
+
- `encode` gets a third argument and `call` a field, `args`: the whole argument tuple. Encode it; `value` stays the
|
|
29
|
+
single argument (for a method with more than one, the tuple).
|
|
30
|
+
- A query or read-only update with no answer, or refused, says that nothing was read (it said to commit a preview).
|
|
31
|
+
- More readers (D-050, proposal `docs/proposals/w6-adoption.md` items 2 and 3): a `text` position is an input of the
|
|
32
|
+
text as given, within its `text` tag (`max_len`, `pattern`, `format`) and `range` lengths, and at most 1024
|
|
33
|
+
characters without a tag; a pattern the layer will not run (over 256 characters, not valid with the `u` flag, a
|
|
34
|
+
back reference, a lookaround, a repeated repeating group) skips the method with that reason. A `bool` position is
|
|
35
|
+
`"true"` or `"false"`. A record, variant or `vec` that no value tag reads is one JSON input whose leaves are read by
|
|
36
|
+
their own rules (`{ "tag": …, "value": … }` for a variant case; at most 256 elements in a `vec` and 4096 values in
|
|
37
|
+
one input), with issue paths into it (`$args[0].command.Follow.followees[1].id`) and a matching JSON Schema. A lone
|
|
38
|
+
`null` or `reserved` argument takes no input. An `outcome` with `default` alone classifies every reply of a method
|
|
39
|
+
with any result. Methods skipped before may now get tools; the packs' tools do not change.
|
|
40
|
+
- TypeScript declarations.
|
package/README.md
CHANGED
|
@@ -1,5 +1,76 @@
|
|
|
1
|
-
# @meantjs/tools
|
|
1
|
+
# @meantjs/tools
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Agent tools from any Candid Profile resolved form, for an app or an agent host: MCP tool definitions, input reading in human units, a preview that a person confirms before every send, and the retry and reconcile rules of spec section 7.3. The design and the readings it takes on open questions are in `docs/proposals/w3b-tools-package.md` in the repository; the eval's own tool arm (arm C) is a separate, frozen copy.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @meantjs/tools
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
- **Runs on:** Node 22 or later and browsers: no `node:*` import, no `Buffer`, no `process` (a test bundles it for the
|
|
10
|
+
browser with esbuild).
|
|
11
|
+
- **Module format:** ES modules only, with hand-written TypeScript declarations for every entry.
|
|
12
|
+
- **Spec:** [Candid Profile v0](https://github.com/B3Pay/Meant/blob/main/profile/docs/profile-v0.md). **Licence:** Apache-2.0.
|
|
13
|
+
- **Versions:** the five Candid Profile packages (`@meantjs/profile`, `/format`, `/consent`, `/tools`,
|
|
14
|
+
`/reconcile`) release together under one version and pin each other exactly; in 0.x a minor is breaking,
|
|
15
|
+
and so is any change to generated text ([releasing](https://github.com/B3Pay/Meant/blob/main/profile/docs/releasing.md)).
|
|
16
|
+
|
|
17
|
+
## API
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { createToolLayer, toolDefinitions, readToolInput, buildToolPlans } from "@meantjs/tools";
|
|
21
|
+
|
|
22
|
+
const canisters = [{ id: "ryjl3-tyaaa-aaaaa-aaaba-cai", label: "TKA", refs: { "query:icrc1_decimals": 8, "query:icrc1_symbol": "TKA" } }];
|
|
23
|
+
|
|
24
|
+
toolDefinitions([icrc1Resolved, icrc2Resolved], { canisters }); // MCP tool definitions, sorted by name
|
|
25
|
+
|
|
26
|
+
const layer = createToolLayer({
|
|
27
|
+
resolved: [icrc1Resolved, icrc2Resolved],
|
|
28
|
+
canisters,
|
|
29
|
+
encode: (method, value, args) => encodeArgs(methods.get(method).args, args).bytes, // candid-core's encoder
|
|
30
|
+
call: async ({ canister, method, mode, arg, value, args }) => ({ status: "replied", reply }), // or rejected / unknown
|
|
31
|
+
now: async ({ canister }) => canisterTimeNs, // that canister's clock, bigint ns
|
|
32
|
+
confirm: async (preview) => askThePerson(preview), // awaited before every send
|
|
33
|
+
reconcile: async ({ canister, method, value, stamp }) => ({ status: "absent" }), // optional, consumer rule 1
|
|
34
|
+
});
|
|
35
|
+
await layer.callTool("prepare_icrc1_transfer", { canister_id: canisters[0].id, to: "…", amount: "1.5 TKA" });
|
|
36
|
+
await layer.callTool("commit", { preview_id: "p1" });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- **Tools.** One per query method (named as the method), one `prepare_<method>` per update method, and `commit`, with arm C's rules for which positions are inputs (a value-tagged position is a text input in human units; the idempotency stamp, `auto` defaults and `ui.hidden` positions are filled by the layer). On the pack fixtures the tools, their inputs and their required sets equal `armCToolDefinitions()` (tested). Beyond arm C, for interfaces the packs do not cover (the last five are items 1, 2, 4, 5 and 6 of B2 in `docs/proposals/w9-b3wallet-gaps.md`; the packs meet none of them, so their tools do not change):
|
|
40
|
+
- **A principal.** A `principal` position with no value tag is a text input in the principal's textual form. It has exactly one, with a checksum, so it is read exactly without a profile tag (spec 5.3 has none for it): the input is trimmed, its case ignored, its checksum checked (`E_PRINCIPAL`), and the value is the canonical text. Its schema gives the grammar, two examples and a `pattern`, and a `describe` label goes in front as for any input.
|
|
41
|
+
- **An integer the profile describes.** An integer position (`nat`, `int` and their sized forms) with no value tag but a `describe` is a text input of decimal digits, read as they are: trimmed, no unit, separators, exponent, `+` or leading zeros (`E_SYNTAX`), within the Candid type's bounds (`E_RANGE`); a whole argument of this kind is the input `integer`. The words say what the number is (an action id, a page size), so an integer without a `describe` still has no rule.
|
|
42
|
+
- **A read-only update** (`effect: { kind: "read", irreversible: false, replicated: true }`, D-038) is a read tool named as the method, sent once as an update call (`call` gets `mode: "update"`), so that the answer is certified; no preview, no confirmation. With an idempotency stamp it is skipped.
|
|
43
|
+
- **An update without `idempotency`** gets a prepare tool too, whose description says it has no stamp. Its first send is like any other; after an unknown outcome it is never sent again (spec 5.3: without the tag, an update with unknown outcome is not retried automatically), see `reconcile` below. With an outcome class `retry_new`, which renews a stamp, it is skipped.
|
|
44
|
+
- **Outcome keys through record fields.** A key may pass through a record field to a case (`Ok.state.Failed`). The classifier follows the reply's cases and, at a record, the field the keys read there; a key that names no case or field, ends on a field, or reads another field of a record than an earlier key does (one reply could match both) skips the method.
|
|
45
|
+
- **More than one argument.** Each argument is an input of its own, `arg0`, `arg1` and so on, read by the rules above; an idempotency stamp skips the method.
|
|
46
|
+
- **Text and bool** (D-050). A `text` position is a text input taken as given (not trimmed), checked against its `text` tag: `max_len` and `range` lengths in code points (1024 without either), `pattern` (unanchored, with the `u` flag) and `format` (`url`: an http or https URL; `email`); lone surrogates are refused. A pattern the layer will not run skips the method with the reason: over 256 characters, not valid with the `u` flag, a back reference, a lookaround, or a group that repeats inside a group that repeats (exponential backtracking). A `bool` position is `"true"` or `"false"`.
|
|
47
|
+
- **Nested arguments** (D-050). A record, variant or `vec` that no value tag reads (an account record is still one text input) is one input in JSON: an object for a record (unknown keys refused), `{ "tag": "Case", "value": … }` for a variant case (no `value` for a `null` case), an array for a `vec` (`null` for an element that is none), and each leaf read by its own rule above, as text. At most 256 elements in a `vec` and 4096 values in one input. An issue names its place (`$args[0].command.Follow.followees[1].id`). The input's JSON Schema mirrors the shape (`oneOf` per case, `maxItems`) with each leaf's own schema. A `null` or `reserved` field takes no input, and neither does a lone `null` or `reserved` argument. A recursive position, or a field or case named by a number, still skips the method.
|
|
48
|
+
- **An outcome with `default` alone** (D-049): every reply of the method is that class, whatever its result; outcome keys still need a method with one result.
|
|
49
|
+
|
|
50
|
+
A method with no rule (including a query with more than one result) is skipped and reported in `buildToolPlans(resolved).skipped`, not thrown on; so is every method whose tool name is `commit` or is another method's tool name, every method with an argument field named `canister_id` (the canister selector's input), and every update whose stamp is the whole argument rather than a field (a required stamp field is filled like an optional one). The stamp and the outcome keys are relpaths, matched by the position they resolve to: `(0).Ok`, `.Ok` and `Ok` are one key.
|
|
51
|
+
- **Input words.** Each input's description is the format library's `Formatter.describe()`, with the canisters' decimals and symbol filled in when they agree (and its regular-expression `pattern` for amounts and subaccounts). When the canisters disagree the words give the grammar without the numbers.
|
|
52
|
+
- **Reading.** `readToolInput(plan, input, { refs, now })` returns `{ value, identity }` or `{ issues: [{ param, path, code, message }] }`, every issue at once, with the format library's codes. `value` is candid-core's domain shape (`T | null` opts, `bigint` for nat/int/nat64/int64, `Uint8Array` blobs, principals as text), so `encodeArgs` takes it: the single argument, or for a method with more than one the argument tuple.
|
|
53
|
+
- **Sending.** `encode(method, value, args)` is injected, so the bytes are the host's encoder's (candid-core's `encodeArgs`, as ic-reactor sends; a test checks every send against it). `args` is the whole argument tuple (empty, one argument, or several); encode it. `value` is the single argument, kept for hosts that encode `[value]`; for a method with more than one argument it is the tuple again, so such a host's `encodeArgs(types, [value])` is refused (`invalid_length`, observed with candid-core) rather than encoded wrongly. `call` receives the bytes, `mode` (`"query"`, or `"update"` for a commit and for a read-only update), and the `value` and `args` they encode. Results are `{ content, structuredContent, isError? }`: structured data with a short text beside it.
|
|
54
|
+
- **Confirmation.** `confirm(preview)` is awaited before every send, including a re-send. There is no auto-approve default: a form with an update method needs it. A decline sends nothing and answers `declined`.
|
|
55
|
+
|
|
56
|
+
## Outcomes
|
|
57
|
+
|
|
58
|
+
| Outcome | When | The intent |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| `declined` | `confirm` said no | settled if it had no attempt, else unchanged; after an unknown outcome the result also carries `earlier_outcome: "unknown"` (the earlier attempt may have executed). A stamp declined before any send sets no floor for later stamps of the same arguments, and is never handed out again |
|
|
61
|
+
| `success`, `already_done` | the class of the reply | done: a later commit answers `already_done` without sending |
|
|
62
|
+
| `retry_same` | the class of the reply | unchanged: handed back, nothing sleeps here. The third one stops the intent (consumer rule 3): `reconcile` after an unknown outcome, else `fail` with `retries_spent`, settled |
|
|
63
|
+
| `retry_new` | the class of the reply | a new preview with a new stamp, relative times resolved again; the old preview is retired. A `CreatedInFuture` re-stamp is capped by its `ledger_time` (only that case, matched by its name) |
|
|
64
|
+
| `fix_input`, `fail` | the class of the reply | settled |
|
|
65
|
+
| `not_executed` | reject code 1, 2 or 3 | unchanged |
|
|
66
|
+
| `unknown` | for an update with an idempotency stamp: no answer, or any reject code other than 1 to 3 (4, 5, 6 and any unlisted code; or any reject from `aaaaa-aa`); a `call` that throws is answered the same (`message` is the error's) | unknown: commit again sends the same bytes; preparing it again returns the same preview with outcome `unknown`, as does any later preparation of an intent that had one |
|
|
67
|
+
| `reconcile` | after an unknown outcome, any answer but success, already_done or retry_same (retry_same resends the same bytes and stamp, so the ledger's deduplication answers Duplicate within the window and TooOld after it); or a third reject 4/5 of the intent. For an update without an idempotency stamp, at once, in place of `unknown`: the finding is `unreadable` with the reason, and the `reconcile` hook is not called, since there is no stamp to match | held until `reconcile` finds the original (then done) or reports it absent (then settled); without a stamp, held: no commit or preparation of it sends again |
|
|
68
|
+
|
|
69
|
+
## Tests
|
|
70
|
+
|
|
71
|
+
`npm test -w packages/tools`: definitions against arm C's, input reading against candid-core's encoder, every outcome row above against a scripted ledger, concurrent preparations and commits, a `call` that throws, the hand-written types against the runtime exports, and the browser bundle. **Observed (2026-10-14, while writing them):** each guard in `src/layer.mjs` was removed once and its test failed; the four regression tests from the review fail on the earlier code. `test/interface-rules.test.mjs` runs the B2 rules on a wallet-like form; **observed (2026-10-24):** each of its ten guards (the update call for a read-only update, the hold after an unknown outcome, a reject 4 and the hook's skip without a stamp, the `describe` requirement, the two record-key checks, the argument tuple, the `retry_new` and stamped-read skips) was removed once and a test failed. `test/readers.test.mjs` runs the D-050 readers, and every value they prepare is encoded by candid-core; **observed (2026-10-27):** removing the bare `{ tag }` for a `null` case, the repeat check on patterns, or the lone-surrogate check made a test fail.
|
|
72
|
+
|
|
73
|
+
## Not here
|
|
74
|
+
|
|
75
|
+
- The demo's agent tools are not switched to this package yet: its tools also read address-book names and choose the calling identity per call (the payer question of #10), and its write engine is the app's.
|
|
76
|
+
- Arm C keeps its own code; rebuilding it on this package before the freeze, with re-pinned hashes, is recommended in the proposal (D-015 reruns nothing while no run has started).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meantjs/tools",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent tools from any Candid Profile resolved form: tool definitions, exact input reading, and a tool layer with a confirmation hook, an injected encoder, call and clock. Browser-safe.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -9,8 +9,45 @@
|
|
|
9
9
|
"directory": "profile/packages/tools"
|
|
10
10
|
},
|
|
11
11
|
"homepage": "https://github.com/B3Pay/Meant/tree/main/profile/packages/tools#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/B3Pay/Meant/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"candid",
|
|
17
|
+
"internet-computer",
|
|
18
|
+
"icp",
|
|
19
|
+
"agent",
|
|
20
|
+
"mcp",
|
|
21
|
+
"tools",
|
|
22
|
+
"wallet"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"main": "./src/index.mjs",
|
|
27
|
+
"types": "./src/index.d.mts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./src/index.d.mts",
|
|
31
|
+
"default": "./src/index.mjs"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
12
35
|
"files": [
|
|
13
|
-
"
|
|
36
|
+
"src",
|
|
37
|
+
"CHANGELOG.md",
|
|
14
38
|
"LICENSE"
|
|
15
|
-
]
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "node --test \"test/*.test.mjs\""
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@meantjs/format": "0.1.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@candid-core/schema": "0.5.0",
|
|
51
|
+
"esbuild": "0.28.2"
|
|
52
|
+
}
|
|
16
53
|
}
|
package/src/index.d.mts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Types for @meantjs/tools. Hand-written; the source is plain ES modules (src/*.mjs).
|
|
2
|
+
|
|
3
|
+
import type { NameEntry } from "@meantjs/format";
|
|
4
|
+
|
|
5
|
+
/** A candid-profile-resolved v0 document (docs/profile-v0.md section 10). */
|
|
6
|
+
export interface ResolvedForm {
|
|
7
|
+
format: "candid-profile-resolved";
|
|
8
|
+
format_version: 0;
|
|
9
|
+
methods: Record<string, unknown>;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Canister {
|
|
14
|
+
/** Principal text of the canister. */
|
|
15
|
+
id: string;
|
|
16
|
+
/** A short name for people and models, such as the token's symbol. */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** The canister's dynamic tag values, keyed by their `from`: `{ "query:icrc1_decimals": 8 }`. */
|
|
19
|
+
refs?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface JsonSchemaString {
|
|
23
|
+
type: "string";
|
|
24
|
+
description: string;
|
|
25
|
+
enum?: string[];
|
|
26
|
+
pattern?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ToolDefinition {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
inputSchema: {
|
|
33
|
+
$schema: string;
|
|
34
|
+
type: "object";
|
|
35
|
+
properties: Record<string, JsonSchemaString>;
|
|
36
|
+
required: string[];
|
|
37
|
+
additionalProperties: false;
|
|
38
|
+
};
|
|
39
|
+
annotations: { destructiveHint: boolean; openWorldHint: boolean; readOnlyHint: boolean; title: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ToolParam {
|
|
43
|
+
name: string;
|
|
44
|
+
node: unknown;
|
|
45
|
+
optional: boolean;
|
|
46
|
+
/** `$args[0].amount`, or `$args[0]` for a whole argument (`$args[1]` for the second of several). */
|
|
47
|
+
path: string;
|
|
48
|
+
}
|
|
49
|
+
// A param whose node is a record, variant or vec with no value tag is one JSON input (D-050): an object, a
|
|
50
|
+
// `{ tag, value }` object, or an array, whose leaves are text read by their own rules.
|
|
51
|
+
|
|
52
|
+
export interface ToolPlan {
|
|
53
|
+
name: string;
|
|
54
|
+
kind: "read" | "prepare";
|
|
55
|
+
method: string;
|
|
56
|
+
/** The method's mode in the resolved form; a read-only update is `"update"` with `kind: "read"`. */
|
|
57
|
+
mode: string;
|
|
58
|
+
/** A read sent as an update call, so that its answer is certified (`effect.replicated`, D-038). */
|
|
59
|
+
replicated: boolean;
|
|
60
|
+
title: string;
|
|
61
|
+
effect: { kind: string; irreversible: boolean; replicated?: boolean };
|
|
62
|
+
/** The method's arguments, in order. */
|
|
63
|
+
argNodes: unknown[];
|
|
64
|
+
params: ToolParam[];
|
|
65
|
+
/** The argument record's fields, or with more than one argument the arguments (`arg0`, `arg1`, ...). */
|
|
66
|
+
/** `none`: a `null` or `reserved` position, filled with null and never asked for. */
|
|
67
|
+
fields: { name: string; node: unknown; role: "param" | "auto" | "stamp" | "hidden" | "none"; optional: boolean }[];
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface InputIssue {
|
|
72
|
+
param: string;
|
|
73
|
+
path: string;
|
|
74
|
+
/** The format library's codes, plus E_INPUT, E_UNKNOWN_PARAM, E_MISSING and E_TYPE. Inside a JSON input, `path`
|
|
75
|
+
* names the place (`$args[0].command.Follow.followees[1].id`). */
|
|
76
|
+
code: string;
|
|
77
|
+
message: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const COMMIT_TOOL: "commit";
|
|
81
|
+
export function prepareToolName(method: string): string;
|
|
82
|
+
export function resolvedMethods(resolved: ResolvedForm | ResolvedForm[]): [string, unknown][];
|
|
83
|
+
export function buildToolPlans(resolved: ResolvedForm | ResolvedForm[]): { plans: Map<string, ToolPlan>; skipped: { method: string; reason: string }[] };
|
|
84
|
+
export function toolDefinitions(resolved: ResolvedForm | ResolvedForm[], options?: { canisters?: Canister[] }): ToolDefinition[];
|
|
85
|
+
|
|
86
|
+
export function readToolInput(
|
|
87
|
+
plan: ToolPlan,
|
|
88
|
+
input: unknown,
|
|
89
|
+
context?: { refs?: Record<string, unknown>; now?: bigint; names?: Record<string, NameEntry[]> },
|
|
90
|
+
): { value: unknown; identity: [string, unknown][] } | { issues: InputIssue[] };
|
|
91
|
+
export function toCandid(node: unknown, value: unknown): unknown;
|
|
92
|
+
export function displayValue(node: unknown, value: unknown, refs?: Record<string, unknown>, names?: Record<string, NameEntry[]>): unknown;
|
|
93
|
+
/** The entries of a names set (spec 5.3, D-045) in a reply of the method that lists it; undefined without the set or a list. */
|
|
94
|
+
export function nameEntries(resolved: unknown, set: string, reply: unknown): NameEntry[] | undefined;
|
|
95
|
+
export function classifyReply(plan: ToolPlan, reply: unknown): { cls: string; path: string[]; node: unknown; value: unknown };
|
|
96
|
+
|
|
97
|
+
export type CallResult =
|
|
98
|
+
| { status: "replied"; reply: unknown }
|
|
99
|
+
| { status: "rejected"; reject_code: number; message: string }
|
|
100
|
+
| { status: "unknown"; message: string };
|
|
101
|
+
|
|
102
|
+
export interface Preview {
|
|
103
|
+
preview_id: string;
|
|
104
|
+
method: string;
|
|
105
|
+
canister: string;
|
|
106
|
+
title: string;
|
|
107
|
+
effect: { kind: string; irreversible: boolean; replicated?: boolean };
|
|
108
|
+
/** The arguments as `displayValue` shows them; an array for a method with more than one. */
|
|
109
|
+
arguments: unknown;
|
|
110
|
+
arg_hex: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type ReconcileFinding =
|
|
114
|
+
| { status: "found"; block?: bigint | string }
|
|
115
|
+
| { status: "absent" | "not_yet" }
|
|
116
|
+
| { status: "unreadable"; reason?: string };
|
|
117
|
+
|
|
118
|
+
export type Outcome =
|
|
119
|
+
| "declined"
|
|
120
|
+
| "success"
|
|
121
|
+
| "already_done"
|
|
122
|
+
| "retry_same"
|
|
123
|
+
| "retry_new"
|
|
124
|
+
| "fix_input"
|
|
125
|
+
| "fail"
|
|
126
|
+
| "not_executed"
|
|
127
|
+
| "unknown"
|
|
128
|
+
| "reconcile";
|
|
129
|
+
|
|
130
|
+
export interface ToolResult {
|
|
131
|
+
content: { type: "text"; text: string }[];
|
|
132
|
+
/** `preview` is there when a prepare tool answers `prepared` (and when an intent is handed back). */
|
|
133
|
+
structuredContent: { status?: string; outcome?: Outcome; earlier_outcome?: "unknown"; preview?: Preview; [key: string]: unknown };
|
|
134
|
+
isError?: true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface ToolLayerConfig {
|
|
138
|
+
resolved: ResolvedForm | ResolvedForm[];
|
|
139
|
+
canisters: Canister[];
|
|
140
|
+
/**
|
|
141
|
+
* The method's arguments, in candid-core domain shapes, to the bytes sent: candid-core's `encodeArgs` of `args`, the
|
|
142
|
+
* whole argument tuple. `value` is the single argument (for a method with more than one, the tuple again).
|
|
143
|
+
*/
|
|
144
|
+
encode(method: string, value: unknown, args: unknown[]): Uint8Array | Promise<Uint8Array>;
|
|
145
|
+
/**
|
|
146
|
+
* Sends `arg` exactly, as a query or an update call (`mode`; a read-only update is `"update"`); `reply` is the
|
|
147
|
+
* decoded first result value in candid-core's domain shape (a variant is `{ tag, value }`).
|
|
148
|
+
*/
|
|
149
|
+
call(req: { canister: string; method: string; mode: string; arg: Uint8Array; value: unknown; args: unknown[] }): Promise<CallResult>;
|
|
150
|
+
/** The clock of the canister named, ns since the Unix epoch (canisters on different subnets keep different times). */
|
|
151
|
+
now(o: { canister: string }): Promise<bigint>;
|
|
152
|
+
/** Awaited before every send. Required when the form has an update method. */
|
|
153
|
+
confirm?(preview: Preview): Promise<boolean | { approved: boolean }>;
|
|
154
|
+
/** Consumer rule 1: look for the original in the canister's history. */
|
|
155
|
+
reconcile?(o: { canister: string; method: string; value: unknown; stamp: bigint; preview: Preview }): Promise<ReconcileFinding>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface ToolLayer {
|
|
159
|
+
listTools(): ToolDefinition[];
|
|
160
|
+
skipped: { method: string; reason: string }[];
|
|
161
|
+
callTool(name: string, input: unknown): Promise<ToolResult>;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function createToolLayer(config: ToolLayerConfig): ToolLayer;
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @meantjs/tools: agent tools from any resolved form, browser-safe (#16).
|
|
2
|
+
// Arm C (packages/mcp-adapters) stays the eval arm with its frozen texts; this is the product tool layer.
|
|
3
|
+
|
|
4
|
+
export { COMMIT_TOOL, buildToolPlans, prepareToolName, resolvedMethods, toolDefinitions } from "./plan.mjs";
|
|
5
|
+
export { displayValue, readToolInput, toCandid } from "./input.mjs";
|
|
6
|
+
export { classifyReply, createToolLayer, nameEntries } from "./layer.mjs";
|