@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.
Files changed (3) hide show
  1. package/README.md +27 -12
  2. package/dist/index.js +16 -6
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # @agentable-ui/lang
2
2
 
3
- Agentable Lang: a compact, streaming-first language for model-generated UI. One node per line, rendered as it arrives, about half the tokens of the same tree as JSON. Framework-free; no model calls, no network.
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
- // Server: send `system` with the user's message to any model and stream the text back.
25
- // Client: <Renderer components={uiCatalog} response={text} isStreaming /> from @agentable-ui/genui,
26
- // or parse it yourself:
27
- const sp = library.stream();
28
- for await (const chunk of modelStream) {
29
- const { root, errors } = sp.push(chunk);
30
- }
31
- sp.push("", true); // done
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
- ## The language
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
- MIT
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 m = /^\[(\d+)\]/.exec(this.src.slice(this.i));
211
- if (!m) throw new Error(`Only numeric indexes are allowed in $${path}[...].`);
212
- this.i += m[0].length;
213
- path += `[${m[1]}]`;
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") continue;
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.0",
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.1.1"
39
+ "@agentable-ui/core": "^0.2.0"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsdown",