@agentable-ui/lang 0.1.0 → 0.2.1
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/README.md +27 -12
- package/dist/index.js +16 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# @agentable-ui/lang
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parse, stream, and generate prompts for model-written UI programs. Each line names a component and its arguments. The package is framework-free and makes no model or network calls. Use [genui](../genui) to render the result.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @agentable-ui/lang @agentable-ui/core
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
+
## Read a program
|
|
10
|
+
|
|
9
11
|
```txt
|
|
10
12
|
root = Stack([stats, note])
|
|
11
13
|
stats = StatGroup([rev, orders])
|
|
@@ -14,6 +16,10 @@ orders = Stat("Orders", 140, "+12", "up")
|
|
|
14
16
|
note = Callout("On track", "Ahead of target by 4%.", "success")
|
|
15
17
|
```
|
|
16
18
|
|
|
19
|
+
## Parse or stream a response
|
|
20
|
+
|
|
21
|
+
The built-in catalog comes from `@agentable-ui/ui`; install it separately if you use this example. `createLibrary` also accepts your own catalog, without React.
|
|
22
|
+
|
|
17
23
|
```ts
|
|
18
24
|
import { createLibrary } from "@agentable-ui/lang";
|
|
19
25
|
import { uiCatalog } from "@agentable-ui/ui";
|
|
@@ -21,30 +27,33 @@ import { uiCatalog } from "@agentable-ui/ui";
|
|
|
21
27
|
const library = createLibrary(uiCatalog);
|
|
22
28
|
const system = library.prompt({ tools: [{ name: "refund_order", description: "Refund a paid order." }] });
|
|
23
29
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// Your application sends system with the user's message to a model.
|
|
31
|
+
// Parse a finished response:
|
|
32
|
+
const result = library.parse('root = Text("Hello")');
|
|
33
|
+
console.log(result.root, result.errors);
|
|
34
|
+
|
|
35
|
+
// Or feed successive chunks to the streaming parser:
|
|
36
|
+
const parser = library.stream();
|
|
37
|
+
parser.push('root = Text("Hel');
|
|
38
|
+
parser.push('lo")\n');
|
|
39
|
+
const finished = parser.push("", true); // flush the final partial line
|
|
32
40
|
```
|
|
33
41
|
|
|
34
|
-
##
|
|
42
|
+
## Syntax at a glance
|
|
35
43
|
|
|
36
44
|
- One statement per line: `name = Component(args)`. The entry is `root`. Forward references are fine; the last definition of a name wins.
|
|
37
45
|
- Arguments are positional in the component's declared prop order, or named: `Callout("On track", text: "Ahead by 4%.")`. `null` skips an optional positional argument.
|
|
38
46
|
- Children are a list of statement names, wherever they sit: `Stack([a, b])`, `Card("Title", [a, b])`, or `children: [a, b]`. A name may be used once.
|
|
39
47
|
- Strings in double quotes, lists in `[]`, objects in `{key: value}`, numbers, `true`, `false`, `null`. `#` starts a comment.
|
|
40
48
|
- State: `$data.x` reads app data (read-only); `$ui.x` is state the interface owns. A path given as an argument binds that prop: `rows: $data.paid`.
|
|
49
|
+
- Read paths can filter and count lists: `$data.items[done=true]`, `$data.items[status!="archived"]`, `$data.items[done=true].length`. A missing boolean field counts as `false`. Filters are read-only; actions can't write through them.
|
|
41
50
|
- `when: $ui.selected` shows a node while the path is truthy, `when: !$ui.done` while falsy.
|
|
42
51
|
- Events are named arguments holding actions: `press: @Set($ui.selected, $event)` or a list `[@Set(...), @Tool(...)]`. `$event` is the event's value, `$event.id` a field of it.
|
|
43
52
|
- Actions: `@Set(path, value)`, `@Merge(path, object)`, `@Push(path, value)`, `@Remove(path)`, `@Increment(path, by?)`, `@Toggle(path)`, `@Tool(name, args?, into?)`, `@Say(text)`. Written paths must be `$ui.*`.
|
|
44
53
|
|
|
45
54
|
A bad line is dropped with a structured error and the rest still renders. Unknown components, unknown arguments, excess arguments and bad action arguments are reported with codes (`unknown-component`, `unknown-arg`, `excess-args`, `bad-arg`, `missing-required`, `parse-failed`, `cycle`, `reused-node`, `no-root`) a host can feed back to the model.
|
|
46
55
|
|
|
47
|
-
## API
|
|
56
|
+
## API reference
|
|
48
57
|
|
|
49
58
|
- `createLibrary(catalog, { root? })` — a catalog is `{ Name: { description, props, childrenProp?, events? } }`; `props` is JSON Schema or any Standard Schema (zod 4, valibot 1). The `uiCatalog` from `@agentable-ui/ui` works as is.
|
|
50
59
|
- `library.prompt({ preamble?, rules?, examples?, tools?, data?, interactive? })` — the system prompt: rules, one signature line per component, tools, an example.
|
|
@@ -55,4 +64,10 @@ A bad line is dropped with a structured error and the rest still renders. Unknow
|
|
|
55
64
|
|
|
56
65
|
The node tree it produces is the same `{ component, props, bind, on, when, children }` shape `@agentable-ui/genui` validates and renders, so a program and a JSON tree are interchangeable everywhere, including the `present` tool.
|
|
57
66
|
|
|
58
|
-
|
|
67
|
+
## Rendering and error handling
|
|
68
|
+
|
|
69
|
+
Parsing returns a tree and structured errors; it does not execute application tools. Pass the accumulated program to `Renderer` from `@agentable-ui/genui` to display it. In Lang, `$event` is the event payload; the JSON tree representation uses `{{value}}`.
|
|
70
|
+
|
|
71
|
+
Handle `errors` and `unresolved` separately. Forward references can remain unresolved while streaming. Pass `done: true` on the final push so the parser processes the final line even without a newline. Token figures are estimates, not a guaranteed reduction compared with JSON.
|
|
72
|
+
|
|
73
|
+
MIT licensed.
|
package/dist/index.js
CHANGED
|
@@ -207,10 +207,17 @@ var LineParser = class {
|
|
|
207
207
|
continue;
|
|
208
208
|
}
|
|
209
209
|
if (this.src[this.i] === "[") {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
const rest = this.src.slice(this.i);
|
|
211
|
+
const m = /^\[(\d+)\]/.exec(rest);
|
|
212
|
+
if (m) {
|
|
213
|
+
this.i += m[0].length;
|
|
214
|
+
path += `[${m[1]}]`;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const f = /^\[\s*([A-Za-z_][A-Za-z0-9_]*)\s*(!?=)\s*("[^"\]]*"|[A-Za-z0-9_.-]+)\s*\]/.exec(rest);
|
|
218
|
+
if (!f) throw new Error(`Use [0] or [field=value] in $${path}[...].`);
|
|
219
|
+
this.i += f[0].length;
|
|
220
|
+
path += `[${f[1]}${f[2]}${f[3]}]`;
|
|
214
221
|
continue;
|
|
215
222
|
}
|
|
216
223
|
return {
|
|
@@ -643,7 +650,10 @@ function evaluate(statements, signatures) {
|
|
|
643
650
|
out[p.name] = x.path;
|
|
644
651
|
continue;
|
|
645
652
|
}
|
|
646
|
-
if (x.kind === "null")
|
|
653
|
+
if (x.kind === "null") {
|
|
654
|
+
if (p.kind === "value") out[p.name] = null;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
647
657
|
const v = template(x, st, `${where} @${name}`);
|
|
648
658
|
if (v === void 0) continue;
|
|
649
659
|
if (p.kind === "string" && typeof v !== "string") {
|
|
@@ -719,7 +729,7 @@ function buildPrompt(lib, opts = {}) {
|
|
|
719
729
|
"Only the components listed below exist. Do not invent components, arguments or events.",
|
|
720
730
|
"Lines starting with # are comments. Do not wrap the program in code fences or add prose before or after it."
|
|
721
731
|
];
|
|
722
|
-
if (interactive) rules.push("State: `$data.x` reads app data (read-only); `$ui.x` is interface state you own. Pass a path as an argument to bind that prop, e.g. `rows: $data.paid`.", "`when: $ui.selected` shows a node only while that path is truthy; `when: !$ui.done` while it is falsy.", "Events take actions: `press: @Set($ui.selected, $event)` or a list `[@Set(...), @Tool(...)]`. `$event` is the value the event fired with; `$event.id` a field of it.", "Actions: @Set(path, value) @Merge(path, object) @Push(path, value) @Remove(path) @Increment(path, by?) @Toggle(path) @Tool(name, args?, into?) @Say(text). Paths written by actions must be $ui.*.");
|
|
732
|
+
if (interactive) rules.push("State: `$data.x` reads app data (read-only); `$ui.x` is interface state you own. Pass a path as an argument to bind that prop, e.g. `rows: $data.paid`.", "Read paths can filter lists and count them: `$data.items[done=true]`, `$data.items[status!=\"archived\"]`, `$data.items[done=true].length`. Filters are read-only; actions cannot write through them.", "`when: $ui.selected` shows a node only while that path is truthy; `when: !$ui.done` while it is falsy.", "Events take actions: `press: @Set($ui.selected, $event)` or a list `[@Set(...), @Tool(...)]`. `$event` is the value the event fired with; `$event.id` a field of it.", "Actions: @Set(path, value) @Merge(path, object) @Push(path, value) @Remove(path) @Increment(path, by?) @Toggle(path) @Tool(name, args?, into?) @Say(text). Paths written by actions must be $ui.*.");
|
|
723
733
|
rules.push(...opts.rules ?? []);
|
|
724
734
|
for (const r of rules) out.push(`- ${r}`);
|
|
725
735
|
out.push("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentable-ui/lang",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Agentable Lang: a compact, streaming-first language for model-generated UI. Parser, streaming parser, prompt generation and serializer, framework-free.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@agentable-ui/core": "^0.
|
|
39
|
+
"@agentable-ui/core": "^0.2.0"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsdown",
|