oui-spec 0.5.0 → 0.6.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.
Files changed (45) hide show
  1. package/README.md +18 -8
  2. package/dist/{chunk-XUX4XVP3.cjs → chunk-5UUJ3F7C.cjs} +49 -22
  3. package/dist/chunk-5UUJ3F7C.cjs.map +1 -0
  4. package/dist/{chunk-27CBVTA4.cjs → chunk-AFNWLRKV.cjs} +82 -18
  5. package/dist/chunk-AFNWLRKV.cjs.map +1 -0
  6. package/dist/{chunk-BNGJJM3T.js → chunk-B2EFZMGY.js} +40 -3
  7. package/dist/chunk-B2EFZMGY.js.map +1 -0
  8. package/dist/{chunk-2AISUNMW.cjs → chunk-LDECTDPU.cjs} +40 -2
  9. package/dist/chunk-LDECTDPU.cjs.map +1 -0
  10. package/dist/{chunk-UKKITNZK.js → chunk-R3SGP2UY.js} +49 -22
  11. package/dist/chunk-R3SGP2UY.js.map +1 -0
  12. package/dist/{chunk-6ZIKO7MG.js → chunk-VQETSIY5.js} +77 -13
  13. package/dist/chunk-VQETSIY5.js.map +1 -0
  14. package/dist/core/index.cjs +7 -7
  15. package/dist/core/index.d.cts +5 -5
  16. package/dist/core/index.d.ts +5 -5
  17. package/dist/core/index.js +3 -3
  18. package/dist/index.cjs +19 -15
  19. package/dist/index.d.cts +4 -3
  20. package/dist/index.d.ts +4 -3
  21. package/dist/index.js +3 -3
  22. package/dist/react/index.d.cts +3 -3
  23. package/dist/react/index.d.ts +3 -3
  24. package/dist/spec/index.cjs +13 -9
  25. package/dist/spec/index.d.cts +21 -360
  26. package/dist/spec/index.d.ts +21 -360
  27. package/dist/spec/index.js +1 -1
  28. package/dist/{surface-runtime-CS0rByFQ.d.ts → surface-runtime-CSZ9UtBO.d.cts} +10 -2
  29. package/dist/{surface-runtime-lqXn221P.d.cts → surface-runtime-D5nINY4b.d.ts} +10 -2
  30. package/dist/transport/index.cjs +2 -2
  31. package/dist/transport/index.d.cts +3 -3
  32. package/dist/transport/index.d.ts +3 -3
  33. package/dist/transport/index.js +1 -1
  34. package/dist/types-CmJb1BQ8.d.cts +403 -0
  35. package/dist/types-CmJb1BQ8.d.ts +403 -0
  36. package/dist/{websocket-YfWILdZ4.d.cts → websocket-Ce3tDSsJ.d.ts} +26 -7
  37. package/dist/{websocket-c5tIUWY5.d.ts → websocket-Ds2Rh_jL.d.cts} +26 -7
  38. package/package.json +1 -1
  39. package/spec/OUI-SPEC-v0.1.md +63 -7
  40. package/dist/chunk-27CBVTA4.cjs.map +0 -1
  41. package/dist/chunk-2AISUNMW.cjs.map +0 -1
  42. package/dist/chunk-6ZIKO7MG.js.map +0 -1
  43. package/dist/chunk-BNGJJM3T.js.map +0 -1
  44. package/dist/chunk-UKKITNZK.js.map +0 -1
  45. package/dist/chunk-XUX4XVP3.cjs.map +0 -1
@@ -117,6 +117,43 @@ function quote(text, path) {
117
117
  return JSON.stringify(text);
118
118
  }
119
119
 
120
- export { ARGS_HASH_PATTERN, OUI_EFFECT_ACCESS, OUI_EFFECT_KINDS, OUI_PROTOCOL_EVENTS, argsHash, canonicalJson, effectAccess, requiresApproval };
121
- //# sourceMappingURL=chunk-BNGJJM3T.js.map
122
- //# sourceMappingURL=chunk-BNGJJM3T.js.map
120
+ // src/spec/surfaces-hash.ts
121
+ var PREFIX = "fnv1a64:";
122
+ function surfacesHash(surfaces) {
123
+ return PREFIX + fnv1a64(sortedJson(surfaces));
124
+ }
125
+ function sortedJson(value) {
126
+ return JSON.stringify(sortKeys(value)) ?? "null";
127
+ }
128
+ function sortKeys(value) {
129
+ if (Array.isArray(value)) return value.map(sortKeys);
130
+ if (value && typeof value === "object") {
131
+ const out = {};
132
+ for (const key of Object.keys(value).sort()) {
133
+ const member = value[key];
134
+ if (member !== void 0) out[key] = sortKeys(member);
135
+ }
136
+ return out;
137
+ }
138
+ return value;
139
+ }
140
+ function fnv1a64(text) {
141
+ let hi = 3421674724;
142
+ let lo = 2216829733;
143
+ for (let i = 0; i < text.length; i++) {
144
+ lo = (lo ^ text.charCodeAt(i)) >>> 0;
145
+ const loProduct = lo * 435;
146
+ const carry = Math.floor(loProduct / 4294967296);
147
+ const newLo = loProduct >>> 0;
148
+ hi = Math.imul(hi, 435) + Math.imul(lo, 256) + carry >>> 0;
149
+ lo = newLo;
150
+ }
151
+ return hex32(hi) + hex32(lo);
152
+ }
153
+ function hex32(n) {
154
+ return (n >>> 0).toString(16).padStart(8, "0");
155
+ }
156
+
157
+ export { ARGS_HASH_PATTERN, OUI_EFFECT_ACCESS, OUI_EFFECT_KINDS, OUI_PROTOCOL_EVENTS, argsHash, canonicalJson, effectAccess, requiresApproval, surfacesHash };
158
+ //# sourceMappingURL=chunk-B2EFZMGY.js.map
159
+ //# sourceMappingURL=chunk-B2EFZMGY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/types.ts","../src/spec/approval.ts","../src/spec/surfaces-hash.ts"],"names":[],"mappings":"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","file":"chunk-B2EFZMGY.js","sourcesContent":["import type { OUIActionApproval, OUIEffectKind } from \"./approval.js\";\n\n/**\n * OUI — Open UI Specification\n *\n * The machine-readable contract for agent-controllable user interfaces.\n * Any UI application that implements this interface can be controlled by\n * any OUI-compatible agent runtime.\n */\n\n/** JSON Schema type (subset used by OUI) */\nexport type JSONSchema = {\n type?: string | string[];\n properties?: Record<string, JSONSchema>;\n items?: JSONSchema;\n required?: string[];\n enum?: (string | number | boolean)[];\n description?: string;\n default?: unknown;\n oneOf?: JSONSchema[];\n anyOf?: JSONSchema[];\n allOf?: JSONSchema[];\n const?: unknown;\n minimum?: number;\n maximum?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n format?: string;\n additionalProperties?: boolean | JSONSchema;\n};\n\n/**\n * A Surface is a controllable boundary within an application.\n * It declares what an agent can do (actions), what it can observe (state),\n * and when it's available (activation conditions).\n *\n * Think of it as \"one page\" or \"one feature\" of an application, described\n * in a way that an agent can understand and operate.\n */\nexport interface OUISurface {\n /** Unique identifier for this surface (e.g., 'dataviz-wizard', 'document-editor') */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** Description of what this surface does — included in agent context */\n description: string;\n\n /** Version of this surface's contract (semver) */\n version?: string;\n\n /** Actions the agent can invoke on this surface */\n actions: OUIAction[];\n\n /** Observable state the agent can read */\n observations?: OUIObservation[];\n\n /** When this surface is active/available */\n activation?: OUIActivation;\n\n /** Additional metadata for agent context */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Declarative polling configuration for async actions.\n * The OUI runtime in the browser manages the polling lifecycle automatically\n * after the action handler returns its dispatch result.\n */\nexport interface OUIActionPolling {\n /** How often to check (milliseconds) */\n intervalMs: number;\n\n /** Max poll attempts before giving up (optional) */\n maxAttempts?: number;\n\n /** Max total duration before giving up in ms (alternative to maxAttempts) */\n maxDurationMs?: number;\n\n /**\n * Subscribe to a realtime event instead of polling.\n * If provided, the runtime listens for this event instead of using setInterval.\n * More efficient when realtime infrastructure exists.\n */\n subscribe?: {\n /** Event name to listen for */\n event: string;\n /**\n * Filter to match the event. Values prefixed with '$dispatchResult.'\n * are resolved from the handler's return value.\n * Example: { jobId: '$dispatchResult.jobId' }\n */\n filter?: Record<string, unknown>;\n };\n}\n\n/**\n * An Action is a single operation an agent can perform on a surface.\n * Each action becomes a \"tool\" that the LLM can call.\n *\n * The action's handler executes in the UI (or API) and returns a result\n * that the agent sees as the tool's output.\n */\nexport interface OUIAction {\n /** Unique action identifier (becomes the tool name) */\n id: string;\n\n /** Human-readable description — this is what the LLM reads to decide when to use it */\n description: string;\n\n /** The action's name as a person reads it, for a confirmation the user approves. */\n title?: string;\n\n /**\n * What using the action does. A `transaction` runs only on the user's\n * approval of the exact request (see `requiresApproval`). Undeclared counts\n * as a write.\n */\n effect?: OUIEffectKind;\n\n /** JSON Schema for the action's input parameters */\n input: JSONSchema;\n\n /** JSON Schema for the action's return value (optional — agent still gets the result) */\n output?: JSONSchema;\n\n /**\n * Destructive: the action removes or replaces something the person made.\n * Unless its effect only reads, it runs only on the user's approval of the\n * exact request, which the surface runtime checks before it runs.\n */\n confirm?: boolean;\n\n /** If true, the action is async — returns immediately with a job/tracking ID */\n async?: boolean;\n\n /** Declarative polling configuration — runtime manages the polling lifecycle after dispatch */\n polling?: OUIActionPolling;\n\n /** Human-readable hint about when this action is appropriate to use */\n usage?: string;\n\n /**\n * Preconditions that should be met before this action is called.\n * Expressed as a description (for the LLM) not as executable logic.\n */\n preconditions?: string;\n\n /** Estimated duration hint (e.g., \"instant\", \"2-5s\", \"30-120s\") */\n estimatedDuration?: string;\n\n /** Tags for categorization */\n tags?: string[];\n}\n\n/**\n * An Observation is a piece of application state that the agent can read.\n * Observations are pushed to the agent in real-time as the app state changes,\n * giving it situational awareness without needing screenshots.\n */\nexport interface OUIObservation {\n /** Unique identifier for this observation */\n id: string;\n\n /** Description of what this observation represents */\n description: string;\n\n /** JSON Schema for the observation's value */\n schema: JSONSchema;\n\n /** How frequently this observation updates */\n updateFrequency?: \"realtime\" | \"on-change\" | \"polling\";\n}\n\n/**\n * Activation conditions — when a surface is available to the agent.\n * A surface might only be active on a specific page, when certain\n * entities are loaded, or under specific application states.\n */\nexport interface OUIActivation {\n /** URL path(s) where this surface is active */\n routes?: string | string[];\n\n /** Entity type/ID that must be present for this surface to activate */\n entity?: {\n type: string;\n id?: string;\n };\n\n /** Custom predicate description (for documentation, not execution) */\n condition?: string;\n}\n\n// ─── Protocol Types ──────────────────────────────────────────────────────────\n\n/**\n * A request from the agent runtime to execute an action on a surface.\n */\nexport interface OUIActionRequest {\n /** Unique request ID for correlation */\n requestId: string;\n\n /** Which surface this request targets */\n surfaceId: string;\n\n /** Which action to invoke */\n actionId: string;\n\n /** The action's input parameters (validated against the action's input schema) */\n params: Record<string, unknown>;\n\n /** Timestamp of the request */\n timestamp: number;\n\n /**\n * The user's approval this request runs on, for an action that needs one.\n * The surface runtime runs such an action only when this matches a grant\n * the tab received from the user's own confirmation, for exactly `params`.\n */\n approval?: OUIActionApproval;\n\n /**\n * The `surfacesHash` of the surfaces the agent runtime already holds for\n * this client: from its snapshot, or from an earlier result. A client whose\n * surfaces still hash to this answers without `surfaces`, so a result\n * carries the client's surfaces only when they changed (§7.3.4).\n */\n knownSurfaces?: string;\n}\n\n/**\n * Why a client's answer is shorter than the result it produced: the receiving\n * side refused the full answer (too large, or otherwise not accepted), so the\n * client sent it again without the parts named in `omitted`. The action's own\n * outcome — `success`, `error`, and `data` unless omitted — is still the one\n * that happened (§7.3.6).\n */\nexport interface OUIResultDelivery {\n trimmed: true;\n /** What the receiving side said when it refused the full answer. */\n reason: string;\n /** The fields left out of this answer. */\n omitted: Array<\"surfaces\" | \"observations\" | \"data\">;\n}\n\n/**\n * The result of an action execution, returned from the surface to the agent.\n */\nexport interface OUIActionResult {\n /** Correlates to the request */\n requestId: string;\n\n /** Whether the action succeeded */\n success: boolean;\n\n /** The action's return data (if successful) */\n data?: unknown;\n\n /** Error information (if failed) */\n error?: {\n code: string;\n message: string;\n details?: unknown;\n };\n\n /** Progress indicator for interim polling results (0-100) */\n progress?: number;\n\n /** Whether this is an interim update (polling still in progress) or final */\n interim?: boolean;\n\n /** Duration of execution in milliseconds */\n durationMs?: number;\n\n /** Timestamp of the result */\n timestamp: number;\n\n /**\n * The surfaces active on the client once the action's effects settled.\n *\n * An action can change what the agent is able to do next: navigating\n * unmounts one page's surfaces and mounts another's. Without this, the agent\n * would act on the capability set it had before the action, which is the\n * set the action just replaced.\n */\n surfaces?: OUISurface[];\n\n /** Latest observation values per surface once the action's effects settled. */\n observations?: OUIObservationSnapshot;\n\n /**\n * The hash of the client's surfaces once the action's effects settled\n * (`surfacesHash`). Always present from a client that sends it; `surfaces`\n * is then present only when this differs from the request's\n * `knownSurfaces`, and an agent runtime that holds surfaces with this hash\n * keeps using them.\n */\n surfacesHash?: string;\n\n /** Present when this answer was trimmed to be delivered (§7.3.6). */\n delivery?: OUIResultDelivery;\n\n /**\n * False when the client's deadline passed before its UI settled, so\n * `surfaces` and `observations` may still be changing. Absent or true\n * otherwise.\n */\n settled?: boolean;\n}\n\n/** Latest observation values, keyed by surface id and then observation id. */\nexport type OUIObservationSnapshot = Record<string, Record<string, unknown>>;\n\n/**\n * What a client can do right now: the surfaces it has mounted and their latest\n * observation values. A client sends this to its agent runtime (for example,\n * with each turn), and returns it on every action result.\n */\nexport interface OUISurfaceSnapshot {\n surfaces: OUISurface[];\n observations: OUIObservationSnapshot;\n /**\n * `surfacesHash(surfaces)`: what an agent runtime sends back as a request's\n * `knownSurfaces`, so the client's answer need not repeat surfaces the\n * runtime already holds.\n */\n surfacesHash?: string;\n}\n\n/**\n * An observation update pushed from the surface to the agent.\n */\nexport interface OUIObservationUpdate {\n /** Which surface this observation belongs to */\n surfaceId: string;\n\n /** Which observation updated */\n observationId: string;\n\n /** The new value */\n value: unknown;\n\n /** Timestamp of the update */\n timestamp: number;\n}\n\n/**\n * Surface registration event — sent when a surface becomes active.\n */\nexport interface OUISurfaceRegistration {\n /** The full surface manifest */\n surface: OUISurface;\n\n /** Timestamp of registration */\n timestamp: number;\n}\n\n/**\n * Surface deregistration event — sent when a surface becomes inactive.\n */\nexport interface OUISurfaceDeregistration {\n /** Which surface is being deregistered */\n surfaceId: string;\n\n /** Timestamp */\n timestamp: number;\n}\n\n// ─── Transport Protocol Events ───────────────────────────────────────────────\n\n/**\n * All protocol events exchanged between agent runtime and surfaces.\n */\nexport type OUIProtocolEvent =\n | { type: \"surface:register\"; payload: OUISurfaceRegistration }\n | { type: \"surface:deregister\"; payload: OUISurfaceDeregistration }\n | { type: \"action:request\"; payload: OUIActionRequest }\n | { type: \"action:result\"; payload: OUIActionResult }\n | { type: \"observation:update\"; payload: OUIObservationUpdate };\n\n/**\n * Protocol event type strings (for type guards and routing).\n */\nexport const OUI_PROTOCOL_EVENTS = {\n SURFACE_REGISTER: \"surface:register\",\n SURFACE_DEREGISTER: \"surface:deregister\",\n ACTION_REQUEST: \"action:request\",\n ACTION_RESULT: \"action:result\",\n OBSERVATION_UPDATE: \"observation:update\",\n} as const;\n","/**\n * Approvals: an irreversible action runs only on an approval the user gave,\n * bound to the exact request.\n *\n * An agent runtime stops at an action that needs approval and shows the user\n * a confirmation. Its approval store issues a single-use grant when the user\n * confirms, and the request that then reaches the tab carries `approval:\n * { approvalId, argsHash }`. The surface runtime runs the action only when\n * that matches a grant this tab received from the user's own confirmation.\n *\n * Everything that checks a request computes the same args hash: SHA-256 over\n * the RFC 8785 (JCS) canonical JSON of its params, lowercase hex. The\n * published vectors (`oui-spec/approval-vectors.json`) hold every\n * implementation, in any language, to it.\n */\n\n/**\n * What using an action does. One vocabulary for every kind of action, so an\n * action is told apart by what it changes:\n *\n * | Effect | What it changes | Access |\n * |---|---|---|\n * | `view`, `selection`, `navigate`, `open` | What is shown | read |\n * | `edit` | The document, one undo step | write |\n * | `file` | Imports or exports a file | write |\n * | `mutate` | Backend data, through an API operation | write |\n * | `job` | Starts work that outlives the call | write |\n * | `transaction` | An irreversible external act: an order, a payment, a send, a publish | write, approved |\n */\nexport type OUIEffectKind =\n | \"view\"\n | \"selection\"\n | \"navigate\"\n | \"open\"\n | \"edit\"\n | \"file\"\n | \"mutate\"\n | \"job\"\n | \"transaction\";\n\n/** Every effect, in the order of the table above. */\nexport const OUI_EFFECT_KINDS: readonly OUIEffectKind[] = [\n \"view\",\n \"selection\",\n \"navigate\",\n \"open\",\n \"edit\",\n \"file\",\n \"mutate\",\n \"job\",\n \"transaction\",\n];\n\n/** Whether an effect only changes what is shown, or changes something. */\nexport const OUI_EFFECT_ACCESS: Readonly<\n Record<OUIEffectKind, \"read\" | \"write\">\n> = {\n view: \"read\",\n selection: \"read\",\n navigate: \"read\",\n open: \"read\",\n edit: \"write\",\n file: \"write\",\n mutate: \"write\",\n job: \"write\",\n transaction: \"write\",\n};\n\n/** `read` or `write`. An action that declares no effect is a `write`: nothing says it only shows. */\nexport function effectAccess(\n effect: OUIEffectKind | undefined,\n): \"read\" | \"write\" {\n return effect === undefined ? \"write\" : OUI_EFFECT_ACCESS[effect];\n}\n\n/**\n * Whether running an action needs the user's approval of the exact request: a\n * `transaction` always, and a `write` declared destructive. An agent\n * runtime's policy may require it of more; nothing waives these.\n */\nexport function requiresApproval(\n effect: OUIEffectKind | undefined,\n destructive?: boolean,\n): boolean {\n if (effect === \"transaction\") return true;\n return !!destructive && effectAccess(effect) === \"write\";\n}\n\n/** What a request for an approved action carries. */\nexport interface OUIActionApproval {\n approvalId: string;\n /** `argsHash(params)` of the request the user approved. */\n argsHash: string;\n}\n\n/** A grant this tab holds: the user approved this request here. */\nexport interface OUIApprovalGrant extends OUIActionApproval {\n /** Epoch ms after which the grant no longer admits anything. */\n expiresAt: number;\n}\n\n/** An args hash: 64 lowercase hex digits. */\nexport const ARGS_HASH_PATTERN = /^[0-9a-f]{64}$/;\n\n/**\n * The RFC 8785 canonical form of a JSON value:\n * - object members sorted by their names' UTF-16 code units, with no\n * whitespace;\n * - numbers in ECMAScript's shortest round-trip form (`-0` is `0`);\n * - strings escaped as ECMAScript's `JSON.stringify` escapes them.\n *\n * The value must be I-JSON (RFC 7493): anything JSON cannot carry — `NaN`,\n * infinities, `undefined` outside an object member, a bigint, a function, a\n * symbol, a class instance, or a string with a lone surrogate — is refused\n * rather than hashed as something the other end never receives. An object\n * member whose value is `undefined` is left out, as it is on the wire.\n */\nexport function canonicalJson(value: unknown): string {\n return serialize(value, \"$\");\n}\n\n/** SHA-256 of the canonical JSON of a request's params, lowercase hex. Runs wherever WebCrypto does. */\nexport async function argsHash(params: unknown): Promise<string> {\n const canonical = canonicalJson(params);\n const subtle = await webCrypto();\n const digest = await subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(canonical),\n );\n let hex = \"\";\n for (const byte of new Uint8Array(digest))\n hex += byte.toString(16).padStart(2, \"0\");\n return hex;\n}\n\n/**\n * WebCrypto: global in browsers and Node 19+; Node 18 has it only on\n * `node:crypto`, imported here by a specifier bundlers leave alone.\n */\nasync function webCrypto(): Promise<SubtleCrypto> {\n const global = globalThis.crypto?.subtle;\n if (global) return global;\n const nodeCrypto = \"node:crypto\";\n try {\n const mod = (await import(/* @vite-ignore */ nodeCrypto)) as {\n webcrypto?: { subtle?: SubtleCrypto };\n };\n if (mod.webcrypto?.subtle) return mod.webcrypto.subtle;\n } catch {\n // Not Node: fall through to the error below.\n }\n throw new Error(\n \"[OUI] argsHash needs WebCrypto (crypto.subtle), which this runtime does not provide\",\n );\n}\n\nfunction refuse(path: string, what: string): never {\n throw new TypeError(`[OUI] ${path} is not I-JSON: ${what}`);\n}\n\nfunction serialize(value: unknown, path: string): string {\n switch (typeof value) {\n case \"string\":\n return quote(value, path);\n case \"number\":\n if (!Number.isFinite(value)) refuse(path, String(value));\n // ECMAScript's Number-to-string is RFC 8785's number form, and prints -0 as 0.\n return JSON.stringify(value);\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"object\": {\n if (value === null) return \"null\";\n if (Array.isArray(value)) {\n return `[${value.map((item, i) => serialize(item, `${path}[${i}]`)).join(\",\")}]`;\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n refuse(path, `a ${proto?.constructor?.name ?? \"non-plain\"} object`);\n }\n const record = value as Record<string, unknown>;\n // Default string order compares UTF-16 code units, which is RFC 8785's order.\n const keys = Object.keys(record)\n .filter((k) => record[k] !== undefined)\n .sort();\n return `{${keys\n .map(\n (k) =>\n `${quote(k, `${path} key`)}:${serialize(record[k], `${path}.${k}`)}`,\n )\n .join(\",\")}}`;\n }\n default:\n return refuse(path, typeof value);\n }\n}\n\nfunction quote(text: string, path: string): string {\n // I-JSON strings are well-formed Unicode: a lone surrogate has no UTF-8 form.\n for (let i = 0; i < text.length; i++) {\n const unit = text.charCodeAt(i);\n if (unit >= 0xd800 && unit <= 0xdbff) {\n const next = text.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n i++;\n continue;\n }\n refuse(path, `a lone surrogate at ${i}`);\n }\n if (unit >= 0xdc00 && unit <= 0xdfff)\n refuse(path, `a lone surrogate at ${i}`);\n }\n return JSON.stringify(text);\n}\n","/**\n * The identity of a set of surfaces, for an answer that need not repeat them\n * (§7.3.4).\n *\n * A client's surfaces are what an agent may do; every result used to carry\n * them all, so each answer weighed what the page's whole manifest weighs —\n * on a studio page with a hundred and fifty actions, about 265 KB for a click.\n * A relay that caps a frame then drops whole answers, and the agent hears\n * nothing. With this hash the agent runtime says which surfaces it holds\n * (`knownSurfaces`), and the client sends its surfaces only when they differ.\n *\n * The hash is of the surfaces' canonical JSON (object keys sorted), so it does\n * not depend on the order a manifest's fields were written in, and is the\n * same wherever it is computed. It is a 64-bit FNV-1a over the JSON's UTF-16\n * code units, kept as two 32-bit halves: not a security measure — the agent\n * runtime is told the surfaces by the same client that hashes them — but\n * collision-resistant enough that two different capability sets of one page\n * never share one.\n */\nimport type { OUISurface } from \"./types.js\";\n\n/** The prefix names the algorithm, so a different one is a different hash rather than a false match. */\nconst PREFIX = \"fnv1a64:\";\n\n/** `surfaces`' hash, as a result's `surfacesHash` and a request's `knownSurfaces` carry it. */\nexport function surfacesHash(surfaces: readonly OUISurface[]): string {\n return PREFIX + fnv1a64(sortedJson(surfaces));\n}\n\n/**\n * JSON with every object's keys sorted, and what JSON cannot carry dropped as\n * JSON.stringify drops it. Not approval.ts's `canonicalJson`, which refuses\n * such values on purpose (an approval must hash exactly what crosses the\n * wire): a snapshot is taken on every answer, and must never throw on a\n * manifest that carries, say, an `undefined` in an array.\n */\nfunction sortedJson(value: unknown): string {\n return JSON.stringify(sortKeys(value)) ?? \"null\";\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n const member = (value as Record<string, unknown>)[key];\n if (member !== undefined) out[key] = sortKeys(member);\n }\n return out;\n }\n return value;\n}\n\n/**\n * 64-bit FNV-1a (offset 0xcbf29ce484222325, prime 0x100000001b3) in two 32-bit\n * halves, so it runs without BigInt and stays fast on a few hundred KB.\n */\nexport function fnv1a64(text: string): string {\n // The 64-bit offset basis, as high and low 32-bit halves.\n let hi = 0xcbf29ce4;\n let lo = 0x84222325;\n for (let i = 0; i < text.length; i++) {\n lo = (lo ^ text.charCodeAt(i)) >>> 0;\n // Multiply (hi:lo) by the prime 0x00000100_000001b3, keeping 64 bits:\n // lo*0x1b3, and hi*0x1b3 + lo*0x100 carried into the high half.\n const loProduct = lo * 0x1b3;\n const carry = Math.floor(loProduct / 0x100000000);\n const newLo = loProduct >>> 0;\n hi = (Math.imul(hi, 0x1b3) + Math.imul(lo, 0x100) + carry) >>> 0;\n lo = newLo;\n }\n return hex32(hi) + hex32(lo);\n}\n\nfunction hex32(n: number): string {\n return (n >>> 0).toString(16).padStart(8, \"0\");\n}\n"]}
@@ -119,6 +119,43 @@ function quote(text, path) {
119
119
  return JSON.stringify(text);
120
120
  }
121
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
+
122
159
  exports.ARGS_HASH_PATTERN = ARGS_HASH_PATTERN;
123
160
  exports.OUI_EFFECT_ACCESS = OUI_EFFECT_ACCESS;
124
161
  exports.OUI_EFFECT_KINDS = OUI_EFFECT_KINDS;
@@ -127,5 +164,6 @@ exports.argsHash = argsHash;
127
164
  exports.canonicalJson = canonicalJson;
128
165
  exports.effectAccess = effectAccess;
129
166
  exports.requiresApproval = requiresApproval;
130
- //# sourceMappingURL=chunk-2AISUNMW.cjs.map
131
- //# sourceMappingURL=chunk-2AISUNMW.cjs.map
167
+ exports.surfacesHash = surfacesHash;
168
+ //# sourceMappingURL=chunk-LDECTDPU.cjs.map
169
+ //# sourceMappingURL=chunk-LDECTDPU.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/types.ts","../src/spec/approval.ts","../src/spec/surfaces-hash.ts"],"names":[],"mappings":"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","file":"chunk-LDECTDPU.cjs","sourcesContent":["import type { OUIActionApproval, OUIEffectKind } from \"./approval.js\";\n\n/**\n * OUI — Open UI Specification\n *\n * The machine-readable contract for agent-controllable user interfaces.\n * Any UI application that implements this interface can be controlled by\n * any OUI-compatible agent runtime.\n */\n\n/** JSON Schema type (subset used by OUI) */\nexport type JSONSchema = {\n type?: string | string[];\n properties?: Record<string, JSONSchema>;\n items?: JSONSchema;\n required?: string[];\n enum?: (string | number | boolean)[];\n description?: string;\n default?: unknown;\n oneOf?: JSONSchema[];\n anyOf?: JSONSchema[];\n allOf?: JSONSchema[];\n const?: unknown;\n minimum?: number;\n maximum?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n format?: string;\n additionalProperties?: boolean | JSONSchema;\n};\n\n/**\n * A Surface is a controllable boundary within an application.\n * It declares what an agent can do (actions), what it can observe (state),\n * and when it's available (activation conditions).\n *\n * Think of it as \"one page\" or \"one feature\" of an application, described\n * in a way that an agent can understand and operate.\n */\nexport interface OUISurface {\n /** Unique identifier for this surface (e.g., 'dataviz-wizard', 'document-editor') */\n id: string;\n\n /** Human-readable name */\n name: string;\n\n /** Description of what this surface does — included in agent context */\n description: string;\n\n /** Version of this surface's contract (semver) */\n version?: string;\n\n /** Actions the agent can invoke on this surface */\n actions: OUIAction[];\n\n /** Observable state the agent can read */\n observations?: OUIObservation[];\n\n /** When this surface is active/available */\n activation?: OUIActivation;\n\n /** Additional metadata for agent context */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Declarative polling configuration for async actions.\n * The OUI runtime in the browser manages the polling lifecycle automatically\n * after the action handler returns its dispatch result.\n */\nexport interface OUIActionPolling {\n /** How often to check (milliseconds) */\n intervalMs: number;\n\n /** Max poll attempts before giving up (optional) */\n maxAttempts?: number;\n\n /** Max total duration before giving up in ms (alternative to maxAttempts) */\n maxDurationMs?: number;\n\n /**\n * Subscribe to a realtime event instead of polling.\n * If provided, the runtime listens for this event instead of using setInterval.\n * More efficient when realtime infrastructure exists.\n */\n subscribe?: {\n /** Event name to listen for */\n event: string;\n /**\n * Filter to match the event. Values prefixed with '$dispatchResult.'\n * are resolved from the handler's return value.\n * Example: { jobId: '$dispatchResult.jobId' }\n */\n filter?: Record<string, unknown>;\n };\n}\n\n/**\n * An Action is a single operation an agent can perform on a surface.\n * Each action becomes a \"tool\" that the LLM can call.\n *\n * The action's handler executes in the UI (or API) and returns a result\n * that the agent sees as the tool's output.\n */\nexport interface OUIAction {\n /** Unique action identifier (becomes the tool name) */\n id: string;\n\n /** Human-readable description — this is what the LLM reads to decide when to use it */\n description: string;\n\n /** The action's name as a person reads it, for a confirmation the user approves. */\n title?: string;\n\n /**\n * What using the action does. A `transaction` runs only on the user's\n * approval of the exact request (see `requiresApproval`). Undeclared counts\n * as a write.\n */\n effect?: OUIEffectKind;\n\n /** JSON Schema for the action's input parameters */\n input: JSONSchema;\n\n /** JSON Schema for the action's return value (optional — agent still gets the result) */\n output?: JSONSchema;\n\n /**\n * Destructive: the action removes or replaces something the person made.\n * Unless its effect only reads, it runs only on the user's approval of the\n * exact request, which the surface runtime checks before it runs.\n */\n confirm?: boolean;\n\n /** If true, the action is async — returns immediately with a job/tracking ID */\n async?: boolean;\n\n /** Declarative polling configuration — runtime manages the polling lifecycle after dispatch */\n polling?: OUIActionPolling;\n\n /** Human-readable hint about when this action is appropriate to use */\n usage?: string;\n\n /**\n * Preconditions that should be met before this action is called.\n * Expressed as a description (for the LLM) not as executable logic.\n */\n preconditions?: string;\n\n /** Estimated duration hint (e.g., \"instant\", \"2-5s\", \"30-120s\") */\n estimatedDuration?: string;\n\n /** Tags for categorization */\n tags?: string[];\n}\n\n/**\n * An Observation is a piece of application state that the agent can read.\n * Observations are pushed to the agent in real-time as the app state changes,\n * giving it situational awareness without needing screenshots.\n */\nexport interface OUIObservation {\n /** Unique identifier for this observation */\n id: string;\n\n /** Description of what this observation represents */\n description: string;\n\n /** JSON Schema for the observation's value */\n schema: JSONSchema;\n\n /** How frequently this observation updates */\n updateFrequency?: \"realtime\" | \"on-change\" | \"polling\";\n}\n\n/**\n * Activation conditions — when a surface is available to the agent.\n * A surface might only be active on a specific page, when certain\n * entities are loaded, or under specific application states.\n */\nexport interface OUIActivation {\n /** URL path(s) where this surface is active */\n routes?: string | string[];\n\n /** Entity type/ID that must be present for this surface to activate */\n entity?: {\n type: string;\n id?: string;\n };\n\n /** Custom predicate description (for documentation, not execution) */\n condition?: string;\n}\n\n// ─── Protocol Types ──────────────────────────────────────────────────────────\n\n/**\n * A request from the agent runtime to execute an action on a surface.\n */\nexport interface OUIActionRequest {\n /** Unique request ID for correlation */\n requestId: string;\n\n /** Which surface this request targets */\n surfaceId: string;\n\n /** Which action to invoke */\n actionId: string;\n\n /** The action's input parameters (validated against the action's input schema) */\n params: Record<string, unknown>;\n\n /** Timestamp of the request */\n timestamp: number;\n\n /**\n * The user's approval this request runs on, for an action that needs one.\n * The surface runtime runs such an action only when this matches a grant\n * the tab received from the user's own confirmation, for exactly `params`.\n */\n approval?: OUIActionApproval;\n\n /**\n * The `surfacesHash` of the surfaces the agent runtime already holds for\n * this client: from its snapshot, or from an earlier result. A client whose\n * surfaces still hash to this answers without `surfaces`, so a result\n * carries the client's surfaces only when they changed (§7.3.4).\n */\n knownSurfaces?: string;\n}\n\n/**\n * Why a client's answer is shorter than the result it produced: the receiving\n * side refused the full answer (too large, or otherwise not accepted), so the\n * client sent it again without the parts named in `omitted`. The action's own\n * outcome — `success`, `error`, and `data` unless omitted — is still the one\n * that happened (§7.3.6).\n */\nexport interface OUIResultDelivery {\n trimmed: true;\n /** What the receiving side said when it refused the full answer. */\n reason: string;\n /** The fields left out of this answer. */\n omitted: Array<\"surfaces\" | \"observations\" | \"data\">;\n}\n\n/**\n * The result of an action execution, returned from the surface to the agent.\n */\nexport interface OUIActionResult {\n /** Correlates to the request */\n requestId: string;\n\n /** Whether the action succeeded */\n success: boolean;\n\n /** The action's return data (if successful) */\n data?: unknown;\n\n /** Error information (if failed) */\n error?: {\n code: string;\n message: string;\n details?: unknown;\n };\n\n /** Progress indicator for interim polling results (0-100) */\n progress?: number;\n\n /** Whether this is an interim update (polling still in progress) or final */\n interim?: boolean;\n\n /** Duration of execution in milliseconds */\n durationMs?: number;\n\n /** Timestamp of the result */\n timestamp: number;\n\n /**\n * The surfaces active on the client once the action's effects settled.\n *\n * An action can change what the agent is able to do next: navigating\n * unmounts one page's surfaces and mounts another's. Without this, the agent\n * would act on the capability set it had before the action, which is the\n * set the action just replaced.\n */\n surfaces?: OUISurface[];\n\n /** Latest observation values per surface once the action's effects settled. */\n observations?: OUIObservationSnapshot;\n\n /**\n * The hash of the client's surfaces once the action's effects settled\n * (`surfacesHash`). Always present from a client that sends it; `surfaces`\n * is then present only when this differs from the request's\n * `knownSurfaces`, and an agent runtime that holds surfaces with this hash\n * keeps using them.\n */\n surfacesHash?: string;\n\n /** Present when this answer was trimmed to be delivered (§7.3.6). */\n delivery?: OUIResultDelivery;\n\n /**\n * False when the client's deadline passed before its UI settled, so\n * `surfaces` and `observations` may still be changing. Absent or true\n * otherwise.\n */\n settled?: boolean;\n}\n\n/** Latest observation values, keyed by surface id and then observation id. */\nexport type OUIObservationSnapshot = Record<string, Record<string, unknown>>;\n\n/**\n * What a client can do right now: the surfaces it has mounted and their latest\n * observation values. A client sends this to its agent runtime (for example,\n * with each turn), and returns it on every action result.\n */\nexport interface OUISurfaceSnapshot {\n surfaces: OUISurface[];\n observations: OUIObservationSnapshot;\n /**\n * `surfacesHash(surfaces)`: what an agent runtime sends back as a request's\n * `knownSurfaces`, so the client's answer need not repeat surfaces the\n * runtime already holds.\n */\n surfacesHash?: string;\n}\n\n/**\n * An observation update pushed from the surface to the agent.\n */\nexport interface OUIObservationUpdate {\n /** Which surface this observation belongs to */\n surfaceId: string;\n\n /** Which observation updated */\n observationId: string;\n\n /** The new value */\n value: unknown;\n\n /** Timestamp of the update */\n timestamp: number;\n}\n\n/**\n * Surface registration event — sent when a surface becomes active.\n */\nexport interface OUISurfaceRegistration {\n /** The full surface manifest */\n surface: OUISurface;\n\n /** Timestamp of registration */\n timestamp: number;\n}\n\n/**\n * Surface deregistration event — sent when a surface becomes inactive.\n */\nexport interface OUISurfaceDeregistration {\n /** Which surface is being deregistered */\n surfaceId: string;\n\n /** Timestamp */\n timestamp: number;\n}\n\n// ─── Transport Protocol Events ───────────────────────────────────────────────\n\n/**\n * All protocol events exchanged between agent runtime and surfaces.\n */\nexport type OUIProtocolEvent =\n | { type: \"surface:register\"; payload: OUISurfaceRegistration }\n | { type: \"surface:deregister\"; payload: OUISurfaceDeregistration }\n | { type: \"action:request\"; payload: OUIActionRequest }\n | { type: \"action:result\"; payload: OUIActionResult }\n | { type: \"observation:update\"; payload: OUIObservationUpdate };\n\n/**\n * Protocol event type strings (for type guards and routing).\n */\nexport const OUI_PROTOCOL_EVENTS = {\n SURFACE_REGISTER: \"surface:register\",\n SURFACE_DEREGISTER: \"surface:deregister\",\n ACTION_REQUEST: \"action:request\",\n ACTION_RESULT: \"action:result\",\n OBSERVATION_UPDATE: \"observation:update\",\n} as const;\n","/**\n * Approvals: an irreversible action runs only on an approval the user gave,\n * bound to the exact request.\n *\n * An agent runtime stops at an action that needs approval and shows the user\n * a confirmation. Its approval store issues a single-use grant when the user\n * confirms, and the request that then reaches the tab carries `approval:\n * { approvalId, argsHash }`. The surface runtime runs the action only when\n * that matches a grant this tab received from the user's own confirmation.\n *\n * Everything that checks a request computes the same args hash: SHA-256 over\n * the RFC 8785 (JCS) canonical JSON of its params, lowercase hex. The\n * published vectors (`oui-spec/approval-vectors.json`) hold every\n * implementation, in any language, to it.\n */\n\n/**\n * What using an action does. One vocabulary for every kind of action, so an\n * action is told apart by what it changes:\n *\n * | Effect | What it changes | Access |\n * |---|---|---|\n * | `view`, `selection`, `navigate`, `open` | What is shown | read |\n * | `edit` | The document, one undo step | write |\n * | `file` | Imports or exports a file | write |\n * | `mutate` | Backend data, through an API operation | write |\n * | `job` | Starts work that outlives the call | write |\n * | `transaction` | An irreversible external act: an order, a payment, a send, a publish | write, approved |\n */\nexport type OUIEffectKind =\n | \"view\"\n | \"selection\"\n | \"navigate\"\n | \"open\"\n | \"edit\"\n | \"file\"\n | \"mutate\"\n | \"job\"\n | \"transaction\";\n\n/** Every effect, in the order of the table above. */\nexport const OUI_EFFECT_KINDS: readonly OUIEffectKind[] = [\n \"view\",\n \"selection\",\n \"navigate\",\n \"open\",\n \"edit\",\n \"file\",\n \"mutate\",\n \"job\",\n \"transaction\",\n];\n\n/** Whether an effect only changes what is shown, or changes something. */\nexport const OUI_EFFECT_ACCESS: Readonly<\n Record<OUIEffectKind, \"read\" | \"write\">\n> = {\n view: \"read\",\n selection: \"read\",\n navigate: \"read\",\n open: \"read\",\n edit: \"write\",\n file: \"write\",\n mutate: \"write\",\n job: \"write\",\n transaction: \"write\",\n};\n\n/** `read` or `write`. An action that declares no effect is a `write`: nothing says it only shows. */\nexport function effectAccess(\n effect: OUIEffectKind | undefined,\n): \"read\" | \"write\" {\n return effect === undefined ? \"write\" : OUI_EFFECT_ACCESS[effect];\n}\n\n/**\n * Whether running an action needs the user's approval of the exact request: a\n * `transaction` always, and a `write` declared destructive. An agent\n * runtime's policy may require it of more; nothing waives these.\n */\nexport function requiresApproval(\n effect: OUIEffectKind | undefined,\n destructive?: boolean,\n): boolean {\n if (effect === \"transaction\") return true;\n return !!destructive && effectAccess(effect) === \"write\";\n}\n\n/** What a request for an approved action carries. */\nexport interface OUIActionApproval {\n approvalId: string;\n /** `argsHash(params)` of the request the user approved. */\n argsHash: string;\n}\n\n/** A grant this tab holds: the user approved this request here. */\nexport interface OUIApprovalGrant extends OUIActionApproval {\n /** Epoch ms after which the grant no longer admits anything. */\n expiresAt: number;\n}\n\n/** An args hash: 64 lowercase hex digits. */\nexport const ARGS_HASH_PATTERN = /^[0-9a-f]{64}$/;\n\n/**\n * The RFC 8785 canonical form of a JSON value:\n * - object members sorted by their names' UTF-16 code units, with no\n * whitespace;\n * - numbers in ECMAScript's shortest round-trip form (`-0` is `0`);\n * - strings escaped as ECMAScript's `JSON.stringify` escapes them.\n *\n * The value must be I-JSON (RFC 7493): anything JSON cannot carry — `NaN`,\n * infinities, `undefined` outside an object member, a bigint, a function, a\n * symbol, a class instance, or a string with a lone surrogate — is refused\n * rather than hashed as something the other end never receives. An object\n * member whose value is `undefined` is left out, as it is on the wire.\n */\nexport function canonicalJson(value: unknown): string {\n return serialize(value, \"$\");\n}\n\n/** SHA-256 of the canonical JSON of a request's params, lowercase hex. Runs wherever WebCrypto does. */\nexport async function argsHash(params: unknown): Promise<string> {\n const canonical = canonicalJson(params);\n const subtle = await webCrypto();\n const digest = await subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(canonical),\n );\n let hex = \"\";\n for (const byte of new Uint8Array(digest))\n hex += byte.toString(16).padStart(2, \"0\");\n return hex;\n}\n\n/**\n * WebCrypto: global in browsers and Node 19+; Node 18 has it only on\n * `node:crypto`, imported here by a specifier bundlers leave alone.\n */\nasync function webCrypto(): Promise<SubtleCrypto> {\n const global = globalThis.crypto?.subtle;\n if (global) return global;\n const nodeCrypto = \"node:crypto\";\n try {\n const mod = (await import(/* @vite-ignore */ nodeCrypto)) as {\n webcrypto?: { subtle?: SubtleCrypto };\n };\n if (mod.webcrypto?.subtle) return mod.webcrypto.subtle;\n } catch {\n // Not Node: fall through to the error below.\n }\n throw new Error(\n \"[OUI] argsHash needs WebCrypto (crypto.subtle), which this runtime does not provide\",\n );\n}\n\nfunction refuse(path: string, what: string): never {\n throw new TypeError(`[OUI] ${path} is not I-JSON: ${what}`);\n}\n\nfunction serialize(value: unknown, path: string): string {\n switch (typeof value) {\n case \"string\":\n return quote(value, path);\n case \"number\":\n if (!Number.isFinite(value)) refuse(path, String(value));\n // ECMAScript's Number-to-string is RFC 8785's number form, and prints -0 as 0.\n return JSON.stringify(value);\n case \"boolean\":\n return value ? \"true\" : \"false\";\n case \"object\": {\n if (value === null) return \"null\";\n if (Array.isArray(value)) {\n return `[${value.map((item, i) => serialize(item, `${path}[${i}]`)).join(\",\")}]`;\n }\n const proto = Object.getPrototypeOf(value);\n if (proto !== Object.prototype && proto !== null) {\n refuse(path, `a ${proto?.constructor?.name ?? \"non-plain\"} object`);\n }\n const record = value as Record<string, unknown>;\n // Default string order compares UTF-16 code units, which is RFC 8785's order.\n const keys = Object.keys(record)\n .filter((k) => record[k] !== undefined)\n .sort();\n return `{${keys\n .map(\n (k) =>\n `${quote(k, `${path} key`)}:${serialize(record[k], `${path}.${k}`)}`,\n )\n .join(\",\")}}`;\n }\n default:\n return refuse(path, typeof value);\n }\n}\n\nfunction quote(text: string, path: string): string {\n // I-JSON strings are well-formed Unicode: a lone surrogate has no UTF-8 form.\n for (let i = 0; i < text.length; i++) {\n const unit = text.charCodeAt(i);\n if (unit >= 0xd800 && unit <= 0xdbff) {\n const next = text.charCodeAt(i + 1);\n if (next >= 0xdc00 && next <= 0xdfff) {\n i++;\n continue;\n }\n refuse(path, `a lone surrogate at ${i}`);\n }\n if (unit >= 0xdc00 && unit <= 0xdfff)\n refuse(path, `a lone surrogate at ${i}`);\n }\n return JSON.stringify(text);\n}\n","/**\n * The identity of a set of surfaces, for an answer that need not repeat them\n * (§7.3.4).\n *\n * A client's surfaces are what an agent may do; every result used to carry\n * them all, so each answer weighed what the page's whole manifest weighs —\n * on a studio page with a hundred and fifty actions, about 265 KB for a click.\n * A relay that caps a frame then drops whole answers, and the agent hears\n * nothing. With this hash the agent runtime says which surfaces it holds\n * (`knownSurfaces`), and the client sends its surfaces only when they differ.\n *\n * The hash is of the surfaces' canonical JSON (object keys sorted), so it does\n * not depend on the order a manifest's fields were written in, and is the\n * same wherever it is computed. It is a 64-bit FNV-1a over the JSON's UTF-16\n * code units, kept as two 32-bit halves: not a security measure — the agent\n * runtime is told the surfaces by the same client that hashes them — but\n * collision-resistant enough that two different capability sets of one page\n * never share one.\n */\nimport type { OUISurface } from \"./types.js\";\n\n/** The prefix names the algorithm, so a different one is a different hash rather than a false match. */\nconst PREFIX = \"fnv1a64:\";\n\n/** `surfaces`' hash, as a result's `surfacesHash` and a request's `knownSurfaces` carry it. */\nexport function surfacesHash(surfaces: readonly OUISurface[]): string {\n return PREFIX + fnv1a64(sortedJson(surfaces));\n}\n\n/**\n * JSON with every object's keys sorted, and what JSON cannot carry dropped as\n * JSON.stringify drops it. Not approval.ts's `canonicalJson`, which refuses\n * such values on purpose (an approval must hash exactly what crosses the\n * wire): a snapshot is taken on every answer, and must never throw on a\n * manifest that carries, say, an `undefined` in an array.\n */\nfunction sortedJson(value: unknown): string {\n return JSON.stringify(sortKeys(value)) ?? \"null\";\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(sortKeys);\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value as Record<string, unknown>).sort()) {\n const member = (value as Record<string, unknown>)[key];\n if (member !== undefined) out[key] = sortKeys(member);\n }\n return out;\n }\n return value;\n}\n\n/**\n * 64-bit FNV-1a (offset 0xcbf29ce484222325, prime 0x100000001b3) in two 32-bit\n * halves, so it runs without BigInt and stays fast on a few hundred KB.\n */\nexport function fnv1a64(text: string): string {\n // The 64-bit offset basis, as high and low 32-bit halves.\n let hi = 0xcbf29ce4;\n let lo = 0x84222325;\n for (let i = 0; i < text.length; i++) {\n lo = (lo ^ text.charCodeAt(i)) >>> 0;\n // Multiply (hi:lo) by the prime 0x00000100_000001b3, keeping 64 bits:\n // lo*0x1b3, and hi*0x1b3 + lo*0x100 carried into the high half.\n const loProduct = lo * 0x1b3;\n const carry = Math.floor(loProduct / 0x100000000);\n const newLo = loProduct >>> 0;\n hi = (Math.imul(hi, 0x1b3) + Math.imul(lo, 0x100) + carry) >>> 0;\n lo = newLo;\n }\n return hex32(hi) + hex32(lo);\n}\n\nfunction hex32(n: number): string {\n return (n >>> 0).toString(16).padStart(8, \"0\");\n}\n"]}
@@ -22,7 +22,8 @@ function createWebSocketTransport(socket, config) {
22
22
  if (shouldBuffer) {
23
23
  while (buffer.length > 0) {
24
24
  const msg = buffer.shift();
25
- socket.emit(msg.event, msg.data);
25
+ if (msg.ack) socket.emit(msg.event, msg.data, msg.ack);
26
+ else socket.emit(msg.event, msg.data);
26
27
  }
27
28
  }
28
29
  connectionHandlers.forEach((h) => h(true));
@@ -31,11 +32,12 @@ function createWebSocketTransport(socket, config) {
31
32
  connected = false;
32
33
  connectionHandlers.forEach((h) => h(false));
33
34
  });
34
- function emit(event, data) {
35
+ function emit(event, data, ack) {
35
36
  if (connected) {
36
- socket.emit(event, data);
37
+ if (ack) socket.emit(event, data, ack);
38
+ else socket.emit(event, data);
37
39
  } else if (shouldBuffer && buffer.length < maxBuffer) {
38
- buffer.push({ event, data });
40
+ buffer.push({ event, data, ...ack ? { ack } : {} });
39
41
  }
40
42
  }
41
43
  return {
@@ -46,33 +48,50 @@ function createWebSocketTransport(socket, config) {
46
48
  onAction(handler) {
47
49
  return listen(
48
50
  `${ns}:dispatch`,
49
- (data) => {
51
+ (data, ack) => {
52
+ const receipt = typeof ack === "function" ? (a) => ack(a) : void 0;
50
53
  if (!data || typeof data.requestId !== "string" || !data.requestId || typeof data.surfaceId !== "string" || typeof data.actionId !== "string") {
51
54
  console.warn(
52
55
  "[OUI] Dropped a dispatch without requestId/surfaceId/actionId",
53
56
  data
54
57
  );
58
+ receipt?.({
59
+ ok: false,
60
+ reason: "a request needs requestId, surfaceId and actionId"
61
+ });
55
62
  return;
56
63
  }
57
- handler({
58
- requestId: data.requestId,
59
- surfaceId: data.surfaceId,
60
- actionId: data.actionId,
61
- params: data.params ?? {},
62
- timestamp: data.timestamp ?? Date.now(),
63
- ...isApproval(data.approval) ? {
64
- approval: {
65
- approvalId: data.approval.approvalId,
66
- argsHash: data.approval.argsHash
67
- }
68
- } : {}
69
- });
64
+ handler(
65
+ {
66
+ requestId: data.requestId,
67
+ surfaceId: data.surfaceId,
68
+ actionId: data.actionId,
69
+ params: data.params ?? {},
70
+ timestamp: data.timestamp ?? Date.now(),
71
+ ...isApproval(data.approval) ? {
72
+ approval: {
73
+ approvalId: data.approval.approvalId,
74
+ argsHash: data.approval.argsHash
75
+ }
76
+ } : {},
77
+ // The surfaces the agent runtime holds, so the answer need not repeat them (§7.3.4).
78
+ ...typeof data.knownSurfaces === "string" && data.knownSurfaces ? { knownSurfaces: data.knownSurfaces } : {}
79
+ },
80
+ receipt
81
+ );
70
82
  }
71
83
  );
72
84
  },
73
85
  // ─── Result ─────────────────────────────────────────────────
74
- sendResult(result) {
75
- emit(`${ns}:action:result`, result);
86
+ sendResult(result, onAcknowledged) {
87
+ emit(
88
+ `${ns}:action:result`,
89
+ result,
90
+ onAcknowledged ? (response) => {
91
+ const ack = acknowledgmentOf(response);
92
+ if (ack) onAcknowledged(ack);
93
+ } : void 0
94
+ );
76
95
  },
77
96
  onResult(handler) {
78
97
  return listen(
@@ -157,7 +176,15 @@ function isApproval(value) {
157
176
  const a = value;
158
177
  return !!a && typeof a.approvalId === "string" && !!a.approvalId && typeof a.argsHash === "string";
159
178
  }
179
+ function acknowledgmentOf(response) {
180
+ const r = response;
181
+ if (!r || typeof r !== "object") return null;
182
+ if (r.ok === true) return { ok: true };
183
+ if (r.ok !== false) return null;
184
+ const why = typeof r.error === "string" ? r.error : typeof r.reason === "string" ? r.reason : "";
185
+ return { ok: false, reason: why || "refused" };
186
+ }
160
187
 
161
188
  export { createWebSocketTransport };
162
- //# sourceMappingURL=chunk-UKKITNZK.js.map
163
- //# sourceMappingURL=chunk-UKKITNZK.js.map
189
+ //# sourceMappingURL=chunk-R3SGP2UY.js.map
190
+ //# sourceMappingURL=chunk-R3SGP2UY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport/websocket.ts"],"names":[],"mappings":"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","file":"chunk-R3SGP2UY.js","sourcesContent":["import type {\n OUIAcknowledgment,\n OUITransport,\n OUITransportConfig,\n OUIActionHandler,\n OUIObservationHandler,\n OUIResultHandler,\n} from \"./types.js\";\nimport type {\n OUISurface,\n OUIObservationUpdate,\n OUIActionRequest,\n OUIActionResult,\n OUIActionApproval,\n} from \"../spec/index.js\";\n\n/**\n * WebSocket transport using Socket.IO.\n * Maps OUI protocol events to socket events with a configurable namespace prefix.\n *\n * Events:\n * {ns}:dispatch — action request (runtime → client)\n * {ns}:action:result — action result (client → runtime)\n * {ns}:observation — observation (client → runtime)\n * {ns}:surface:register — registration (client → runtime)\n * {ns}:surface:deregister — deregistration (client → runtime)\n */\nexport function createWebSocketTransport(\n socket: SocketLike,\n config?: OUITransportConfig,\n): OUITransport {\n const ns = config?.namespace ?? \"oui\";\n const buffer: Array<{ event: string; data: unknown; ack?: Ack }> = [];\n const maxBuffer = config?.maxBufferSize ?? 100;\n const shouldBuffer = config?.bufferWhileDisconnected ?? true;\n\n let connected = socket.connected ?? false;\n const connectionHandlers: Array<(c: boolean) => void> = [];\n\n // Every listener this transport attaches, so dispose() can remove exactly\n // these and nothing the integrator attached to the same socket.\n const attached: Array<{ event: string; handler: (...args: any[]) => void }> =\n [];\n function listen(\n event: string,\n handler: (...args: any[]) => void,\n ): () => void {\n socket.on(event, handler);\n const entry = { event, handler };\n attached.push(entry);\n return () => {\n socket.off(event, handler);\n const i = attached.indexOf(entry);\n if (i >= 0) attached.splice(i, 1);\n };\n }\n\n listen(\"connect\", () => {\n connected = true;\n // Flush before announcing: anything queued while offline was sent earlier\n // than whatever a connection handler is about to send.\n if (shouldBuffer) {\n while (buffer.length > 0) {\n const msg = buffer.shift()!;\n if (msg.ack) socket.emit(msg.event, msg.data, msg.ack);\n else socket.emit(msg.event, msg.data);\n }\n }\n connectionHandlers.forEach((h) => h(true));\n });\n\n listen(\"disconnect\", () => {\n connected = false;\n connectionHandlers.forEach((h) => h(false));\n });\n\n function emit(event: string, data: unknown, ack?: Ack) {\n if (connected) {\n if (ack) socket.emit(event, data, ack);\n else socket.emit(event, data);\n } else if (shouldBuffer && buffer.length < maxBuffer) {\n buffer.push({ event, data, ...(ack ? { ack } : {}) });\n }\n }\n\n return {\n // ─── Dispatch ───────────────────────────────────────────────\n dispatch(request: OUIActionRequest) {\n emit(`${ns}:dispatch`, request);\n },\n\n onAction(handler: OUIActionHandler) {\n return listen(\n `${ns}:dispatch`,\n (data: Partial<OUIActionRequest> | undefined, ack?: unknown) => {\n // The sender's receipt, when it asked for one (§7.3.7).\n const receipt =\n typeof ack === \"function\"\n ? (a: OUIAcknowledgment) => (ack as (r: unknown) => void)(a)\n : undefined;\n // A request without a requestId cannot be answered, and answering is\n // the contract. Refuse it loudly rather than run an action whose\n // result has nowhere to go.\n if (\n !data ||\n typeof data.requestId !== \"string\" ||\n !data.requestId ||\n typeof data.surfaceId !== \"string\" ||\n typeof data.actionId !== \"string\"\n ) {\n console.warn(\n \"[OUI] Dropped a dispatch without requestId/surfaceId/actionId\",\n data,\n );\n receipt?.({\n ok: false,\n reason: \"a request needs requestId, surfaceId and actionId\",\n });\n return;\n }\n handler(\n {\n requestId: data.requestId,\n surfaceId: data.surfaceId,\n actionId: data.actionId,\n params: data.params ?? {},\n timestamp: data.timestamp ?? Date.now(),\n ...(isApproval(data.approval)\n ? {\n approval: {\n approvalId: data.approval.approvalId,\n argsHash: data.approval.argsHash,\n },\n }\n : {}),\n // The surfaces the agent runtime holds, so the answer need not repeat them (§7.3.4).\n ...(typeof data.knownSurfaces === \"string\" && data.knownSurfaces\n ? { knownSurfaces: data.knownSurfaces }\n : {}),\n },\n receipt,\n );\n },\n );\n },\n\n // ─── Result ─────────────────────────────────────────────────\n sendResult(\n result: OUIActionResult,\n onAcknowledged?: (ack: OUIAcknowledgment) => void,\n ) {\n // Acknowledged, so a refusal is heard rather than silent (§7.3.6).\n emit(\n `${ns}:action:result`,\n result,\n onAcknowledged\n ? (response: unknown) => {\n const ack = acknowledgmentOf(response);\n if (ack) onAcknowledged(ack);\n }\n : undefined,\n );\n },\n\n onResult(handler: OUIResultHandler) {\n return listen(`${ns}:action:result`, (data: OUIActionResult) =>\n handler(data),\n );\n },\n\n // ─── Observation Channel ────────────────────────────────────\n pushObservation(update: OUIObservationUpdate) {\n emit(`${ns}:observation`, update);\n },\n\n onObservation(handler: OUIObservationHandler) {\n return listen(`${ns}:observation`, (data: OUIObservationUpdate) =>\n handler(data),\n );\n },\n\n // ─── Surface Lifecycle ──────────────────────────────────────\n registerSurface(surface: OUISurface) {\n emit(`${ns}:surface:register`, { surface, timestamp: Date.now() });\n },\n\n deregisterSurface(surfaceId: string) {\n emit(`${ns}:surface:deregister`, { surfaceId, timestamp: Date.now() });\n },\n\n onSurfaceRegister(handler: (surface: OUISurface) => void) {\n return listen(`${ns}:surface:register`, (data: { surface: OUISurface }) =>\n handler(data.surface),\n );\n },\n\n onSurfaceDeregister(handler: (surfaceId: string) => void) {\n return listen(`${ns}:surface:deregister`, (data: { surfaceId: string }) =>\n handler(data.surfaceId),\n );\n },\n\n // ─── Connection ─────────────────────────────────────────────\n get connected() {\n return connected;\n },\n\n async connect() {\n if (connected) return;\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(\n new Error(\n `OUI transport connect timeout (${config?.connectTimeoutMs ?? 10000}ms)`,\n ),\n );\n }, config?.connectTimeoutMs ?? 10000);\n\n socket.once(\"connect\", () => {\n clearTimeout(timeout);\n resolve();\n });\n\n if (typeof socket.connect === \"function\") {\n socket.connect();\n }\n });\n },\n\n disconnect() {\n if (typeof socket.disconnect === \"function\") {\n socket.disconnect();\n }\n },\n\n onConnectionChange(handler: (c: boolean) => void) {\n connectionHandlers.push(handler);\n return () => {\n const idx = connectionHandlers.indexOf(handler);\n if (idx >= 0) connectionHandlers.splice(idx, 1);\n };\n },\n\n dispose() {\n for (const { event, handler } of attached.splice(0)) {\n socket.off(event, handler);\n }\n connectionHandlers.length = 0;\n buffer.length = 0;\n },\n };\n}\n\n/**\n * Minimal socket interface — compatible with Socket.IO client or server socket.\n * Only the methods OUI actually uses.\n */\nexport interface SocketLike {\n readonly connected?: boolean;\n /** An optional last argument is the receiver's acknowledgment callback, as Socket.IO takes it. */\n emit(event: string, ...args: any[]): void;\n on(event: string, handler: (...args: any[]) => void): void;\n off(event: string, handler: (...args: any[]) => void): void;\n once(event: string, handler: (...args: any[]) => void): void;\n connect?(): void;\n disconnect?(): void;\n}\n\n/** An approval as the wire carries it; anything else is dropped, so the runtime treats the request as unapproved. */\nfunction isApproval(value: unknown): value is OUIActionApproval {\n const a = value as Partial<OUIActionApproval> | null | undefined;\n return (\n !!a &&\n typeof a.approvalId === \"string\" &&\n !!a.approvalId &&\n typeof a.argsHash === \"string\"\n );\n}\n\n/** The receiver's acknowledgment callback. */\ntype Ack = (response: unknown) => void;\n\n/**\n * A receiver's acknowledgment: `{ ok: true }`, or `{ ok: false, error }` /\n * `{ ok: false, reason }` as a refusal; null for anything else.\n */\nfunction acknowledgmentOf(response: unknown): OUIAcknowledgment | null {\n const r = response as\n { ok?: unknown; error?: unknown; reason?: unknown } | null | undefined;\n if (!r || typeof r !== \"object\") return null;\n if (r.ok === true) return { ok: true };\n if (r.ok !== false) return null;\n const why =\n typeof r.error === \"string\"\n ? r.error\n : typeof r.reason === \"string\"\n ? r.reason\n : \"\";\n return { ok: false, reason: why || \"refused\" };\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { ARGS_HASH_PATTERN, requiresApproval, argsHash } from './chunk-BNGJJM3T.js';
2
- import { createWebSocketTransport } from './chunk-UKKITNZK.js';
1
+ import { ARGS_HASH_PATTERN, surfacesHash, requiresApproval, argsHash } from './chunk-B2EFZMGY.js';
2
+ import { createWebSocketTransport } from './chunk-R3SGP2UY.js';
3
3
 
4
4
  // src/core/brand.ts
5
5
  function brand(value) {
@@ -156,12 +156,14 @@ function createOUI(config) {
156
156
  var DEFAULT_QUIET_MS = 250;
157
157
  var DEFAULT_SETTLE_TIMEOUT_MS = 5e3;
158
158
  var DEFAULT_DEDUPE_WINDOW = 500;
159
+ var DEFAULT_REANSWER_AFTER_MS = 8e3;
159
160
  var SETTLE_TICK_MS = 50;
160
161
  function createSurfaceRuntime(options = {}) {
161
162
  const announce = options.announce ?? true;
162
163
  const quietMs = options.settle?.quietMs ?? DEFAULT_QUIET_MS;
163
164
  const settleTimeoutMs = options.settle?.timeoutMs ?? DEFAULT_SETTLE_TIMEOUT_MS;
164
165
  const dedupeWindow = options.dedupeWindow ?? DEFAULT_DEDUPE_WINDOW;
166
+ const reanswerAfterMs = options.reanswerAfterMs ?? DEFAULT_REANSWER_AFTER_MS;
165
167
  let nextKey = 1;
166
168
  const entries = [];
167
169
  const observations = /* @__PURE__ */ new Map();
@@ -186,6 +188,8 @@ function createSurfaceRuntime(options = {}) {
186
188
  function acknowledged(requestId) {
187
189
  return ackFor(requestId).promise;
188
190
  }
191
+ const delivery = /* @__PURE__ */ new Map();
192
+ const answeredHash = /* @__PURE__ */ new Map();
189
193
  let lastChangeAt = Date.now();
190
194
  const grants = /* @__PURE__ */ new Map();
191
195
  async function approvalRefusal(request) {
@@ -299,17 +303,20 @@ function createSurfaceRuntime(options = {}) {
299
303
  const finish = async (success, data, error) => {
300
304
  await acknowledged(requestId);
301
305
  const snap = snapshot();
302
- transport?.sendResult({
306
+ const known = answeredHash.get(requestId);
307
+ answer({
303
308
  requestId,
304
309
  success,
305
310
  ...data !== void 0 ? { data: toJsonSafe(data) } : {},
306
311
  ...error ? { error } : {},
307
312
  interim: false,
308
313
  timestamp: Date.now(),
309
- surfaces: snap.surfaces,
310
- observations: snap.observations
314
+ ...snap.surfacesHash !== known ? { surfaces: snap.surfaces } : {},
315
+ observations: snap.observations,
316
+ surfacesHash: snap.surfacesHash
311
317
  });
312
318
  acks.delete(requestId);
319
+ answeredHash.delete(requestId);
313
320
  };
314
321
  const observationId = `${surfaceId}:${actionId}:status`;
315
322
  const pollerKey = `${surfaceId}\0${actionId}`;
@@ -471,6 +478,7 @@ function createSurfaceRuntime(options = {}) {
471
478
  }
472
479
  const settled = await waitUntilSettled(startedAt);
473
480
  const snap = snapshot();
481
+ if (interim) answeredHash.set(request.requestId, snap.surfacesHash);
474
482
  return {
475
483
  requestId: request.requestId,
476
484
  success: result.success,
@@ -479,11 +487,39 @@ function createSurfaceRuntime(options = {}) {
479
487
  ...interim ? { interim: true } : {},
480
488
  durationMs: Date.now() - startedAt,
481
489
  timestamp: Date.now(),
482
- surfaces: snap.surfaces,
490
+ // Only what the agent runtime does not already hold (§7.3.4).
491
+ ...snap.surfacesHash !== request.knownSurfaces ? { surfaces: snap.surfaces } : {},
483
492
  observations: snap.observations,
493
+ surfacesHash: snap.surfacesHash,
484
494
  settled
485
495
  };
486
496
  }
497
+ function answer(result, onReceived) {
498
+ const steps = [
499
+ ["surfaces", "observations"],
500
+ ["surfaces", "observations", "data"]
501
+ ];
502
+ const send = (frame, step) => {
503
+ transport?.sendResult(frame, (ack) => {
504
+ if (ack.ok) {
505
+ onReceived?.();
506
+ return;
507
+ }
508
+ const omitted = steps[step];
509
+ if (!omitted) {
510
+ console.warn(
511
+ `[OUI] The answer to ${result.requestId} was refused even trimmed: ${ack.reason}`
512
+ );
513
+ return;
514
+ }
515
+ console.warn(
516
+ `[OUI] The answer to ${result.requestId} was refused (${ack.reason}); sending it without ${omitted.join(", ")}`
517
+ );
518
+ send(trimmed(result, ack.reason, omitted), step + 1);
519
+ });
520
+ };
521
+ send(result, 0);
522
+ }
487
523
  function execute(request) {
488
524
  const seen = results.get(request.requestId);
489
525
  if (seen) return seen;
@@ -492,6 +528,7 @@ function createSurfaceRuntime(options = {}) {
492
528
  while (results.size > dedupeWindow) {
493
529
  const oldest = results.keys().next().value;
494
530
  results.delete(oldest);
531
+ delivery.delete(oldest);
495
532
  }
496
533
  return pending;
497
534
  }
@@ -504,7 +541,11 @@ function createSurfaceRuntime(options = {}) {
504
541
  if (values && values.size > 0)
505
542
  obs[e.surface.id] = Object.fromEntries(values);
506
543
  }
507
- return { surfaces, observations: obs };
544
+ return {
545
+ surfaces,
546
+ observations: obs,
547
+ surfacesHash: surfacesHash(surfaces)
548
+ };
508
549
  }
509
550
  function attach(next) {
510
551
  if (next === socket) return;
@@ -517,18 +558,33 @@ function createSurfaceRuntime(options = {}) {
517
558
  const t = createWebSocketTransport(next, options.transport);
518
559
  transport = t;
519
560
  detachTransport.push(
520
- t.onAction((request) => {
561
+ t.onAction((request, receipt) => {
521
562
  if (options.accept && !options.accept(request)) {
522
563
  console.warn(
523
564
  `[OUI] Refused a request the client does not accept: ${request.surfaceId}.${request.actionId} (${request.requestId})`
524
565
  );
566
+ receipt?.({
567
+ ok: false,
568
+ reason: "this client does not accept requests now"
569
+ });
525
570
  return;
526
571
  }
572
+ receipt?.({ ok: true });
573
+ const id = request.requestId;
574
+ const prior = delivery.get(id);
575
+ if (prior && prior.state !== "sent") return;
576
+ if (prior && Date.now() - prior.at < reanswerAfterMs) return;
577
+ if (!prior) delivery.set(id, { state: "running", at: Date.now() });
527
578
  void execute(request).then((result) => {
528
- transport?.sendResult(result);
529
- const ack = ackFor(request.requestId);
579
+ if (results.has(id))
580
+ delivery.set(id, { state: "sent", at: Date.now() });
581
+ answer(result, () => {
582
+ if (results.has(id))
583
+ delivery.set(id, { state: "received", at: Date.now() });
584
+ });
585
+ const ack = ackFor(id);
530
586
  ack.resolve();
531
- if (!result.interim) acks.delete(request.requestId);
587
+ if (!result.interim) acks.delete(id);
532
588
  });
533
589
  })
534
590
  );
@@ -618,10 +674,18 @@ function createSurfaceRuntime(options = {}) {
618
674
  holds.clear();
619
675
  listeners.clear();
620
676
  acks.clear();
677
+ answeredHash.clear();
678
+ delivery.clear();
621
679
  grants.clear();
622
680
  }
623
681
  };
624
682
  }
683
+ function trimmed(result, reason, omitted) {
684
+ const out = { ...result };
685
+ for (const field of omitted) delete out[field];
686
+ out.delivery = { trimmed: true, reason, omitted: [...omitted] };
687
+ return out;
688
+ }
625
689
  function sleep(ms) {
626
690
  return new Promise((resolve) => setTimeout(resolve, ms));
627
691
  }
@@ -635,5 +699,5 @@ function toJsonSafe(value) {
635
699
  }
636
700
 
637
701
  export { createOUI, createSurfaceRuntime, defineSurface };
638
- //# sourceMappingURL=chunk-6ZIKO7MG.js.map
639
- //# sourceMappingURL=chunk-6ZIKO7MG.js.map
702
+ //# sourceMappingURL=chunk-VQETSIY5.js.map
703
+ //# sourceMappingURL=chunk-VQETSIY5.js.map