oui-spec 0.6.0 → 0.7.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/README.md +28 -20
- package/dist/chunk-2CPOHQGI.cjs +463 -0
- package/dist/chunk-2CPOHQGI.cjs.map +1 -0
- package/dist/{chunk-VQETSIY5.js → chunk-4MEYE6XZ.js} +244 -37
- package/dist/chunk-4MEYE6XZ.js.map +1 -0
- package/dist/{chunk-AFNWLRKV.cjs → chunk-DOJXITEZ.cjs} +248 -41
- package/dist/chunk-DOJXITEZ.cjs.map +1 -0
- package/dist/{chunk-F3SKRAGU.cjs → chunk-FSGB5JNJ.cjs} +5 -3
- package/dist/chunk-FSGB5JNJ.cjs.map +1 -0
- package/dist/chunk-RZUYWZFK.js +431 -0
- package/dist/chunk-RZUYWZFK.js.map +1 -0
- package/dist/{chunk-NYCJZQSP.js → chunk-UOTAHGIV.js} +5 -3
- package/dist/chunk-UOTAHGIV.js.map +1 -0
- package/dist/core/index.cjs +6 -6
- package/dist/core/index.d.cts +5 -5
- package/dist/core/index.d.ts +5 -5
- package/dist/core/index.js +2 -2
- package/dist/index.cjs +108 -20
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +3 -3
- package/dist/react/index.cjs +6 -6
- package/dist/react/index.d.cts +7 -6
- package/dist/react/index.d.ts +7 -6
- package/dist/react/index.js +1 -1
- package/dist/spec/index.cjs +98 -10
- package/dist/spec/index.d.cts +76 -4
- package/dist/spec/index.d.ts +76 -4
- package/dist/spec/index.js +1 -1
- package/dist/{surface-runtime-D5nINY4b.d.ts → surface-runtime-BPEtT3LZ.d.ts} +30 -6
- package/dist/{surface-runtime-CSZ9UtBO.d.cts → surface-runtime-CexHqUn-.d.cts} +30 -6
- package/dist/transport/index.d.cts +3 -3
- package/dist/transport/index.d.ts +3 -3
- package/dist/{types-CmJb1BQ8.d.cts → types-CnlVRPFL.d.cts} +92 -2
- package/dist/{types-CmJb1BQ8.d.ts → types-CnlVRPFL.d.ts} +92 -2
- package/dist/{websocket-Ce3tDSsJ.d.ts → websocket-DKL9qoO-.d.ts} +1 -1
- package/dist/{websocket-Ds2Rh_jL.d.cts → websocket-HnzPlfk6.d.cts} +1 -1
- package/package.json +35 -41
- package/spec/OUI-SPEC-v0.1.md +116 -7
- package/dist/chunk-AFNWLRKV.cjs.map +0 -1
- package/dist/chunk-B2EFZMGY.js +0 -159
- package/dist/chunk-B2EFZMGY.js.map +0 -1
- package/dist/chunk-F3SKRAGU.cjs.map +0 -1
- package/dist/chunk-LDECTDPU.cjs +0 -169
- package/dist/chunk-LDECTDPU.cjs.map +0 -1
- package/dist/chunk-NYCJZQSP.js.map +0 -1
- package/dist/chunk-VQETSIY5.js.map +0 -1
package/README.md
CHANGED
|
@@ -64,13 +64,25 @@ change that: navigating unmounts one page's surfaces and mounts another's. An
|
|
|
64
64
|
agent that only knew the capability set from before the action would be acting
|
|
65
65
|
on the page it just left.
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
with `
|
|
73
|
-
|
|
67
|
+
Action definitions are heavy (every action's schema: several hundred KB on a
|
|
68
|
+
studio page), so a client does not send them. It sends an **index**: one entry
|
|
69
|
+
per action, with its title, the first sentence of its description, its effect
|
|
70
|
+
and a one-line summary of what it takes. An agent runtime fetches the
|
|
71
|
+
definition it needs with the runtime's own `oui.describe`, and reads state that
|
|
72
|
+
did not fit with `oui.read`. This is the index form: pass `form: "index"` to
|
|
73
|
+
`createSurfaceRuntime` once the agent runtime reads it. Until 1.0 the default is
|
|
74
|
+
`"full"`, which sends every definition as earlier versions did.
|
|
75
|
+
|
|
76
|
+
A result repeats the index only when the agent runtime does not hold it: the
|
|
77
|
+
runtime sends the `surfacesHash` it holds as the request's `knownSurfaces`, and
|
|
78
|
+
the result carries `index` only when the client's hash differs.
|
|
79
|
+
|
|
80
|
+
What a client sends is fitted to a byte budget before it is sent (480 KB for a
|
|
81
|
+
result, 256 KB for a snapshot): long lists are cut first, then long texts, and
|
|
82
|
+
`fit` says where, so the rest can be read. A result the receiver still refuses
|
|
83
|
+
is acknowledged as refused, and the client sends it again trimmed a step at a
|
|
84
|
+
time (the index, then the observations, then the data), with `delivery` saying
|
|
85
|
+
what was left out and why: the agent always learns the action's outcome. A request is acknowledged on receipt when the sender asks,
|
|
74
86
|
and a repeat of a request whose answer is on its way gets no second copy.
|
|
75
87
|
|
|
76
88
|
An async action (`async: true`) is acknowledged at once with `interim: true`,
|
|
@@ -173,7 +185,7 @@ function CounterPage() {
|
|
|
173
185
|
}
|
|
174
186
|
```
|
|
175
187
|
|
|
176
|
-
`runtime.snapshot()` returns `{ surfaces, observations, surfacesHash }` for
|
|
188
|
+
`runtime.snapshot()` returns `{ surfaces, observations, surfacesHash }` (or `index` in place of `surfaces`, in index form) for
|
|
177
189
|
everything mounted. An agent runtime that keeps no registry of its own can take it with
|
|
178
190
|
each request (for example, with each chat turn); set `announce: false` and the
|
|
179
191
|
runtime sends nothing but results over the socket.
|
|
@@ -256,7 +268,7 @@ sequenceDiagram
|
|
|
256
268
|
WS->>Browser: oui:dispatch
|
|
257
269
|
Note over Browser: Handler runs once (de-duplicated by requestId)
|
|
258
270
|
Note over Browser: Waits until the UI settles
|
|
259
|
-
Browser->>WS: oui:action:result { requestId, success, data, observations, surfacesHash, surfaces if changed }
|
|
271
|
+
Browser->>WS: oui:action:result { requestId, success, data, observations, surfacesHash, surfaces or index if changed }
|
|
260
272
|
WS->>Agent: result for requestId
|
|
261
273
|
```
|
|
262
274
|
|
|
@@ -736,6 +748,7 @@ They even share JSON Schema for parameter validation — a deliberate design cho
|
|
|
736
748
|
- [x] `createSurfaceRuntime()` — client-side record of mounted surfaces; every request answered with a result and the snapshot after it
|
|
737
749
|
- [x] Approvals — `transaction` and destructive actions run only on a grant from the user's own confirmation, bound to the exact params (0.5.0)
|
|
738
750
|
- [x] Results carry surfaces only when they changed (`knownSurfaces` / `surfacesHash`), and a refused result is sent again trimmed, saying why (0.6.0)
|
|
751
|
+
- [x] The index form: a client sends an index of its actions, definitions are fetched with `oui.describe`, state is read with `oui.read`, and every frame is fitted to a byte budget (0.7.0)
|
|
739
752
|
- [ ] `oui-spec/devtools` — Surface inspector / debugger
|
|
740
753
|
- [ ] `oui-spec/vue` — Vue bindings
|
|
741
754
|
- [ ] `oui-spec/validator` — Runtime schema validation
|
|
@@ -747,20 +760,15 @@ They even share JSON Schema for parameter validation — a deliberate design cho
|
|
|
747
760
|
|
|
748
761
|
Contributions welcome. Please read the spec at [`spec/OUI-SPEC-v0.1.md`](./spec/OUI-SPEC-v0.1.md) before proposing changes to ensure alignment with the design principles.
|
|
749
762
|
|
|
763
|
+
`oui-spec` lives in the [OUI monorepo](https://github.com/wesreid/oui) beside the `@ouispec/*` packages. See its [CONTRIBUTING.md](https://github.com/wesreid/oui/blob/main/CONTRIBUTING.md):
|
|
764
|
+
|
|
750
765
|
```bash
|
|
751
|
-
# Clone and install
|
|
752
766
|
git clone https://github.com/wesreid/oui.git
|
|
753
767
|
cd oui
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
# Run tests
|
|
760
|
-
npm test
|
|
761
|
-
|
|
762
|
-
# Typecheck
|
|
763
|
-
npm run typecheck
|
|
768
|
+
pnpm install
|
|
769
|
+
pnpm --filter oui-spec build
|
|
770
|
+
pnpm --filter oui-spec test
|
|
771
|
+
pnpm --filter oui-spec typecheck
|
|
764
772
|
```
|
|
765
773
|
|
|
766
774
|
---
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/spec/types.ts
|
|
4
|
+
var OUI_PROTOCOL_EVENTS = {
|
|
5
|
+
SURFACE_REGISTER: "surface:register",
|
|
6
|
+
SURFACE_DEREGISTER: "surface:deregister",
|
|
7
|
+
ACTION_REQUEST: "action:request",
|
|
8
|
+
ACTION_RESULT: "action:result",
|
|
9
|
+
OBSERVATION_UPDATE: "observation:update"
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/spec/approval.ts
|
|
13
|
+
var OUI_EFFECT_KINDS = [
|
|
14
|
+
"view",
|
|
15
|
+
"selection",
|
|
16
|
+
"navigate",
|
|
17
|
+
"open",
|
|
18
|
+
"edit",
|
|
19
|
+
"file",
|
|
20
|
+
"mutate",
|
|
21
|
+
"job",
|
|
22
|
+
"transaction"
|
|
23
|
+
];
|
|
24
|
+
var OUI_EFFECT_ACCESS = {
|
|
25
|
+
view: "read",
|
|
26
|
+
selection: "read",
|
|
27
|
+
navigate: "read",
|
|
28
|
+
open: "read",
|
|
29
|
+
edit: "write",
|
|
30
|
+
file: "write",
|
|
31
|
+
mutate: "write",
|
|
32
|
+
job: "write",
|
|
33
|
+
transaction: "write"
|
|
34
|
+
};
|
|
35
|
+
function effectAccess(effect) {
|
|
36
|
+
return effect === void 0 ? "write" : OUI_EFFECT_ACCESS[effect];
|
|
37
|
+
}
|
|
38
|
+
function requiresApproval(effect, destructive) {
|
|
39
|
+
if (effect === "transaction") return true;
|
|
40
|
+
return !!destructive && effectAccess(effect) === "write";
|
|
41
|
+
}
|
|
42
|
+
var ARGS_HASH_PATTERN = /^[0-9a-f]{64}$/;
|
|
43
|
+
function canonicalJson(value) {
|
|
44
|
+
return serialize(value, "$");
|
|
45
|
+
}
|
|
46
|
+
async function argsHash(params) {
|
|
47
|
+
const canonical = canonicalJson(params);
|
|
48
|
+
const subtle = await webCrypto();
|
|
49
|
+
const digest = await subtle.digest(
|
|
50
|
+
"SHA-256",
|
|
51
|
+
new TextEncoder().encode(canonical)
|
|
52
|
+
);
|
|
53
|
+
let hex = "";
|
|
54
|
+
for (const byte of new Uint8Array(digest))
|
|
55
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
56
|
+
return hex;
|
|
57
|
+
}
|
|
58
|
+
async function webCrypto() {
|
|
59
|
+
const global = globalThis.crypto?.subtle;
|
|
60
|
+
if (global) return global;
|
|
61
|
+
const nodeCrypto = "node:crypto";
|
|
62
|
+
try {
|
|
63
|
+
const mod = await import(
|
|
64
|
+
/* @vite-ignore */
|
|
65
|
+
nodeCrypto
|
|
66
|
+
);
|
|
67
|
+
if (mod.webcrypto?.subtle) return mod.webcrypto.subtle;
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
throw new Error(
|
|
71
|
+
"[OUI] argsHash needs WebCrypto (crypto.subtle), which this runtime does not provide"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
function refuse(path, what) {
|
|
75
|
+
throw new TypeError(`[OUI] ${path} is not I-JSON: ${what}`);
|
|
76
|
+
}
|
|
77
|
+
function serialize(value, path) {
|
|
78
|
+
switch (typeof value) {
|
|
79
|
+
case "string":
|
|
80
|
+
return quote(value, path);
|
|
81
|
+
case "number":
|
|
82
|
+
if (!Number.isFinite(value)) refuse(path, String(value));
|
|
83
|
+
return JSON.stringify(value);
|
|
84
|
+
case "boolean":
|
|
85
|
+
return value ? "true" : "false";
|
|
86
|
+
case "object": {
|
|
87
|
+
if (value === null) return "null";
|
|
88
|
+
if (Array.isArray(value)) {
|
|
89
|
+
return `[${value.map((item, i) => serialize(item, `${path}[${i}]`)).join(",")}]`;
|
|
90
|
+
}
|
|
91
|
+
const proto = Object.getPrototypeOf(value);
|
|
92
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
93
|
+
refuse(path, `a ${proto?.constructor?.name ?? "non-plain"} object`);
|
|
94
|
+
}
|
|
95
|
+
const record = value;
|
|
96
|
+
const keys = Object.keys(record).filter((k) => record[k] !== void 0).sort();
|
|
97
|
+
return `{${keys.map(
|
|
98
|
+
(k) => `${quote(k, `${path} key`)}:${serialize(record[k], `${path}.${k}`)}`
|
|
99
|
+
).join(",")}}`;
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return refuse(path, typeof value);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function quote(text, path) {
|
|
106
|
+
for (let i = 0; i < text.length; i++) {
|
|
107
|
+
const unit = text.charCodeAt(i);
|
|
108
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
109
|
+
const next = text.charCodeAt(i + 1);
|
|
110
|
+
if (next >= 56320 && next <= 57343) {
|
|
111
|
+
i++;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
refuse(path, `a lone surrogate at ${i}`);
|
|
115
|
+
}
|
|
116
|
+
if (unit >= 56320 && unit <= 57343)
|
|
117
|
+
refuse(path, `a lone surrogate at ${i}`);
|
|
118
|
+
}
|
|
119
|
+
return JSON.stringify(text);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/spec/surfaces-hash.ts
|
|
123
|
+
var PREFIX = "fnv1a64:";
|
|
124
|
+
function surfacesHash(surfaces) {
|
|
125
|
+
return PREFIX + fnv1a64(sortedJson(surfaces));
|
|
126
|
+
}
|
|
127
|
+
function sortedJson(value) {
|
|
128
|
+
return JSON.stringify(sortKeys(value)) ?? "null";
|
|
129
|
+
}
|
|
130
|
+
function sortKeys(value) {
|
|
131
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
132
|
+
if (value && typeof value === "object") {
|
|
133
|
+
const out = {};
|
|
134
|
+
for (const key of Object.keys(value).sort()) {
|
|
135
|
+
const member = value[key];
|
|
136
|
+
if (member !== void 0) out[key] = sortKeys(member);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
function fnv1a64(text) {
|
|
143
|
+
let hi = 3421674724;
|
|
144
|
+
let lo = 2216829733;
|
|
145
|
+
for (let i = 0; i < text.length; i++) {
|
|
146
|
+
lo = (lo ^ text.charCodeAt(i)) >>> 0;
|
|
147
|
+
const loProduct = lo * 435;
|
|
148
|
+
const carry = Math.floor(loProduct / 4294967296);
|
|
149
|
+
const newLo = loProduct >>> 0;
|
|
150
|
+
hi = Math.imul(hi, 435) + Math.imul(lo, 256) + carry >>> 0;
|
|
151
|
+
lo = newLo;
|
|
152
|
+
}
|
|
153
|
+
return hex32(hi) + hex32(lo);
|
|
154
|
+
}
|
|
155
|
+
function hex32(n) {
|
|
156
|
+
return (n >>> 0).toString(16).padStart(8, "0");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/spec/index-form.ts
|
|
160
|
+
var INDEX_DESCRIPTION_CHARS = 160;
|
|
161
|
+
var INDEX_INPUT_CHARS = 120;
|
|
162
|
+
var INDEX_INPUT_PROPERTIES = 6;
|
|
163
|
+
var INDEX_ENUM_VALUES = 4;
|
|
164
|
+
var OUI_RUNTIME_SURFACE = "oui";
|
|
165
|
+
var OUI_DESCRIBE_ACTION = "describe";
|
|
166
|
+
var OUI_READ_ACTION = "read";
|
|
167
|
+
function utf8Length(text) {
|
|
168
|
+
let bytes = 0;
|
|
169
|
+
for (let i = 0; i < text.length; i++) {
|
|
170
|
+
const code = text.charCodeAt(i);
|
|
171
|
+
if (code < 128) bytes += 1;
|
|
172
|
+
else if (code < 2048) bytes += 2;
|
|
173
|
+
else if (code >= 55296 && code <= 56319) {
|
|
174
|
+
bytes += 4;
|
|
175
|
+
i++;
|
|
176
|
+
} else bytes += 3;
|
|
177
|
+
}
|
|
178
|
+
return bytes;
|
|
179
|
+
}
|
|
180
|
+
function jsonBytes(value) {
|
|
181
|
+
const json = JSON.stringify(value);
|
|
182
|
+
return json === void 0 ? 0 : utf8Length(json);
|
|
183
|
+
}
|
|
184
|
+
function clip(text, max) {
|
|
185
|
+
return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}\u2026`;
|
|
186
|
+
}
|
|
187
|
+
function firstSentence(text) {
|
|
188
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
189
|
+
const end = line.search(/[.!?](?= [A-Z0-9"'([])/);
|
|
190
|
+
return clip(
|
|
191
|
+
end >= 0 ? line.slice(0, end + 1) : line,
|
|
192
|
+
INDEX_DESCRIPTION_CHARS
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function typeOf(schema) {
|
|
196
|
+
const t = schema.type;
|
|
197
|
+
return Array.isArray(t) ? t.join("|") : t ?? "";
|
|
198
|
+
}
|
|
199
|
+
function unitOf(schema) {
|
|
200
|
+
const unit = schema["x-unit"];
|
|
201
|
+
return typeof unit === "string" && unit ? ` ${unit}` : "";
|
|
202
|
+
}
|
|
203
|
+
function unionOf(schema) {
|
|
204
|
+
const members = schema.oneOf ?? schema.anyOf;
|
|
205
|
+
return members && members.length > 0 ? members : null;
|
|
206
|
+
}
|
|
207
|
+
function discriminatorOf(members) {
|
|
208
|
+
const first = members[0]?.properties;
|
|
209
|
+
if (!first) return null;
|
|
210
|
+
for (const name of Object.keys(first)) {
|
|
211
|
+
const constants = members.map((m) => constantOf(m.properties?.[name]));
|
|
212
|
+
if (constants.every((c) => c !== void 0) && new Set(constants).size === members.length)
|
|
213
|
+
return name;
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
function constantOf(schema) {
|
|
218
|
+
if (!schema) return void 0;
|
|
219
|
+
if (typeof schema.const === "string" || typeof schema.const === "number" || typeof schema.const === "boolean") {
|
|
220
|
+
return schema.const;
|
|
221
|
+
}
|
|
222
|
+
return schema.enum?.length === 1 ? schema.enum[0] : void 0;
|
|
223
|
+
}
|
|
224
|
+
function valueSummary(schema) {
|
|
225
|
+
const constant = constantOf(schema);
|
|
226
|
+
if (constant !== void 0 && !schema.enum) return JSON.stringify(constant);
|
|
227
|
+
if (schema.enum) {
|
|
228
|
+
const shown = schema.enum.slice(0, INDEX_ENUM_VALUES).map(String).join("|");
|
|
229
|
+
return schema.enum.length > INDEX_ENUM_VALUES ? `${shown}|\u2026(${schema.enum.length})` : shown;
|
|
230
|
+
}
|
|
231
|
+
const union = unionOf(schema);
|
|
232
|
+
if (union) return `one of ${union.length}`;
|
|
233
|
+
const type = typeOf(schema);
|
|
234
|
+
if (type === "number" || type === "integer") {
|
|
235
|
+
const { minimum: min, maximum: max } = schema;
|
|
236
|
+
const range = min !== void 0 && max !== void 0 ? ` ${min}\u2013${max}` : min !== void 0 ? ` \u2265${min}` : max !== void 0 ? ` \u2264${max}` : "";
|
|
237
|
+
return `${type}${range}${unitOf(schema)}`;
|
|
238
|
+
}
|
|
239
|
+
if (type === "string")
|
|
240
|
+
return schema.format ? `string(${schema.format})` : "string";
|
|
241
|
+
if (type === "array")
|
|
242
|
+
return schema.items ? `list of ${valueSummary(schema.items)}` : "list";
|
|
243
|
+
if (type === "object") {
|
|
244
|
+
const n = Object.keys(schema.properties ?? {}).length;
|
|
245
|
+
return n > 0 ? `object(${n})` : "object";
|
|
246
|
+
}
|
|
247
|
+
return type || "any";
|
|
248
|
+
}
|
|
249
|
+
function summarizeInput(schema) {
|
|
250
|
+
if (!schema) return "none";
|
|
251
|
+
const union = unionOf(schema);
|
|
252
|
+
if (union) {
|
|
253
|
+
const by = discriminatorOf(union);
|
|
254
|
+
return clip(
|
|
255
|
+
`one of ${union.length} shapes${by ? ` by ${by}` : ""}`,
|
|
256
|
+
INDEX_INPUT_CHARS
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const properties = schema.properties ?? {};
|
|
260
|
+
const names = Object.keys(properties);
|
|
261
|
+
if (names.length === 0)
|
|
262
|
+
return typeOf(schema) && typeOf(schema) !== "object" ? valueSummary(schema) : "none";
|
|
263
|
+
const required = new Set(schema.required ?? []);
|
|
264
|
+
const ordered = [
|
|
265
|
+
...names.filter((n) => required.has(n)),
|
|
266
|
+
...names.filter((n) => !required.has(n))
|
|
267
|
+
];
|
|
268
|
+
const shown = ordered.slice(0, INDEX_INPUT_PROPERTIES);
|
|
269
|
+
const more = ordered.length - shown.length;
|
|
270
|
+
const parts = shown.map(
|
|
271
|
+
(n) => `${n}${required.has(n) ? "" : "?"}: ${valueSummary(properties[n])}`
|
|
272
|
+
);
|
|
273
|
+
if (more > 0) parts.push(`+${more} more`);
|
|
274
|
+
return clip(parts.join(", "), INDEX_INPUT_CHARS);
|
|
275
|
+
}
|
|
276
|
+
function definitionHash(action) {
|
|
277
|
+
return fnv1a64(sortedJson(action));
|
|
278
|
+
}
|
|
279
|
+
function indexEntry(action) {
|
|
280
|
+
return {
|
|
281
|
+
id: action.id,
|
|
282
|
+
...action.title ? { title: action.title } : {},
|
|
283
|
+
description: firstSentence(action.description),
|
|
284
|
+
...action.effect !== void 0 ? { effect: action.effect } : {},
|
|
285
|
+
...action.confirm ? { confirm: true } : {},
|
|
286
|
+
...action.async ? { async: true } : {},
|
|
287
|
+
...action.estimatedDuration ? { estimatedDuration: action.estimatedDuration } : {},
|
|
288
|
+
...action.polling?.maxDurationMs !== void 0 ? { maxDurationMs: action.polling.maxDurationMs } : {},
|
|
289
|
+
input: summarizeInput(action.input),
|
|
290
|
+
definitionHash: definitionHash(action),
|
|
291
|
+
definitionBytes: jsonBytes(action)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function surfaceIndex(surface) {
|
|
295
|
+
return {
|
|
296
|
+
id: surface.id,
|
|
297
|
+
name: surface.name,
|
|
298
|
+
description: surface.description,
|
|
299
|
+
...surface.version !== void 0 ? { version: surface.version } : {},
|
|
300
|
+
...surface.observations ? { observations: surface.observations } : {},
|
|
301
|
+
index: surface.actions.map(indexEntry)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/spec/fit.ts
|
|
306
|
+
var FIT_LIST_STEPS = [200, 50, 20, 5, 0];
|
|
307
|
+
var FIT_TEXT_CHARS = 2e3;
|
|
308
|
+
function pointerStep(pointer, key) {
|
|
309
|
+
return `${pointer}/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
310
|
+
}
|
|
311
|
+
function pointerSteps(pointer) {
|
|
312
|
+
if (pointer === "") return [];
|
|
313
|
+
if (!pointer.startsWith("/"))
|
|
314
|
+
throw new Error(
|
|
315
|
+
`"${pointer}" is not a JSON Pointer: it must be "" or start with "/"`
|
|
316
|
+
);
|
|
317
|
+
return pointer.slice(1).split("/").map((s) => s.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
318
|
+
}
|
|
319
|
+
function atPointer(value, pointer) {
|
|
320
|
+
let at = value;
|
|
321
|
+
for (const step of pointerSteps(pointer)) {
|
|
322
|
+
if (Array.isArray(at)) {
|
|
323
|
+
at = /^(0|[1-9]\d*)$/.test(step) ? at[Number(step)] : void 0;
|
|
324
|
+
} else if (at && typeof at === "object") {
|
|
325
|
+
at = Object.prototype.hasOwnProperty.call(at, step) ? at[step] : void 0;
|
|
326
|
+
} else return void 0;
|
|
327
|
+
if (at === void 0) return void 0;
|
|
328
|
+
}
|
|
329
|
+
return at;
|
|
330
|
+
}
|
|
331
|
+
function slots(observations, want) {
|
|
332
|
+
const found = [];
|
|
333
|
+
const walk = (holder, key, surface, observation, path) => {
|
|
334
|
+
const value = holder[key];
|
|
335
|
+
if (Array.isArray(value)) {
|
|
336
|
+
if (want === "list")
|
|
337
|
+
found.push({ holder, key, surface, observation, path });
|
|
338
|
+
value.forEach(
|
|
339
|
+
(_, i) => walk(value, i, surface, observation, pointerStep(path, i))
|
|
340
|
+
);
|
|
341
|
+
} else if (value && typeof value === "object") {
|
|
342
|
+
for (const k of Object.keys(value))
|
|
343
|
+
walk(
|
|
344
|
+
value,
|
|
345
|
+
k,
|
|
346
|
+
surface,
|
|
347
|
+
observation,
|
|
348
|
+
pointerStep(path, k)
|
|
349
|
+
);
|
|
350
|
+
} else if (typeof value === "string" && want === "text") {
|
|
351
|
+
found.push({ holder, key, surface, observation, path });
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
for (const surface of Object.keys(observations)) {
|
|
355
|
+
for (const observation of Object.keys(observations[surface])) {
|
|
356
|
+
walk(observations[surface], observation, surface, observation, "");
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return found;
|
|
360
|
+
}
|
|
361
|
+
function fitObservations(observations, budget) {
|
|
362
|
+
if (jsonBytes(observations) <= budget) return { observations, cuts: [] };
|
|
363
|
+
const fitted = JSON.parse(
|
|
364
|
+
JSON.stringify(observations)
|
|
365
|
+
);
|
|
366
|
+
const cuts = /* @__PURE__ */ new Map();
|
|
367
|
+
const note = (slot, kind, total, kept) => {
|
|
368
|
+
const id = `${slot.surface}\0${slot.observation}\0${slot.path}`;
|
|
369
|
+
const prior = cuts.get(id);
|
|
370
|
+
cuts.set(id, {
|
|
371
|
+
surface: slot.surface,
|
|
372
|
+
observation: slot.observation,
|
|
373
|
+
path: slot.path,
|
|
374
|
+
kind,
|
|
375
|
+
total: prior ? prior.total : total,
|
|
376
|
+
kept
|
|
377
|
+
});
|
|
378
|
+
};
|
|
379
|
+
const fits = () => jsonBytes(fitted) <= budget;
|
|
380
|
+
for (const rows of FIT_LIST_STEPS) {
|
|
381
|
+
for (const slot of slots(fitted, "list")) {
|
|
382
|
+
const list = slot.holder[slot.key];
|
|
383
|
+
if (list.length <= rows) continue;
|
|
384
|
+
note(slot, "list", list.length, rows);
|
|
385
|
+
list.length = rows;
|
|
386
|
+
}
|
|
387
|
+
for (const [id, cut] of cuts) {
|
|
388
|
+
if (cut.kind === "list" && atPointer(fitted[cut.surface]?.[cut.observation], cut.path) === void 0)
|
|
389
|
+
cuts.delete(id);
|
|
390
|
+
}
|
|
391
|
+
if (fits()) return { observations: fitted, cuts: [...cuts.values()] };
|
|
392
|
+
}
|
|
393
|
+
for (const slot of slots(fitted, "text")) {
|
|
394
|
+
const text = slot.holder[slot.key];
|
|
395
|
+
if (text.length <= FIT_TEXT_CHARS) continue;
|
|
396
|
+
note(slot, "text", text.length, FIT_TEXT_CHARS);
|
|
397
|
+
slot.holder[slot.key] = text.slice(
|
|
398
|
+
0,
|
|
399
|
+
FIT_TEXT_CHARS
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
if (fits()) return { observations: fitted, cuts: [...cuts.values()] };
|
|
403
|
+
const values = Object.keys(fitted).flatMap(
|
|
404
|
+
(surface) => Object.keys(fitted[surface]).map((observation) => ({
|
|
405
|
+
surface,
|
|
406
|
+
observation,
|
|
407
|
+
bytes: jsonBytes(fitted[surface][observation])
|
|
408
|
+
}))
|
|
409
|
+
).sort(
|
|
410
|
+
(a, b) => b.bytes - a.bytes || a.surface.localeCompare(b.surface) || a.observation.localeCompare(b.observation)
|
|
411
|
+
);
|
|
412
|
+
for (const { surface, observation, bytes } of values) {
|
|
413
|
+
if (fits()) break;
|
|
414
|
+
for (const [id, cut] of cuts) {
|
|
415
|
+
if (cut.surface === surface && cut.observation === observation)
|
|
416
|
+
cuts.delete(id);
|
|
417
|
+
}
|
|
418
|
+
fitted[surface][observation] = null;
|
|
419
|
+
cuts.set(`${surface}\0${observation}\0`, {
|
|
420
|
+
surface,
|
|
421
|
+
observation,
|
|
422
|
+
path: "",
|
|
423
|
+
kind: "value",
|
|
424
|
+
total: bytes,
|
|
425
|
+
kept: 0
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
return { observations: fitted, cuts: [...cuts.values()] };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
exports.ARGS_HASH_PATTERN = ARGS_HASH_PATTERN;
|
|
432
|
+
exports.FIT_LIST_STEPS = FIT_LIST_STEPS;
|
|
433
|
+
exports.FIT_TEXT_CHARS = FIT_TEXT_CHARS;
|
|
434
|
+
exports.INDEX_DESCRIPTION_CHARS = INDEX_DESCRIPTION_CHARS;
|
|
435
|
+
exports.INDEX_INPUT_CHARS = INDEX_INPUT_CHARS;
|
|
436
|
+
exports.INDEX_INPUT_PROPERTIES = INDEX_INPUT_PROPERTIES;
|
|
437
|
+
exports.OUI_DESCRIBE_ACTION = OUI_DESCRIBE_ACTION;
|
|
438
|
+
exports.OUI_EFFECT_ACCESS = OUI_EFFECT_ACCESS;
|
|
439
|
+
exports.OUI_EFFECT_KINDS = OUI_EFFECT_KINDS;
|
|
440
|
+
exports.OUI_PROTOCOL_EVENTS = OUI_PROTOCOL_EVENTS;
|
|
441
|
+
exports.OUI_READ_ACTION = OUI_READ_ACTION;
|
|
442
|
+
exports.OUI_RUNTIME_SURFACE = OUI_RUNTIME_SURFACE;
|
|
443
|
+
exports.argsHash = argsHash;
|
|
444
|
+
exports.atPointer = atPointer;
|
|
445
|
+
exports.canonicalJson = canonicalJson;
|
|
446
|
+
exports.constantOf = constantOf;
|
|
447
|
+
exports.definitionHash = definitionHash;
|
|
448
|
+
exports.discriminatorOf = discriminatorOf;
|
|
449
|
+
exports.effectAccess = effectAccess;
|
|
450
|
+
exports.firstSentence = firstSentence;
|
|
451
|
+
exports.fitObservations = fitObservations;
|
|
452
|
+
exports.indexEntry = indexEntry;
|
|
453
|
+
exports.jsonBytes = jsonBytes;
|
|
454
|
+
exports.pointerStep = pointerStep;
|
|
455
|
+
exports.pointerSteps = pointerSteps;
|
|
456
|
+
exports.requiresApproval = requiresApproval;
|
|
457
|
+
exports.sortedJson = sortedJson;
|
|
458
|
+
exports.summarizeInput = summarizeInput;
|
|
459
|
+
exports.surfaceIndex = surfaceIndex;
|
|
460
|
+
exports.surfacesHash = surfacesHash;
|
|
461
|
+
exports.utf8Length = utf8Length;
|
|
462
|
+
//# sourceMappingURL=chunk-2CPOHQGI.cjs.map
|
|
463
|
+
//# sourceMappingURL=chunk-2CPOHQGI.cjs.map
|