@hanzo/ui 8.27.3 → 8.27.5

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 (69) hide show
  1. package/dist/backdrop/Backdrop.cjs +420 -0
  2. package/dist/backdrop/Backdrop.d.ts +53 -0
  3. package/dist/backdrop/Backdrop.d.ts.map +1 -0
  4. package/dist/backdrop/Backdrop.js +418 -0
  5. package/dist/backdrop/Backdrop.js.map +1 -0
  6. package/dist/backdrop/index.cjs +50 -0
  7. package/dist/backdrop/index.d.ts +29 -0
  8. package/dist/backdrop/index.d.ts.map +1 -0
  9. package/dist/backdrop/index.js +29 -0
  10. package/dist/backdrop/index.js.map +1 -0
  11. package/dist/backdrop/scene.cjs +439 -0
  12. package/dist/backdrop/scene.d.ts +235 -0
  13. package/dist/backdrop/scene.d.ts.map +1 -0
  14. package/dist/backdrop/scene.js +423 -0
  15. package/dist/backdrop/scene.js.map +1 -0
  16. package/dist/backends/gui/select.cjs +38 -27
  17. package/dist/backends/gui/select.d.ts +14 -5
  18. package/dist/backends/gui/select.d.ts.map +1 -1
  19. package/dist/backends/gui/select.js +42 -31
  20. package/dist/backends/gui/select.js.map +1 -1
  21. package/dist/backends/gui/tooltip.cjs +22 -3
  22. package/dist/backends/gui/tooltip.d.ts +12 -4
  23. package/dist/backends/gui/tooltip.d.ts.map +1 -1
  24. package/dist/backends/gui/tooltip.js +22 -3
  25. package/dist/backends/gui/tooltip.js.map +1 -1
  26. package/dist/chat/Chat.cjs +23 -1
  27. package/dist/chat/Chat.d.ts +6 -3
  28. package/dist/chat/Chat.d.ts.map +1 -1
  29. package/dist/chat/Chat.js +23 -1
  30. package/dist/chat/Chat.js.map +1 -1
  31. package/dist/chat/Inspector.cjs +51 -0
  32. package/dist/chat/Inspector.d.ts +49 -0
  33. package/dist/chat/Inspector.d.ts.map +1 -0
  34. package/dist/chat/Inspector.js +49 -0
  35. package/dist/chat/Inspector.js.map +1 -0
  36. package/dist/chat/Message.cjs +3 -1
  37. package/dist/chat/Message.d.ts +3 -1
  38. package/dist/chat/Message.d.ts.map +1 -1
  39. package/dist/chat/Message.js +3 -1
  40. package/dist/chat/Message.js.map +1 -1
  41. package/dist/chat/Parts.cjs +159 -0
  42. package/dist/chat/Parts.d.ts +186 -0
  43. package/dist/chat/Parts.d.ts.map +1 -0
  44. package/dist/chat/Parts.js +153 -0
  45. package/dist/chat/Parts.js.map +1 -0
  46. package/dist/chat/Persona.cjs +34 -0
  47. package/dist/chat/Persona.d.ts +37 -0
  48. package/dist/chat/Persona.d.ts.map +1 -0
  49. package/dist/chat/Persona.js +30 -0
  50. package/dist/chat/Persona.js.map +1 -0
  51. package/dist/chat/Preview.cjs +42 -0
  52. package/dist/chat/Preview.d.ts +36 -0
  53. package/dist/chat/Preview.d.ts.map +1 -0
  54. package/dist/chat/Preview.js +40 -0
  55. package/dist/chat/Preview.js.map +1 -0
  56. package/dist/chat/directive.cjs +272 -0
  57. package/dist/chat/directive.d.ts +167 -0
  58. package/dist/chat/directive.d.ts.map +1 -0
  59. package/dist/chat/directive.js +266 -0
  60. package/dist/chat/directive.js.map +1 -0
  61. package/dist/chat/index.cjs +59 -4
  62. package/dist/chat/index.d.ts +31 -3
  63. package/dist/chat/index.d.ts.map +1 -1
  64. package/dist/chat/index.js +31 -3
  65. package/dist/chat/index.js.map +1 -1
  66. package/dist/gallery.html +4 -4
  67. package/dist/styles.css +3 -3
  68. package/dist/theme.css +3 -3
  69. package/package.json +7 -1
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ /**
4
+ * Persona — a face on the canvas, and what it is doing.
5
+ *
6
+ * A persona is not a new engine. It is a MAPPING: an emotion names a clip, the
7
+ * clip goes to `Backdrop`'s `clips` mode, and that mode's preloaded crossfade
8
+ * does the switching it already does — one live frame over another, never a
9
+ * flash of black. So a mood change is a prop change, and there is exactly one
10
+ * piece of machinery playing video in this package rather than two.
11
+ *
12
+ * The mapping is the whole component, and it is deliberately thin. What an
13
+ * emotion MEANS — when a persona is listening, when it is thinking — belongs to
14
+ * whatever is driving the conversation; a component that guessed would be
15
+ * guessing from the one place with the least information.
16
+ *
17
+ * `idle` is required and every other emotion is optional, so a persona with one
18
+ * clip is a legal persona and adding a mood is adding a file. `clip` is pure and
19
+ * exported, which is what lets a surface preload the next mood, or test the
20
+ * mapping, without mounting anything.
21
+ */
22
+ import { Backdrop } from '../backdrop/Backdrop.js';
23
+ /** The clip for this emotion, falling back to idle. */
24
+ export const clip = (scenes, emotion) => scenes[emotion] ?? scenes.idle;
25
+ export function Persona({ scenes, emotion = 'idle', ...props }) {
26
+ // One clip, so it loops itself rather than ending and handing over — a mood
27
+ // holds until something changes it, which is what a mood is.
28
+ return _jsx(Backdrop, { ...props, mode: "clips", clips: [clip(scenes, emotion)] });
29
+ }
30
+ //# sourceMappingURL=Persona.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Persona.js","sourceRoot":"","sources":["../../src/chat/Persona.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,QAAQ,EAAsB,MAAM,sBAAsB,CAAA;AAiBnE,uDAAuD;AACvD,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,OAAgB,EAAU,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAA;AAQhG,MAAM,UAAU,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,GAAG,KAAK,EAAgB;IAC1E,4EAA4E;IAC5E,6DAA6D;IAC7D,OAAO,KAAC,QAAQ,OAAK,KAAK,EAAE,IAAI,EAAC,OAAO,EAAC,KAAK,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAI,CAAA;AAC7E,CAAC"}
@@ -0,0 +1,42 @@
1
+ 'use client';"use strict";
2
+ 'use client';
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Preview = Preview;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ /**
7
+ * Preview — an interface the model wrote, shown but not run.
8
+ *
9
+ * Generative UI arrives as markup, and markup from a model is untrusted input
10
+ * that happens to be a document. Rendering it into the host page is
11
+ * cross-site scripting with extra steps; refusing to render it at all leaves
12
+ * the feature as a paragraph of HTML source. Neither is the answer.
13
+ *
14
+ * The answer is `sandbox=""`. An empty allow-list is the STRICTEST setting the
15
+ * attribute has, not a lax one: no scripts, no same-origin, no forms, no
16
+ * navigation, no popups, no pointer lock. The document lays out and paints —
17
+ * real CSS, real fonts, real layout, a real screenshot — and can do nothing
18
+ * else. That is exactly the shape of a mockup, so nothing is lost by taking
19
+ * away the half that is dangerous.
20
+ *
21
+ * This is why `Parts` can draw an `artifact` and still refuses a `uiResource`.
22
+ * They are not the same risk: a `uiResource` is a URL, and loading one pulls a
23
+ * third-party origin into the page over the network. Inline markup with
24
+ * scripting off reaches nothing and runs nothing.
25
+ *
26
+ * Web only, like `Backdrop` — it is an `<iframe>`, and there is no such thing
27
+ * off the web. A surface that runs on native sends artifacts with no `markup`
28
+ * and gets `ArtifactCard`, which is where that decision already lived.
29
+ */
30
+ const gui_1 = require("@hanzo/gui");
31
+ const slot_1 = require("../backends/gui/slot.cjs");
32
+ function Preview({ markup, title, height = 320, ...props }) {
33
+ const named = title ?? 'Preview';
34
+ return ((0, jsx_runtime_1.jsxs)(gui_1.YStack, { ...(0, slot_1.slot)('preview'), width: "100%", maxW: 720, rounded: "$4", overflow: "hidden", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", ...props, children: [title ? ((0, jsx_runtime_1.jsx)(gui_1.XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: (0, jsx_runtime_1.jsx)(gui_1.SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: title }) })) : null, (0, jsx_runtime_1.jsx)("iframe", { title: named, srcDoc: markup, sandbox: "", referrerPolicy: "no-referrer", style: {
35
+ display: 'block',
36
+ width: '100%',
37
+ height,
38
+ border: 0,
39
+ background: '#fff',
40
+ colorScheme: 'light',
41
+ } })] }));
42
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Preview — an interface the model wrote, shown but not run.
3
+ *
4
+ * Generative UI arrives as markup, and markup from a model is untrusted input
5
+ * that happens to be a document. Rendering it into the host page is
6
+ * cross-site scripting with extra steps; refusing to render it at all leaves
7
+ * the feature as a paragraph of HTML source. Neither is the answer.
8
+ *
9
+ * The answer is `sandbox=""`. An empty allow-list is the STRICTEST setting the
10
+ * attribute has, not a lax one: no scripts, no same-origin, no forms, no
11
+ * navigation, no popups, no pointer lock. The document lays out and paints —
12
+ * real CSS, real fonts, real layout, a real screenshot — and can do nothing
13
+ * else. That is exactly the shape of a mockup, so nothing is lost by taking
14
+ * away the half that is dangerous.
15
+ *
16
+ * This is why `Parts` can draw an `artifact` and still refuses a `uiResource`.
17
+ * They are not the same risk: a `uiResource` is a URL, and loading one pulls a
18
+ * third-party origin into the page over the network. Inline markup with
19
+ * scripting off reaches nothing and runs nothing.
20
+ *
21
+ * Web only, like `Backdrop` — it is an `<iframe>`, and there is no such thing
22
+ * off the web. A surface that runs on native sends artifacts with no `markup`
23
+ * and gets `ArtifactCard`, which is where that decision already lived.
24
+ */
25
+ import { YStack } from '@hanzo/gui';
26
+ import type { ComponentProps } from 'react';
27
+ export interface PreviewProps extends Omit<ComponentProps<typeof YStack>, 'children'> {
28
+ /** A self-contained HTML document. Script in it will not run. */
29
+ markup: string;
30
+ /** Named in the frame's header. Also the frame's accessible name. */
31
+ title?: string;
32
+ /** Viewport height, px. The document scrolls inside it. */
33
+ height?: number;
34
+ }
35
+ export declare function Preview({ markup, title, height, ...props }: PreviewProps): import("react").JSX.Element;
36
+ //# sourceMappingURL=Preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Preview.d.ts","sourceRoot":"","sources":["../../src/chat/Preview.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAuB,MAAM,EAAE,MAAM,YAAY,CAAA;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAI3C,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,UAAU,CAAC;IACnF,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,wBAAgB,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAY,EAAE,GAAG,KAAK,EAAE,EAAE,YAAY,+BAiD9E"}
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Preview — an interface the model wrote, shown but not run.
5
+ *
6
+ * Generative UI arrives as markup, and markup from a model is untrusted input
7
+ * that happens to be a document. Rendering it into the host page is
8
+ * cross-site scripting with extra steps; refusing to render it at all leaves
9
+ * the feature as a paragraph of HTML source. Neither is the answer.
10
+ *
11
+ * The answer is `sandbox=""`. An empty allow-list is the STRICTEST setting the
12
+ * attribute has, not a lax one: no scripts, no same-origin, no forms, no
13
+ * navigation, no popups, no pointer lock. The document lays out and paints —
14
+ * real CSS, real fonts, real layout, a real screenshot — and can do nothing
15
+ * else. That is exactly the shape of a mockup, so nothing is lost by taking
16
+ * away the half that is dangerous.
17
+ *
18
+ * This is why `Parts` can draw an `artifact` and still refuses a `uiResource`.
19
+ * They are not the same risk: a `uiResource` is a URL, and loading one pulls a
20
+ * third-party origin into the page over the network. Inline markup with
21
+ * scripting off reaches nothing and runs nothing.
22
+ *
23
+ * Web only, like `Backdrop` — it is an `<iframe>`, and there is no such thing
24
+ * off the web. A surface that runs on native sends artifacts with no `markup`
25
+ * and gets `ArtifactCard`, which is where that decision already lived.
26
+ */
27
+ import { SizableText, XStack, YStack } from '@hanzo/gui';
28
+ import { slot } from '../backends/gui/slot.js';
29
+ export function Preview({ markup, title, height = 320, ...props }) {
30
+ const named = title ?? 'Preview';
31
+ return (_jsxs(YStack, { ...slot('preview'), width: "100%", maxW: 720, rounded: "$4", overflow: "hidden", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", ...props, children: [title ? (_jsx(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", borderBottomWidth: 1, borderColor: "$borderColor", children: _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: title }) })) : null, _jsx("iframe", { title: named, srcDoc: markup, sandbox: "", referrerPolicy: "no-referrer", style: {
32
+ display: 'block',
33
+ width: '100%',
34
+ height,
35
+ border: 0,
36
+ background: '#fff',
37
+ colorScheme: 'light',
38
+ } })] }));
39
+ }
40
+ //# sourceMappingURL=Preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Preview.js","sourceRoot":"","sources":["../../src/chat/Preview.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAA;AAW3C,MAAM,UAAU,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE,GAAG,KAAK,EAAgB;IAC7E,MAAM,KAAK,GAAG,KAAK,IAAI,SAAS,CAAA;IAChC,OAAO,CACL,MAAC,MAAM,OACD,IAAI,CAAC,SAAS,CAAC,EACnB,KAAK,EAAC,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,OAAO,EAAC,IAAI,EACZ,QAAQ,EAAC,QAAQ,EACjB,WAAW,EAAE,CAAC,EACd,WAAW,EAAC,cAAc,EAC1B,EAAE,EAAC,QAAQ,KACP,KAAK,aAER,KAAK,CAAC,CAAC,CAAC,CACP,KAAC,MAAM,IACL,KAAK,EAAC,QAAQ,EACd,GAAG,EAAC,IAAI,EACR,EAAE,EAAC,IAAI,EACP,EAAE,EAAC,IAAI,EACP,iBAAiB,EAAE,CAAC,EACpB,WAAW,EAAC,cAAc,YAE1B,KAAC,WAAW,IAAC,IAAI,EAAC,IAAI,EAAC,KAAK,EAAC,OAAO,EAAC,aAAa,EAAE,CAAC,YAClD,KAAK,GACM,GACP,CACV,CAAC,CAAC,CAAC,IAAI,EAMR,iBACE,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAC,EAAE,EACV,cAAc,EAAC,aAAa,EAC5B,KAAK,EAAE;oBACL,OAAO,EAAE,OAAO;oBAChB,KAAK,EAAE,MAAM;oBACb,MAAM;oBACN,MAAM,EAAE,CAAC;oBACT,UAAU,EAAE,MAAM;oBAClB,WAAW,EAAE,OAAO;iBACrB,GACD,IACK,CACV,CAAA;AACH,CAAC"}
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.made = exports.EMPTY = exports.answer = exports.TOOLS = void 0;
4
+ exports.read = read;
5
+ exports.stage = stage;
6
+ /** The moods a persona can be asked for — `Emotion`, as a schema enum. */
7
+ const MOODS = [
8
+ 'idle',
9
+ 'listening',
10
+ 'thinking',
11
+ 'speaking',
12
+ 'happy',
13
+ 'sad',
14
+ 'surprised',
15
+ ];
16
+ const KINDS = ['canvas', 'code', 'view'];
17
+ /**
18
+ * The three tools, ready to pass to `messages.stream`.
19
+ *
20
+ * Descriptions say WHEN to call, not just what the tool is: a model reaches for
21
+ * a tool on its trigger condition, and a description that only names the tool
22
+ * is the common way to get a tool that never fires.
23
+ */
24
+ exports.TOOLS = [
25
+ {
26
+ name: 'feel',
27
+ description: 'Set the mood of the on-screen persona. Call it when what you are doing changes — before a long piece of work, when you reach an answer, when something goes wrong. The mood holds until you change it, so call it again rather than leaving a stale one.',
28
+ input_schema: {
29
+ type: 'object',
30
+ properties: {
31
+ emotion: { type: 'string', enum: MOODS, description: 'The mood to hold.' },
32
+ },
33
+ required: ['emotion'],
34
+ },
35
+ },
36
+ {
37
+ name: 'show',
38
+ description: 'Render an interface inline for the person to look at. Call it whenever a layout, a screen, a chart or a mockup would answer better than prose. `markup` is a self-contained HTML document including its own <style>; it is displayed with scripting disabled, so write markup and CSS, never script.',
39
+ input_schema: {
40
+ type: 'object',
41
+ properties: {
42
+ title: { type: 'string', description: 'A short name for the thing.' },
43
+ kind: {
44
+ type: 'string',
45
+ enum: KINDS,
46
+ description: 'view for a rendered interface, canvas for a document, code for source.',
47
+ },
48
+ markup: { type: 'string', description: 'A self-contained HTML document.' },
49
+ },
50
+ required: ['title', 'kind', 'markup'],
51
+ },
52
+ },
53
+ {
54
+ name: 'report',
55
+ description: 'Fill the side panel with an account of this run. Call it once you know what the run did: a one-paragraph summary, what you were given, and what you produced. The panel is what the person reads to check your work, so name real values rather than describing them.',
56
+ input_schema: {
57
+ type: 'object',
58
+ properties: {
59
+ summary: { type: 'string', description: 'One paragraph: what happened.' },
60
+ inputs: { type: 'object', description: 'Name to value, what the run was given.' },
61
+ outputs: { type: 'object', description: 'Name to value, what the run produced.' },
62
+ },
63
+ required: ['summary'],
64
+ },
65
+ },
66
+ ];
67
+ /* -------------------------------------------------------------------- reading */
68
+ /** One field off an untrusted object. The wire is a boundary; nothing below
69
+ * trusts a shape it has not checked. */
70
+ const str = (value, key) => {
71
+ const x = value?.[key];
72
+ return typeof x === 'string' ? x : undefined;
73
+ };
74
+ const facts = (value, key) => {
75
+ const x = value?.[key];
76
+ return x != null && typeof x === 'object' && !Array.isArray(x) ? x : undefined;
77
+ };
78
+ const one = (value, key, set) => {
79
+ const x = str(value, key);
80
+ return set.includes(x) ? x : undefined;
81
+ };
82
+ /**
83
+ * A tool call, narrowed to a directive — or `undefined` for anything else.
84
+ *
85
+ * `undefined` is not a failure. Most tool calls in a turn are the caller's own
86
+ * tools, and those belong in the transcript as steps; this answers only "is
87
+ * this one of ours, and is it well-formed". A directive missing a required
88
+ * field is not ours either — a half-read `show` would draw an empty frame.
89
+ */
90
+ function read(name, id, input) {
91
+ switch (name) {
92
+ case 'feel': {
93
+ const emotion = one(input, 'emotion', MOODS);
94
+ return emotion ? { name, id, emotion } : undefined;
95
+ }
96
+ case 'show': {
97
+ const title = str(input, 'title');
98
+ const markup = str(input, 'markup');
99
+ const kind = one(input, 'kind', KINDS) ?? 'view';
100
+ return title && markup ? { name, id, title, kind, markup } : undefined;
101
+ }
102
+ case 'report': {
103
+ const summary = str(input, 'summary');
104
+ return summary
105
+ ? { name, id, summary, inputs: facts(input, 'inputs'), outputs: facts(input, 'outputs') }
106
+ : undefined;
107
+ }
108
+ default:
109
+ return undefined;
110
+ }
111
+ }
112
+ /** What a directive is answered with. Drawing IS the execution, so the result
113
+ * is a confirmation — but it has to be sent, or the turn never continues. */
114
+ const answer = (directive) => ({
115
+ type: 'tool_result',
116
+ tool_use_id: directive.id,
117
+ content: SAID[directive.name],
118
+ });
119
+ exports.answer = answer;
120
+ const SAID = {
121
+ feel: 'The persona is showing it.',
122
+ show: 'Rendered for the person to see.',
123
+ report: 'The panel is showing it.',
124
+ };
125
+ /** A stage with nothing on it. The starting value for a turn. */
126
+ exports.EMPTY = { emotion: 'idle', parts: [], ran: [], open: {} };
127
+ /** Adjacent prose, kept as one part. `join` does this at render time too, but
128
+ * a fold that appends per delta would hand it thousands of fragments. */
129
+ const add = (parts, part) => {
130
+ const last = parts[parts.length - 1];
131
+ if ((part.type === 'text' || part.type === 'reasoning') && last?.type === part.type) {
132
+ return [...parts.slice(0, -1), { type: part.type, text: last.text + part.text }];
133
+ }
134
+ return [...parts, part];
135
+ };
136
+ /** The mood the stream implies, unless the model has fixed one. */
137
+ const move = (prev, emotion) => prev.held ?? emotion;
138
+ /** Apply a directive. */
139
+ const apply = (prev, d) => {
140
+ const ran = [...prev.ran, d];
141
+ switch (d.name) {
142
+ case 'feel':
143
+ return { ...prev, ran, held: d.emotion, emotion: d.emotion };
144
+ case 'show':
145
+ return {
146
+ ...prev,
147
+ ran,
148
+ parts: add(prev.parts, {
149
+ type: 'artifact',
150
+ title: d.title,
151
+ kind: d.kind,
152
+ id: d.id,
153
+ markup: d.markup,
154
+ }),
155
+ };
156
+ case 'report':
157
+ return {
158
+ ...prev,
159
+ ran,
160
+ report: { summary: d.summary, inputs: d.inputs, outputs: d.outputs },
161
+ };
162
+ }
163
+ };
164
+ /**
165
+ * The stream, folded into what is on screen.
166
+ *
167
+ * Pure, and total over the wire: an event this does not recognise returns the
168
+ * stage unchanged rather than throwing, because a stream carries `ping` and
169
+ * `message_delta` and whatever it gains next.
170
+ */
171
+ function stage(prev, frame) {
172
+ switch (frame.type) {
173
+ // A new turn reads fresh. Only the held mood clears — the parts and the
174
+ // report belong to whoever is keeping them.
175
+ case 'message_start':
176
+ return { ...prev, held: undefined, emotion: 'thinking' };
177
+ case 'content_block_start': {
178
+ const kind = str(frame.content_block, 'type');
179
+ if (kind === 'tool_use') {
180
+ const id = str(frame.content_block, 'id') ?? '';
181
+ const name = str(frame.content_block, 'name') ?? '';
182
+ return {
183
+ ...prev,
184
+ emotion: move(prev, 'thinking'),
185
+ open: { ...prev.open, [frame.index ?? 0]: { id, name, json: '' } },
186
+ };
187
+ }
188
+ if (kind === 'thinking')
189
+ return { ...prev, emotion: move(prev, 'thinking') };
190
+ if (kind === 'text')
191
+ return { ...prev, emotion: move(prev, 'speaking') };
192
+ return prev;
193
+ }
194
+ case 'content_block_delta': {
195
+ const kind = str(frame.delta, 'type');
196
+ if (kind === 'text_delta') {
197
+ const text = str(frame.delta, 'text') ?? '';
198
+ return {
199
+ ...prev,
200
+ emotion: move(prev, 'speaking'),
201
+ parts: add(prev.parts, { type: 'text', text }),
202
+ };
203
+ }
204
+ if (kind === 'thinking_delta') {
205
+ const text = str(frame.delta, 'thinking') ?? '';
206
+ return {
207
+ ...prev,
208
+ emotion: move(prev, 'thinking'),
209
+ parts: add(prev.parts, { type: 'reasoning', text }),
210
+ };
211
+ }
212
+ if (kind === 'input_json_delta') {
213
+ const at = frame.index ?? 0;
214
+ const call = prev.open[at];
215
+ if (!call)
216
+ return prev;
217
+ const json = call.json + (str(frame.delta, 'partial_json') ?? '');
218
+ return { ...prev, open: { ...prev.open, [at]: { ...call, json } } };
219
+ }
220
+ return prev;
221
+ }
222
+ case 'content_block_stop': {
223
+ const at = frame.index ?? 0;
224
+ const call = prev.open[at];
225
+ if (!call)
226
+ return prev;
227
+ const { [at]: _closed, ...open } = prev.open;
228
+ const next = { ...prev, open };
229
+ const directive = read(call.name, call.id, parse(call.json));
230
+ if (directive)
231
+ return apply(next, directive);
232
+ // Somebody else's tool. It belongs in the transcript as a step.
233
+ return {
234
+ ...next,
235
+ parts: add(next.parts, {
236
+ type: 'toolCall',
237
+ name: call.name,
238
+ id: call.id,
239
+ args: parse(call.json),
240
+ }),
241
+ };
242
+ }
243
+ // The turn is over. The mood the model chose survives it — a turn that
244
+ // ended happy rests happy; one that said nothing rests idle.
245
+ case 'message_stop':
246
+ return { ...prev, emotion: prev.held ?? 'idle' };
247
+ case 'error':
248
+ return { ...prev, emotion: move(prev, 'sad') };
249
+ default:
250
+ return prev;
251
+ }
252
+ }
253
+ /** A tool's arguments. An empty block sends no deltas at all, and a truncated
254
+ * turn leaves a fragment; neither should take the page down. */
255
+ const parse = (json) => {
256
+ if (!json)
257
+ return undefined;
258
+ try {
259
+ return JSON.parse(json);
260
+ }
261
+ catch {
262
+ return undefined;
263
+ }
264
+ };
265
+ /**
266
+ * What the turn produced, for the panel.
267
+ *
268
+ * Prose is the answer and belongs in the thread; everything else is evidence —
269
+ * what ran, what came back, what was made — and that is what the panel is for.
270
+ */
271
+ const made = (from) => from.parts.filter((part) => part.type !== 'text' && part.type !== 'reasoning');
272
+ exports.made = made;
@@ -0,0 +1,167 @@
1
+ /**
2
+ * The control channel: three tools the model calls to drive the interface.
3
+ *
4
+ * A model already knows how to call a tool, so generative UI needs no second
5
+ * protocol beside tool use. `TOOLS` are ordinary Anthropic tools a caller hands
6
+ * to `messages.stream`; the client executes them by DRAWING rather than by
7
+ * computing, and answers each with a `tool_result` exactly as it would any
8
+ * other. That is the whole design — no new wire, no parallel channel, nothing
9
+ * for a model to learn.
10
+ *
11
+ * feel({ emotion }) the persona's mood
12
+ * show({ title, kind, markup }) a generated interface, rendered
13
+ * report({ summary, inputs, outputs }) the run, in the side panel
14
+ *
15
+ * ONE WORD EACH, and no dots. A tool name is `[a-zA-Z0-9_-]` — `persona.set`
16
+ * is rejected by the API before the model ever sees it — so a namespace has to
17
+ * be spelled some other way or not at all. Not at all: three verbs the model
18
+ * reads without a legend beat three compounds that only look organised.
19
+ *
20
+ * `stage` is the fold from the stream to what is on screen. It takes the raw
21
+ * `content_block_*` events, so the persona moves even in a turn that calls
22
+ * nothing — thinking while the model thinks, speaking on the first word — and a
23
+ * `feel` directive overrides that reading until the model changes its mind.
24
+ * One fold, one state, three components read it.
25
+ *
26
+ * Nothing here imports `@hanzo/ai`. `Frame` is structural and open, the way
27
+ * `words`'s `Part` is open: it reads the wire, and the wire is not ours to
28
+ * close. An `AnthropicStreamEvent` satisfies it with no dependency in either
29
+ * direction, and `directive.test.ts` pins that assignability both ways.
30
+ */
31
+ import type { ArtifactKind } from '../agents/Pane.js';
32
+ import type { MessagePart } from './Parts.js';
33
+ import type { Emotion } from './Persona.js';
34
+ /** What the model asked the interface to do. `name` is the tool it called, so
35
+ * the discriminant and the wire are the same word. */
36
+ export type Directive =
37
+ /** Move the persona. Holds until the model says otherwise. */
38
+ {
39
+ name: 'feel';
40
+ id: string;
41
+ emotion: Emotion;
42
+ }
43
+ /** Draw an interface the model wrote. `markup` is HTML, rendered inert. */
44
+ | {
45
+ name: 'show';
46
+ id: string;
47
+ title: string;
48
+ kind: ArtifactKind;
49
+ markup: string;
50
+ }
51
+ /** Fill the side panel with the run's own account of itself. */
52
+ | {
53
+ name: 'report';
54
+ id: string;
55
+ summary: string;
56
+ inputs?: Facts;
57
+ outputs?: Facts;
58
+ };
59
+ /** Name → value. What a run was given, and what it produced. */
60
+ export type Facts = Record<string, unknown>;
61
+ /** A `report` directive's payload, without the envelope. */
62
+ export interface Report {
63
+ summary: string;
64
+ inputs?: Facts;
65
+ outputs?: Facts;
66
+ }
67
+ /**
68
+ * A tool definition, structurally.
69
+ *
70
+ * A `type` alias and not an `interface` on purpose: only a type literal gets an
71
+ * implicit index signature, and without one this is NOT assignable to
72
+ * `AnthropicTool` (which carries `[key: string]: unknown`). An interface here
73
+ * would typecheck in this package and fail at every call site.
74
+ */
75
+ export type Tool = {
76
+ name: string;
77
+ description: string;
78
+ input_schema: {
79
+ type: 'object';
80
+ properties: Record<string, unknown>;
81
+ required?: string[];
82
+ };
83
+ };
84
+ /**
85
+ * The three tools, ready to pass to `messages.stream`.
86
+ *
87
+ * Descriptions say WHEN to call, not just what the tool is: a model reaches for
88
+ * a tool on its trigger condition, and a description that only names the tool
89
+ * is the common way to get a tool that never fires.
90
+ */
91
+ export declare const TOOLS: Tool[];
92
+ /**
93
+ * A tool call, narrowed to a directive — or `undefined` for anything else.
94
+ *
95
+ * `undefined` is not a failure. Most tool calls in a turn are the caller's own
96
+ * tools, and those belong in the transcript as steps; this answers only "is
97
+ * this one of ours, and is it well-formed". A directive missing a required
98
+ * field is not ours either — a half-read `show` would draw an empty frame.
99
+ */
100
+ export declare function read(name: string, id: string, input: unknown): Directive | undefined;
101
+ /** What a directive is answered with. Drawing IS the execution, so the result
102
+ * is a confirmation — but it has to be sent, or the turn never continues. */
103
+ export declare const answer: (directive: Directive) => {
104
+ type: 'tool_result';
105
+ tool_use_id: string;
106
+ content: string;
107
+ };
108
+ /**
109
+ * One event off the model's stream.
110
+ *
111
+ * Open, and `type` is the only field it insists on — which is also what keeps
112
+ * every `@hanzo/ai` event assignable to it. `content_block` and `delta` are
113
+ * `unknown` deliberately: their shape varies per block and per provider, a
114
+ * narrower spelling makes half the union unassignable, and this is a boundary
115
+ * where reading defensively is the correct habit anyway.
116
+ */
117
+ export interface Frame {
118
+ type: string;
119
+ /** Which content block, on the three events that name one. */
120
+ index?: number;
121
+ /** `content_block_start`'s block. */
122
+ content_block?: unknown;
123
+ /** `content_block_delta`'s delta. */
124
+ delta?: unknown;
125
+ }
126
+ /** A tool call still arriving. Its `input` is JSON split across deltas and is
127
+ * not parseable until the block stops. */
128
+ interface Call {
129
+ id: string;
130
+ name: string;
131
+ json: string;
132
+ }
133
+ /** What the model has made of the interface. */
134
+ export interface Stage {
135
+ /** What the persona is doing — the mood to draw. */
136
+ emotion: Emotion;
137
+ /** The mood a `feel` directive fixed. While set, the stream does not move the
138
+ * persona; the model's word outranks the reading. */
139
+ held?: Emotion;
140
+ /** The turn, as parts. Directives that draw produce one; the rest do not. */
141
+ parts: readonly MessagePart[];
142
+ /** The side panel's content, once `report` has landed. */
143
+ report?: Report;
144
+ /** Every directive this turn, in order. `ran.map(answer)` is the reply. */
145
+ ran: readonly Directive[];
146
+ /** Tool blocks still arriving, by index. */
147
+ open: Readonly<Record<number, Call>>;
148
+ }
149
+ /** A stage with nothing on it. The starting value for a turn. */
150
+ export declare const EMPTY: Stage;
151
+ /**
152
+ * The stream, folded into what is on screen.
153
+ *
154
+ * Pure, and total over the wire: an event this does not recognise returns the
155
+ * stage unchanged rather than throwing, because a stream carries `ping` and
156
+ * `message_delta` and whatever it gains next.
157
+ */
158
+ export declare function stage(prev: Stage, frame: Frame): Stage;
159
+ /**
160
+ * What the turn produced, for the panel.
161
+ *
162
+ * Prose is the answer and belongs in the thread; everything else is evidence —
163
+ * what ran, what came back, what was made — and that is what the panel is for.
164
+ */
165
+ export declare const made: (from: Stage) => MessagePart[];
166
+ export {};
167
+ //# sourceMappingURL=directive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directive.d.ts","sourceRoot":"","sources":["../../src/chat/directive.ts"],"names":[],"mappings":"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"}