oui-spec 0.1.0 → 0.3.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 (61) hide show
  1. package/README.md +161 -104
  2. package/dist/{chunk-CVUUSPF4.js → chunk-BMCL2K74.js} +2 -2
  3. package/dist/chunk-BMCL2K74.js.map +1 -0
  4. package/dist/chunk-DCVVTP3V.cjs +155 -0
  5. package/dist/chunk-DCVVTP3V.cjs.map +1 -0
  6. package/dist/chunk-F3SKRAGU.cjs +98 -0
  7. package/dist/chunk-F3SKRAGU.cjs.map +1 -0
  8. package/dist/chunk-F3WKIRAU.js +153 -0
  9. package/dist/chunk-F3WKIRAU.js.map +1 -0
  10. package/dist/chunk-IIZ6HL2L.js +89 -0
  11. package/dist/chunk-IIZ6HL2L.js.map +1 -0
  12. package/dist/chunk-KNWGQRXL.cjs +567 -0
  13. package/dist/chunk-KNWGQRXL.cjs.map +1 -0
  14. package/dist/chunk-NYCJZQSP.js +92 -0
  15. package/dist/chunk-NYCJZQSP.js.map +1 -0
  16. package/dist/chunk-ROGKEMLL.cjs +91 -0
  17. package/dist/chunk-ROGKEMLL.cjs.map +1 -0
  18. package/dist/chunk-TRNVXAB5.js +563 -0
  19. package/dist/chunk-TRNVXAB5.js.map +1 -0
  20. package/dist/{chunk-3DCIL4FV.cjs → chunk-ZKM4XWAB.cjs} +2 -2
  21. package/dist/chunk-ZKM4XWAB.cjs.map +1 -0
  22. package/dist/core/index.cjs +13 -4
  23. package/dist/core/index.d.cts +123 -102
  24. package/dist/core/index.d.ts +123 -102
  25. package/dist/core/index.js +3 -2
  26. package/dist/index.cjs +31 -10
  27. package/dist/index.d.cts +7 -4
  28. package/dist/index.d.ts +7 -4
  29. package/dist/index.js +5 -4
  30. package/dist/react/index.cjs +15 -3
  31. package/dist/react/index.d.cts +33 -50
  32. package/dist/react/index.d.ts +33 -50
  33. package/dist/react/index.js +1 -1
  34. package/dist/spec/index.cjs +2 -2
  35. package/dist/spec/index.d.cts +35 -7
  36. package/dist/spec/index.d.ts +35 -7
  37. package/dist/spec/index.js +1 -1
  38. package/dist/surface-runtime--2lZGB7Z.d.cts +241 -0
  39. package/dist/surface-runtime-Dj_nhd7r.d.ts +241 -0
  40. package/dist/transport/index.cjs +4 -3
  41. package/dist/transport/index.d.cts +4 -77
  42. package/dist/transport/index.d.ts +4 -77
  43. package/dist/transport/index.js +2 -1
  44. package/dist/websocket-YfWILdZ4.d.cts +96 -0
  45. package/dist/websocket-c5tIUWY5.d.ts +96 -0
  46. package/package.json +8 -3
  47. package/spec/OUI-SPEC-v0.1.md +229 -155
  48. package/dist/chunk-2ON5IEXB.cjs +0 -167
  49. package/dist/chunk-2ON5IEXB.cjs.map +0 -1
  50. package/dist/chunk-3DCIL4FV.cjs.map +0 -1
  51. package/dist/chunk-7EXSMPGR.js +0 -207
  52. package/dist/chunk-7EXSMPGR.js.map +0 -1
  53. package/dist/chunk-CVUUSPF4.js.map +0 -1
  54. package/dist/chunk-JO5LGOB6.js +0 -88
  55. package/dist/chunk-JO5LGOB6.js.map +0 -1
  56. package/dist/chunk-LEXIU2MH.cjs +0 -90
  57. package/dist/chunk-LEXIU2MH.cjs.map +0 -1
  58. package/dist/chunk-UIGRLZOX.js +0 -164
  59. package/dist/chunk-UIGRLZOX.js.map +0 -1
  60. package/dist/chunk-WZXUKJ7E.cjs +0 -210
  61. package/dist/chunk-WZXUKJ7E.cjs.map +0 -1
@@ -10,5 +10,5 @@ var OUI_PROTOCOL_EVENTS = {
10
10
  };
11
11
 
12
12
  exports.OUI_PROTOCOL_EVENTS = OUI_PROTOCOL_EVENTS;
13
- //# sourceMappingURL=chunk-3DCIL4FV.cjs.map
14
- //# sourceMappingURL=chunk-3DCIL4FV.cjs.map
13
+ //# sourceMappingURL=chunk-ZKM4XWAB.cjs.map
14
+ //# sourceMappingURL=chunk-ZKM4XWAB.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/types.ts"],"names":[],"mappings":";;;AAiUO,IAAM,mBAAA,GAAsB;AAAA,EACjC,gBAAA,EAAkB,kBAAA;AAAA,EAClB,kBAAA,EAAoB,oBAAA;AAAA,EACpB,cAAA,EAAgB,gBAAA;AAAA,EAChB,aAAA,EAAe,eAAA;AAAA,EACf,kBAAA,EAAoB;AACtB","file":"chunk-ZKM4XWAB.cjs","sourcesContent":["/**\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 /** 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 /** If true, the agent runtime should confirm with the user before executing */\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/**\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 * 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\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"]}
@@ -1,17 +1,26 @@
1
1
  'use strict';
2
2
 
3
- var chunkLEXIU2MH_cjs = require('../chunk-LEXIU2MH.cjs');
4
- var chunk3DCIL4FV_cjs = require('../chunk-3DCIL4FV.cjs');
3
+ var chunkKNWGQRXL_cjs = require('../chunk-KNWGQRXL.cjs');
4
+ var chunkZKM4XWAB_cjs = require('../chunk-ZKM4XWAB.cjs');
5
+ require('../chunk-DCVVTP3V.cjs');
5
6
 
6
7
 
7
8
 
9
+ Object.defineProperty(exports, "createOUI", {
10
+ enumerable: true,
11
+ get: function () { return chunkKNWGQRXL_cjs.createOUI; }
12
+ });
13
+ Object.defineProperty(exports, "createSurfaceRuntime", {
14
+ enumerable: true,
15
+ get: function () { return chunkKNWGQRXL_cjs.createSurfaceRuntime; }
16
+ });
8
17
  Object.defineProperty(exports, "defineSurface", {
9
18
  enumerable: true,
10
- get: function () { return chunkLEXIU2MH_cjs.defineSurface; }
19
+ get: function () { return chunkKNWGQRXL_cjs.defineSurface; }
11
20
  });
12
21
  Object.defineProperty(exports, "OUI_PROTOCOL_EVENTS", {
13
22
  enumerable: true,
14
- get: function () { return chunk3DCIL4FV_cjs.OUI_PROTOCOL_EVENTS; }
23
+ get: function () { return chunkZKM4XWAB_cjs.OUI_PROTOCOL_EVENTS; }
15
24
  });
16
25
  //# sourceMappingURL=index.cjs.map
17
26
  //# sourceMappingURL=index.cjs.map
@@ -1,120 +1,141 @@
1
- import { JSONSchema, OUIActionPolling, OUIActivation, OUISurface } from '../spec/index.cjs';
2
- export { OUIAction, OUIActionRequest, OUIActionResult, OUIObservation, OUIObservationUpdate, OUIProtocolEvent, OUISurfaceDeregistration, OUISurfaceRegistration, OUI_PROTOCOL_EVENTS } from '../spec/index.cjs';
1
+ import { OUISurface } from '../spec/index.cjs';
2
+ export { JSONSchema, OUIAction, OUIActionPolling, OUIActionRequest, OUIActionResult, OUIActivation, OUIObservation, OUIObservationSnapshot, OUIObservationUpdate, OUIProtocolEvent, OUISurfaceDeregistration, OUISurfaceRegistration, OUISurfaceSnapshot, OUI_PROTOCOL_EVENTS } from '../spec/index.cjs';
3
+ import { D as DefinedSurface, O as OUIBranded } from '../surface-runtime--2lZGB7Z.cjs';
4
+ export { A as ActionDefinition, a as ActionHandlerResult, b as ActionPollingConfig, M as MountedSurface, c as ObservationDefinition, S as SettleOptions, d as SurfaceDefinition, e as SurfaceRuntime, f as SurfaceRuntimeOptions, g as createSurfaceRuntime, h as defineSurface } from '../surface-runtime--2lZGB7Z.cjs';
5
+ import { S as SocketLike, d as OUITransportConfig, c as OUITransport } from '../websocket-YfWILdZ4.cjs';
3
6
 
4
7
  /**
5
- * Configuration for defining an OUI surface with handlers.
6
- * This is the "integration file" an app provides — it declares both
7
- * the schema (what the agent sees) and the implementation (what executes).
8
+ * Compile-time validation for a createOUI configuration.
9
+ *
10
+ * These types resolve to a descriptive error type when a rule is broken, so the
11
+ * failure surfaces at the call site with a message naming the offending id —
12
+ * not as `Type 'X' is not assignable to type 'never'`.
13
+ *
14
+ * Everything here was previously a runtime `throw` inside defineSurface, or
15
+ * nothing at all. A duplicate action id threw when the surface was constructed,
16
+ * which in a React app means at render, in a browser, after a deploy. The same
17
+ * mistake is now a red squiggle.
8
18
  */
9
- interface SurfaceDefinition<TContext = unknown> {
10
- id: string;
11
- name: string;
12
- description: string;
13
- version?: string;
14
- activation?: OUIActivation;
15
- actions: ActionDefinition<TContext>[];
16
- observations?: ObservationDefinition[];
17
- metadata?: Record<string, unknown>;
18
- }
19
+ /** A readable compile error. The message lands in the tooltip. */
20
+ type OUIConfigError<TMessage extends string> = {
21
+ readonly __ouiError: TMessage;
22
+ };
23
+ /** Ids appearing more than once in a tuple of string literals. */
24
+ type Duplicates<T extends readonly string[], Seen extends string = never> = T extends readonly [
25
+ infer Head extends string,
26
+ ...infer Rest extends readonly string[]
27
+ ] ? Head extends Seen ? Head | Duplicates<Rest, Seen | Head> : Duplicates<Rest, Seen | Head> : never;
19
28
  /**
20
- * Polling config with an optional resolve handler.
21
- * The spec's OUIActionPolling is the manifest shape (no functions).
22
- * The core extends it with the resolve implementation for the runtime.
29
+ * `never` when every id is unique, otherwise the duplicated ids.
30
+ * Exported for tests — a validation type nobody can assert against is a
31
+ * validation type nobody can trust.
23
32
  */
24
- interface ActionPollingConfig<TContext = unknown> extends OUIActionPolling {
25
- /**
26
- * Called on each poll interval. Receives the dispatch result from the handler
27
- * plus the current context. Return { done: true, data } to stop polling,
28
- * or { done: false, data } to continue.
29
- */
30
- resolve?: (dispatchResult: unknown, context: TContext) => Promise<{
31
- done: boolean;
32
- data: unknown;
33
- }>;
34
- }
33
+ type DuplicateIds<T extends readonly string[]> = Duplicates<T>;
34
+ type IsEmpty<T extends readonly unknown[]> = T extends readonly [] ? true : false;
35
35
  /**
36
- * An action definition includes both the schema (for the agent) and
37
- * the handler (for execution). This is the single source of truth —
38
- * no separate YAML, no separate listener, no drift possible.
36
+ * Validate the surface list, returning the config type when it passes and an
37
+ * OUIConfigError when it does not.
38
+ *
39
+ * The rules, and why each exists:
40
+ *
41
+ * Non-empty — an OUI instance with no surfaces registers nothing and
42
+ * dispatches nowhere. It looks connected and does nothing, which is the
43
+ * failure mode this whole design is trying to eliminate.
44
+ *
45
+ * Unique surface ids — registration is keyed by surface id, so a duplicate
46
+ * silently replaces the first surface's manifest. The agent then sees one
47
+ * surface's actions attributed to the other.
39
48
  */
40
- interface ActionDefinition<TContext = unknown> {
41
- id: string;
42
- description: string;
43
- input: JSONSchema;
44
- output?: JSONSchema;
45
- confirm?: boolean;
46
- async?: boolean;
49
+ type ValidateSurfaceIds<TIds extends readonly string[]> = IsEmpty<TIds> extends true ? OUIConfigError<"createOUI requires at least one surface. An instance with no surfaces registers nothing and dispatches nowhere."> : [DuplicateIds<TIds>] extends [never] ? unknown : OUIConfigError<`Duplicate surface id: "${DuplicateIds<TIds>}". Surface registration is keyed by id, so the second would silently replace the first.`>;
50
+
51
+ /**
52
+ * createOUI — a fixed set of surfaces announced over one socket.
53
+ *
54
+ * For a client whose surfaces come and go (a React app, where a page's
55
+ * surfaces mount with the page), use `createSurfaceRuntime` instead: it keeps
56
+ * the client's own record of what is mounted and answers every request.
57
+ *
58
+ * WHY THIS EXISTS
59
+ *
60
+ * OUI shipped a correct protocol and a correct transport, and its first
61
+ * integrator used neither. studio-ui imported only the `SocketLike` TYPE from
62
+ * oui-spec and rebuilt the wire by hand: a literal map of protocol types to
63
+ * socket names, `oui:` hardcoded so the configurable namespace was unreachable,
64
+ * payload interfaces re-declared on the server, and an `action:result` channel
65
+ * mapped but never sent — duplicating the observation channel that already
66
+ * carries results.
67
+ *
68
+ * Nothing was wrong with the spec. Nothing enforced it. Structural typing meant
69
+ * every hand-rolled piece type-checked, and both ends agreed only by the
70
+ * coincidence of matching string literals. Rename one and the other keeps
71
+ * compiling and silently stops receiving.
72
+ *
73
+ * So the requirements are gated here:
74
+ *
75
+ * OUI owns the wire. The integrator provides a socket; it cannot provide a
76
+ * transport, because `OUITransport` values are branded and only this module
77
+ * brands them.
78
+ *
79
+ * OUI owns the result path. Every dispatch carries a requestId and is
80
+ * answered on OUI's result channel. An integrator cannot add a parallel
81
+ * channel because it cannot construct a transport to add one to.
82
+ *
83
+ * The integrator owns content. Surfaces, action schemas, observation schemas
84
+ * and handlers all arrive through `defineSurface`, whose output is branded —
85
+ * so a plain object shaped like a surface will not compile.
86
+ *
87
+ * Violations fail at compile time with a message naming the offending id.
88
+ */
89
+
90
+ /** A transport OUI constructed. Integrators cannot produce one. */
91
+ type OwnedTransport = OUITransport & OUIBranded<"transport">;
92
+ /** Surface ids as literal types, for the duplicate check. */
93
+ type SurfaceIds<T extends readonly DefinedSurface<never>[]> = {
94
+ [K in keyof T]: T[K]["id"];
95
+ };
96
+ interface OUIConfig<TSurfaces extends readonly DefinedSurface<never>[]> {
47
97
  /**
48
- * Polling/subscription config for async actions.
49
- * After the handler returns, the OUI runtime starts polling or subscribing
50
- * and pushes observation updates until the operation completes.
51
- * Extends the spec's OUIActionPolling with an optional resolve handler.
98
+ * The socket OUI will speak over. The integrator owns the connection —
99
+ * auth, reconnection, lifecycle — and OUI owns what travels on it.
52
100
  */
53
- polling?: ActionPollingConfig<TContext>;
54
- usage?: string;
55
- preconditions?: string;
56
- estimatedDuration?: string;
57
- tags?: string[];
101
+ socket: SocketLike;
58
102
  /**
59
- * The handler that executes when the agent invokes this action.
60
- * Receives typed params and a context object (app-specific state/utilities).
61
- * Returns a result that the agent sees as the tool output.
103
+ * Surfaces produced by `defineSurface`. A plain object with the same shape
104
+ * will not compile: DefinedSurface is branded.
62
105
  */
63
- handler: (params: Record<string, unknown>, context: TContext) => Promise<ActionHandlerResult>;
64
- }
65
- interface ActionHandlerResult {
66
- success: boolean;
67
- data?: unknown;
68
- error?: {
69
- code: string;
70
- message: string;
71
- details?: unknown;
106
+ surfaces: TSurfaces;
107
+ /**
108
+ * Wire configuration. `namespace` defaults to 'oui' and is the supported way
109
+ * to change the event prefix — hardcoding it at the call site is what made
110
+ * this option unreachable in the first integration.
111
+ */
112
+ transport?: Omit<OUITransportConfig, "namespace"> & {
113
+ namespace?: string;
72
114
  };
73
- /** For async actions: metadata passed to the polling resolver */
74
- dispatchMeta?: Record<string, unknown>;
75
115
  }
76
- interface ObservationDefinition {
77
- id: string;
78
- description: string;
79
- schema: JSONSchema;
80
- /** Function that returns the current observation value */
81
- getValue?: () => unknown;
116
+ /** A live OUI instance. */
117
+ interface OUIInstance {
118
+ /** The transport OUI owns. Exposed for wiring, not for reimplementation. */
119
+ readonly transport: OwnedTransport;
120
+ /** Manifests for every registered surface, as the agent sees them. */
121
+ readonly manifests: readonly OUISurface[];
122
+ /** Register every surface and connect. */
123
+ start(): Promise<void>;
124
+ /**
125
+ * Deregister every surface and detach OUI from the socket. The socket stays
126
+ * connected: the integrator owns it, and other code may share it.
127
+ */
128
+ stop(): void;
129
+ /** Look up a registered surface by id. */
130
+ getSurface(id: string): DefinedSurface<never> | undefined;
82
131
  }
83
132
  /**
84
- * Define an OUI surface — the single entry point for integrating
85
- * an application feature with the OUI protocol.
86
- *
87
- * This function validates the definition and returns a typed surface
88
- * object that can be registered with an agent runtime or transport layer.
133
+ * Instantiate OUI.
89
134
  *
90
- * @example
91
- * ```typescript
92
- * const mySurface = defineSurface({
93
- * id: 'my-feature',
94
- * name: 'My Feature',
95
- * description: 'Does something cool',
96
- * actions: [
97
- * {
98
- * id: 'do_thing',
99
- * description: 'Does the thing',
100
- * input: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
101
- * handler: async (params) => {
102
- * const result = await doTheThing(params.name);
103
- * return { success: true, data: result };
104
- * },
105
- * },
106
- * ],
107
- * });
108
- * ```
135
+ * `const` on the type parameter captures surface ids as literal types, which is
136
+ * what allows duplicate detection to name the offending id rather than failing
137
+ * with an unassignable-to-never error.
109
138
  */
110
- declare function defineSurface<TContext = unknown>(definition: SurfaceDefinition<TContext>): DefinedSurface<TContext>;
111
- /** A fully defined surface with both manifest extraction and execution capabilities */
112
- interface DefinedSurface<TContext = unknown> extends SurfaceDefinition<TContext> {
113
- toManifest(): OUISurface;
114
- executeAction(actionId: string, params: Record<string, unknown>, context: TContext): Promise<ActionHandlerResult>;
115
- getActionIds(): string[];
116
- /** Get the polling config for an action (if async) */
117
- getPollingConfig(actionId: string): OUIActionPolling | undefined;
118
- }
139
+ declare function createOUI<const TSurfaces extends readonly DefinedSurface<never>[]>(config: OUIConfig<TSurfaces> & ValidateSurfaceIds<SurfaceIds<TSurfaces>>): OUIInstance;
119
140
 
120
- export { type ActionDefinition, type ActionHandlerResult, type ActionPollingConfig, type DefinedSurface, JSONSchema, OUIActivation, OUISurface, type ObservationDefinition, type SurfaceDefinition, defineSurface };
141
+ export { DefinedSurface, type DuplicateIds, OUIBranded, type OUIConfig, type OUIConfigError, type OUIInstance, OUISurface, type OwnedTransport, type ValidateSurfaceIds, createOUI };
@@ -1,120 +1,141 @@
1
- import { JSONSchema, OUIActionPolling, OUIActivation, OUISurface } from '../spec/index.js';
2
- export { OUIAction, OUIActionRequest, OUIActionResult, OUIObservation, OUIObservationUpdate, OUIProtocolEvent, OUISurfaceDeregistration, OUISurfaceRegistration, OUI_PROTOCOL_EVENTS } from '../spec/index.js';
1
+ import { OUISurface } from '../spec/index.js';
2
+ export { JSONSchema, OUIAction, OUIActionPolling, OUIActionRequest, OUIActionResult, OUIActivation, OUIObservation, OUIObservationSnapshot, OUIObservationUpdate, OUIProtocolEvent, OUISurfaceDeregistration, OUISurfaceRegistration, OUISurfaceSnapshot, OUI_PROTOCOL_EVENTS } from '../spec/index.js';
3
+ import { D as DefinedSurface, O as OUIBranded } from '../surface-runtime-Dj_nhd7r.js';
4
+ export { A as ActionDefinition, a as ActionHandlerResult, b as ActionPollingConfig, M as MountedSurface, c as ObservationDefinition, S as SettleOptions, d as SurfaceDefinition, e as SurfaceRuntime, f as SurfaceRuntimeOptions, g as createSurfaceRuntime, h as defineSurface } from '../surface-runtime-Dj_nhd7r.js';
5
+ import { S as SocketLike, d as OUITransportConfig, c as OUITransport } from '../websocket-c5tIUWY5.js';
3
6
 
4
7
  /**
5
- * Configuration for defining an OUI surface with handlers.
6
- * This is the "integration file" an app provides — it declares both
7
- * the schema (what the agent sees) and the implementation (what executes).
8
+ * Compile-time validation for a createOUI configuration.
9
+ *
10
+ * These types resolve to a descriptive error type when a rule is broken, so the
11
+ * failure surfaces at the call site with a message naming the offending id —
12
+ * not as `Type 'X' is not assignable to type 'never'`.
13
+ *
14
+ * Everything here was previously a runtime `throw` inside defineSurface, or
15
+ * nothing at all. A duplicate action id threw when the surface was constructed,
16
+ * which in a React app means at render, in a browser, after a deploy. The same
17
+ * mistake is now a red squiggle.
8
18
  */
9
- interface SurfaceDefinition<TContext = unknown> {
10
- id: string;
11
- name: string;
12
- description: string;
13
- version?: string;
14
- activation?: OUIActivation;
15
- actions: ActionDefinition<TContext>[];
16
- observations?: ObservationDefinition[];
17
- metadata?: Record<string, unknown>;
18
- }
19
+ /** A readable compile error. The message lands in the tooltip. */
20
+ type OUIConfigError<TMessage extends string> = {
21
+ readonly __ouiError: TMessage;
22
+ };
23
+ /** Ids appearing more than once in a tuple of string literals. */
24
+ type Duplicates<T extends readonly string[], Seen extends string = never> = T extends readonly [
25
+ infer Head extends string,
26
+ ...infer Rest extends readonly string[]
27
+ ] ? Head extends Seen ? Head | Duplicates<Rest, Seen | Head> : Duplicates<Rest, Seen | Head> : never;
19
28
  /**
20
- * Polling config with an optional resolve handler.
21
- * The spec's OUIActionPolling is the manifest shape (no functions).
22
- * The core extends it with the resolve implementation for the runtime.
29
+ * `never` when every id is unique, otherwise the duplicated ids.
30
+ * Exported for tests — a validation type nobody can assert against is a
31
+ * validation type nobody can trust.
23
32
  */
24
- interface ActionPollingConfig<TContext = unknown> extends OUIActionPolling {
25
- /**
26
- * Called on each poll interval. Receives the dispatch result from the handler
27
- * plus the current context. Return { done: true, data } to stop polling,
28
- * or { done: false, data } to continue.
29
- */
30
- resolve?: (dispatchResult: unknown, context: TContext) => Promise<{
31
- done: boolean;
32
- data: unknown;
33
- }>;
34
- }
33
+ type DuplicateIds<T extends readonly string[]> = Duplicates<T>;
34
+ type IsEmpty<T extends readonly unknown[]> = T extends readonly [] ? true : false;
35
35
  /**
36
- * An action definition includes both the schema (for the agent) and
37
- * the handler (for execution). This is the single source of truth —
38
- * no separate YAML, no separate listener, no drift possible.
36
+ * Validate the surface list, returning the config type when it passes and an
37
+ * OUIConfigError when it does not.
38
+ *
39
+ * The rules, and why each exists:
40
+ *
41
+ * Non-empty — an OUI instance with no surfaces registers nothing and
42
+ * dispatches nowhere. It looks connected and does nothing, which is the
43
+ * failure mode this whole design is trying to eliminate.
44
+ *
45
+ * Unique surface ids — registration is keyed by surface id, so a duplicate
46
+ * silently replaces the first surface's manifest. The agent then sees one
47
+ * surface's actions attributed to the other.
39
48
  */
40
- interface ActionDefinition<TContext = unknown> {
41
- id: string;
42
- description: string;
43
- input: JSONSchema;
44
- output?: JSONSchema;
45
- confirm?: boolean;
46
- async?: boolean;
49
+ type ValidateSurfaceIds<TIds extends readonly string[]> = IsEmpty<TIds> extends true ? OUIConfigError<"createOUI requires at least one surface. An instance with no surfaces registers nothing and dispatches nowhere."> : [DuplicateIds<TIds>] extends [never] ? unknown : OUIConfigError<`Duplicate surface id: "${DuplicateIds<TIds>}". Surface registration is keyed by id, so the second would silently replace the first.`>;
50
+
51
+ /**
52
+ * createOUI — a fixed set of surfaces announced over one socket.
53
+ *
54
+ * For a client whose surfaces come and go (a React app, where a page's
55
+ * surfaces mount with the page), use `createSurfaceRuntime` instead: it keeps
56
+ * the client's own record of what is mounted and answers every request.
57
+ *
58
+ * WHY THIS EXISTS
59
+ *
60
+ * OUI shipped a correct protocol and a correct transport, and its first
61
+ * integrator used neither. studio-ui imported only the `SocketLike` TYPE from
62
+ * oui-spec and rebuilt the wire by hand: a literal map of protocol types to
63
+ * socket names, `oui:` hardcoded so the configurable namespace was unreachable,
64
+ * payload interfaces re-declared on the server, and an `action:result` channel
65
+ * mapped but never sent — duplicating the observation channel that already
66
+ * carries results.
67
+ *
68
+ * Nothing was wrong with the spec. Nothing enforced it. Structural typing meant
69
+ * every hand-rolled piece type-checked, and both ends agreed only by the
70
+ * coincidence of matching string literals. Rename one and the other keeps
71
+ * compiling and silently stops receiving.
72
+ *
73
+ * So the requirements are gated here:
74
+ *
75
+ * OUI owns the wire. The integrator provides a socket; it cannot provide a
76
+ * transport, because `OUITransport` values are branded and only this module
77
+ * brands them.
78
+ *
79
+ * OUI owns the result path. Every dispatch carries a requestId and is
80
+ * answered on OUI's result channel. An integrator cannot add a parallel
81
+ * channel because it cannot construct a transport to add one to.
82
+ *
83
+ * The integrator owns content. Surfaces, action schemas, observation schemas
84
+ * and handlers all arrive through `defineSurface`, whose output is branded —
85
+ * so a plain object shaped like a surface will not compile.
86
+ *
87
+ * Violations fail at compile time with a message naming the offending id.
88
+ */
89
+
90
+ /** A transport OUI constructed. Integrators cannot produce one. */
91
+ type OwnedTransport = OUITransport & OUIBranded<"transport">;
92
+ /** Surface ids as literal types, for the duplicate check. */
93
+ type SurfaceIds<T extends readonly DefinedSurface<never>[]> = {
94
+ [K in keyof T]: T[K]["id"];
95
+ };
96
+ interface OUIConfig<TSurfaces extends readonly DefinedSurface<never>[]> {
47
97
  /**
48
- * Polling/subscription config for async actions.
49
- * After the handler returns, the OUI runtime starts polling or subscribing
50
- * and pushes observation updates until the operation completes.
51
- * Extends the spec's OUIActionPolling with an optional resolve handler.
98
+ * The socket OUI will speak over. The integrator owns the connection —
99
+ * auth, reconnection, lifecycle — and OUI owns what travels on it.
52
100
  */
53
- polling?: ActionPollingConfig<TContext>;
54
- usage?: string;
55
- preconditions?: string;
56
- estimatedDuration?: string;
57
- tags?: string[];
101
+ socket: SocketLike;
58
102
  /**
59
- * The handler that executes when the agent invokes this action.
60
- * Receives typed params and a context object (app-specific state/utilities).
61
- * Returns a result that the agent sees as the tool output.
103
+ * Surfaces produced by `defineSurface`. A plain object with the same shape
104
+ * will not compile: DefinedSurface is branded.
62
105
  */
63
- handler: (params: Record<string, unknown>, context: TContext) => Promise<ActionHandlerResult>;
64
- }
65
- interface ActionHandlerResult {
66
- success: boolean;
67
- data?: unknown;
68
- error?: {
69
- code: string;
70
- message: string;
71
- details?: unknown;
106
+ surfaces: TSurfaces;
107
+ /**
108
+ * Wire configuration. `namespace` defaults to 'oui' and is the supported way
109
+ * to change the event prefix — hardcoding it at the call site is what made
110
+ * this option unreachable in the first integration.
111
+ */
112
+ transport?: Omit<OUITransportConfig, "namespace"> & {
113
+ namespace?: string;
72
114
  };
73
- /** For async actions: metadata passed to the polling resolver */
74
- dispatchMeta?: Record<string, unknown>;
75
115
  }
76
- interface ObservationDefinition {
77
- id: string;
78
- description: string;
79
- schema: JSONSchema;
80
- /** Function that returns the current observation value */
81
- getValue?: () => unknown;
116
+ /** A live OUI instance. */
117
+ interface OUIInstance {
118
+ /** The transport OUI owns. Exposed for wiring, not for reimplementation. */
119
+ readonly transport: OwnedTransport;
120
+ /** Manifests for every registered surface, as the agent sees them. */
121
+ readonly manifests: readonly OUISurface[];
122
+ /** Register every surface and connect. */
123
+ start(): Promise<void>;
124
+ /**
125
+ * Deregister every surface and detach OUI from the socket. The socket stays
126
+ * connected: the integrator owns it, and other code may share it.
127
+ */
128
+ stop(): void;
129
+ /** Look up a registered surface by id. */
130
+ getSurface(id: string): DefinedSurface<never> | undefined;
82
131
  }
83
132
  /**
84
- * Define an OUI surface — the single entry point for integrating
85
- * an application feature with the OUI protocol.
86
- *
87
- * This function validates the definition and returns a typed surface
88
- * object that can be registered with an agent runtime or transport layer.
133
+ * Instantiate OUI.
89
134
  *
90
- * @example
91
- * ```typescript
92
- * const mySurface = defineSurface({
93
- * id: 'my-feature',
94
- * name: 'My Feature',
95
- * description: 'Does something cool',
96
- * actions: [
97
- * {
98
- * id: 'do_thing',
99
- * description: 'Does the thing',
100
- * input: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
101
- * handler: async (params) => {
102
- * const result = await doTheThing(params.name);
103
- * return { success: true, data: result };
104
- * },
105
- * },
106
- * ],
107
- * });
108
- * ```
135
+ * `const` on the type parameter captures surface ids as literal types, which is
136
+ * what allows duplicate detection to name the offending id rather than failing
137
+ * with an unassignable-to-never error.
109
138
  */
110
- declare function defineSurface<TContext = unknown>(definition: SurfaceDefinition<TContext>): DefinedSurface<TContext>;
111
- /** A fully defined surface with both manifest extraction and execution capabilities */
112
- interface DefinedSurface<TContext = unknown> extends SurfaceDefinition<TContext> {
113
- toManifest(): OUISurface;
114
- executeAction(actionId: string, params: Record<string, unknown>, context: TContext): Promise<ActionHandlerResult>;
115
- getActionIds(): string[];
116
- /** Get the polling config for an action (if async) */
117
- getPollingConfig(actionId: string): OUIActionPolling | undefined;
118
- }
139
+ declare function createOUI<const TSurfaces extends readonly DefinedSurface<never>[]>(config: OUIConfig<TSurfaces> & ValidateSurfaceIds<SurfaceIds<TSurfaces>>): OUIInstance;
119
140
 
120
- export { type ActionDefinition, type ActionHandlerResult, type ActionPollingConfig, type DefinedSurface, JSONSchema, OUIActivation, OUISurface, type ObservationDefinition, type SurfaceDefinition, defineSurface };
141
+ export { DefinedSurface, type DuplicateIds, OUIBranded, type OUIConfig, type OUIConfigError, type OUIInstance, OUISurface, type OwnedTransport, type ValidateSurfaceIds, createOUI };
@@ -1,4 +1,5 @@
1
- export { defineSurface } from '../chunk-JO5LGOB6.js';
2
- export { OUI_PROTOCOL_EVENTS } from '../chunk-CVUUSPF4.js';
1
+ export { createOUI, createSurfaceRuntime, defineSurface } from '../chunk-TRNVXAB5.js';
2
+ export { OUI_PROTOCOL_EVENTS } from '../chunk-BMCL2K74.js';
3
+ import '../chunk-F3WKIRAU.js';
3
4
  //# sourceMappingURL=index.js.map
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.cjs CHANGED
@@ -1,35 +1,56 @@
1
1
  'use strict';
2
2
 
3
- var chunkLEXIU2MH_cjs = require('./chunk-LEXIU2MH.cjs');
4
- var chunk3DCIL4FV_cjs = require('./chunk-3DCIL4FV.cjs');
5
- var chunk2ON5IEXB_cjs = require('./chunk-2ON5IEXB.cjs');
6
- var chunkWZXUKJ7E_cjs = require('./chunk-WZXUKJ7E.cjs');
3
+ var chunkKNWGQRXL_cjs = require('./chunk-KNWGQRXL.cjs');
4
+ var chunkZKM4XWAB_cjs = require('./chunk-ZKM4XWAB.cjs');
5
+ var chunkF3SKRAGU_cjs = require('./chunk-F3SKRAGU.cjs');
6
+ var chunkROGKEMLL_cjs = require('./chunk-ROGKEMLL.cjs');
7
+ var chunkDCVVTP3V_cjs = require('./chunk-DCVVTP3V.cjs');
7
8
 
8
9
 
9
10
 
11
+ Object.defineProperty(exports, "createOUI", {
12
+ enumerable: true,
13
+ get: function () { return chunkKNWGQRXL_cjs.createOUI; }
14
+ });
15
+ Object.defineProperty(exports, "createSurfaceRuntime", {
16
+ enumerable: true,
17
+ get: function () { return chunkKNWGQRXL_cjs.createSurfaceRuntime; }
18
+ });
10
19
  Object.defineProperty(exports, "defineSurface", {
11
20
  enumerable: true,
12
- get: function () { return chunkLEXIU2MH_cjs.defineSurface; }
21
+ get: function () { return chunkKNWGQRXL_cjs.defineSurface; }
13
22
  });
14
23
  Object.defineProperty(exports, "OUI_PROTOCOL_EVENTS", {
15
24
  enumerable: true,
16
- get: function () { return chunk3DCIL4FV_cjs.OUI_PROTOCOL_EVENTS; }
25
+ get: function () { return chunkZKM4XWAB_cjs.OUI_PROTOCOL_EVENTS; }
26
+ });
27
+ Object.defineProperty(exports, "SurfaceRuntimeProvider", {
28
+ enumerable: true,
29
+ get: function () { return chunkF3SKRAGU_cjs.SurfaceRuntimeProvider; }
17
30
  });
18
31
  Object.defineProperty(exports, "useObservation", {
19
32
  enumerable: true,
20
- get: function () { return chunk2ON5IEXB_cjs.useObservation; }
33
+ get: function () { return chunkF3SKRAGU_cjs.useObservation; }
21
34
  });
22
35
  Object.defineProperty(exports, "useSurface", {
23
36
  enumerable: true,
24
- get: function () { return chunk2ON5IEXB_cjs.useSurface; }
37
+ get: function () { return chunkF3SKRAGU_cjs.useSurface; }
38
+ });
39
+ Object.defineProperty(exports, "useSurfaceHold", {
40
+ enumerable: true,
41
+ get: function () { return chunkF3SKRAGU_cjs.useSurfaceHold; }
42
+ });
43
+ Object.defineProperty(exports, "useSurfaceRuntime", {
44
+ enumerable: true,
45
+ get: function () { return chunkF3SKRAGU_cjs.useSurfaceRuntime; }
25
46
  });
26
47
  Object.defineProperty(exports, "createDirectTransportPair", {
27
48
  enumerable: true,
28
- get: function () { return chunkWZXUKJ7E_cjs.createDirectTransportPair; }
49
+ get: function () { return chunkROGKEMLL_cjs.createDirectTransportPair; }
29
50
  });
30
51
  Object.defineProperty(exports, "createWebSocketTransport", {
31
52
  enumerable: true,
32
- get: function () { return chunkWZXUKJ7E_cjs.createWebSocketTransport; }
53
+ get: function () { return chunkDCVVTP3V_cjs.createWebSocketTransport; }
33
54
  });
34
55
  //# sourceMappingURL=index.cjs.map
35
56
  //# sourceMappingURL=index.cjs.map