oui-spec 0.1.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 (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +680 -0
  3. package/dist/chunk-2ON5IEXB.cjs +167 -0
  4. package/dist/chunk-2ON5IEXB.cjs.map +1 -0
  5. package/dist/chunk-3DCIL4FV.cjs +14 -0
  6. package/dist/chunk-3DCIL4FV.cjs.map +1 -0
  7. package/dist/chunk-7EXSMPGR.js +207 -0
  8. package/dist/chunk-7EXSMPGR.js.map +1 -0
  9. package/dist/chunk-CVUUSPF4.js +12 -0
  10. package/dist/chunk-CVUUSPF4.js.map +1 -0
  11. package/dist/chunk-JO5LGOB6.js +88 -0
  12. package/dist/chunk-JO5LGOB6.js.map +1 -0
  13. package/dist/chunk-LEXIU2MH.cjs +90 -0
  14. package/dist/chunk-LEXIU2MH.cjs.map +1 -0
  15. package/dist/chunk-UIGRLZOX.js +164 -0
  16. package/dist/chunk-UIGRLZOX.js.map +1 -0
  17. package/dist/chunk-WZXUKJ7E.cjs +210 -0
  18. package/dist/chunk-WZXUKJ7E.cjs.map +1 -0
  19. package/dist/core/index.cjs +17 -0
  20. package/dist/core/index.cjs.map +1 -0
  21. package/dist/core/index.d.cts +120 -0
  22. package/dist/core/index.d.ts +120 -0
  23. package/dist/core/index.js +4 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/index.cjs +35 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +4 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +6 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/react/index.cjs +16 -0
  32. package/dist/react/index.cjs.map +1 -0
  33. package/dist/react/index.d.cts +62 -0
  34. package/dist/react/index.d.ts +62 -0
  35. package/dist/react/index.js +3 -0
  36. package/dist/react/index.js.map +1 -0
  37. package/dist/spec/index.cjs +12 -0
  38. package/dist/spec/index.cjs.map +1 -0
  39. package/dist/spec/index.d.cts +249 -0
  40. package/dist/spec/index.d.ts +249 -0
  41. package/dist/spec/index.js +3 -0
  42. package/dist/spec/index.js.map +1 -0
  43. package/dist/transport/index.cjs +16 -0
  44. package/dist/transport/index.cjs.map +1 -0
  45. package/dist/transport/index.d.cts +91 -0
  46. package/dist/transport/index.d.ts +91 -0
  47. package/dist/transport/index.js +3 -0
  48. package/dist/transport/index.js.map +1 -0
  49. package/package.json +109 -0
  50. package/spec/OUI-SPEC-v0.1.md +1323 -0
@@ -0,0 +1,167 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/react/use-surface.ts
6
+ function useSurface(options) {
7
+ const { surface, context, send, active = true } = options;
8
+ const contextRef = react.useRef(context);
9
+ contextRef.current = context;
10
+ const surfaceRef = react.useRef(surface);
11
+ surfaceRef.current = surface;
12
+ const sendRef = react.useRef(send);
13
+ sendRef.current = send;
14
+ const activePollers = react.useRef(/* @__PURE__ */ new Map());
15
+ const pushObservation = react.useCallback((observationId, value) => {
16
+ const update = {
17
+ surfaceId: surfaceRef.current.id,
18
+ observationId,
19
+ value,
20
+ timestamp: Date.now()
21
+ };
22
+ sendRef.current({ type: "observation:update", payload: update });
23
+ }, []);
24
+ const startPolling = react.useCallback((actionId, pollingConfig, dispatchResult) => {
25
+ const observationId = `${surfaceRef.current.id}:${actionId}:status`;
26
+ let attempts = 0;
27
+ const maxAttempts = pollingConfig.maxAttempts ?? Infinity;
28
+ const startTime = Date.now();
29
+ const maxDuration = pollingConfig.maxDurationMs ?? Infinity;
30
+ if (pollingConfig.subscribe) {
31
+ pushObservation(observationId, { status: "dispatched", dispatchResult, interim: true });
32
+ return;
33
+ }
34
+ if (!pollingConfig.resolve) {
35
+ pushObservation(observationId, { status: "dispatched", dispatchResult, interim: false });
36
+ return;
37
+ }
38
+ pushObservation(observationId, { status: "polling", dispatchResult, interim: true });
39
+ const timer = setInterval(async () => {
40
+ attempts++;
41
+ const elapsed = Date.now() - startTime;
42
+ if (attempts > maxAttempts || elapsed > maxDuration) {
43
+ clearInterval(timer);
44
+ activePollers.current.delete(actionId);
45
+ pushObservation(observationId, { status: "timeout", dispatchResult, interim: false });
46
+ return;
47
+ }
48
+ try {
49
+ const action = surfaceRef.current.actions.find((a) => a.id === actionId);
50
+ if (action?.polling?.resolve) {
51
+ const result = await action.polling.resolve(dispatchResult, contextRef.current);
52
+ pushObservation(observationId, { ...result.data, interim: !result.done });
53
+ if (result.done) {
54
+ clearInterval(timer);
55
+ activePollers.current.delete(actionId);
56
+ }
57
+ }
58
+ } catch (err) {
59
+ pushObservation(observationId, { status: "poll_error", error: String(err), interim: true });
60
+ }
61
+ }, pollingConfig.intervalMs);
62
+ activePollers.current.set(actionId, timer);
63
+ }, [pushObservation]);
64
+ const clearAllPollers = react.useCallback(() => {
65
+ for (const [, timer] of activePollers.current) {
66
+ clearInterval(timer);
67
+ }
68
+ activePollers.current.clear();
69
+ }, []);
70
+ react.useEffect(() => {
71
+ if (!active) {
72
+ send({
73
+ type: "surface:deregister",
74
+ payload: { surfaceId: surface.id, timestamp: Date.now() }
75
+ });
76
+ return;
77
+ }
78
+ send({
79
+ type: "surface:register",
80
+ payload: {
81
+ surface: surface.toManifest(),
82
+ timestamp: Date.now()
83
+ }
84
+ });
85
+ return () => {
86
+ clearAllPollers();
87
+ send({
88
+ type: "surface:deregister",
89
+ payload: { surfaceId: surface.id, timestamp: Date.now() }
90
+ });
91
+ };
92
+ }, [surface.id, active, send, clearAllPollers]);
93
+ react.useEffect(() => {
94
+ return () => {
95
+ clearAllPollers();
96
+ };
97
+ }, [clearAllPollers]);
98
+ const handleActionRequest = react.useCallback(async (request) => {
99
+ if (request.surfaceId !== surfaceRef.current.id) return;
100
+ const { actionId, params } = request;
101
+ const observationId = `${surfaceRef.current.id}:${actionId}:status`;
102
+ let result;
103
+ try {
104
+ result = await surfaceRef.current.executeAction(actionId, params, contextRef.current);
105
+ } catch (err) {
106
+ pushObservation(observationId, {
107
+ status: "error",
108
+ error: err instanceof Error ? err.message : String(err),
109
+ interim: false
110
+ });
111
+ return;
112
+ }
113
+ pushObservation(observationId, {
114
+ status: result.success ? "dispatched" : "error",
115
+ data: result.data,
116
+ error: result.error,
117
+ interim: !!result.success
118
+ });
119
+ if (result.success) {
120
+ const action = surfaceRef.current.actions.find((a) => a.id === actionId);
121
+ if (action?.polling) {
122
+ startPolling(actionId, action.polling, result.data ?? result.dispatchMeta);
123
+ } else {
124
+ pushObservation(observationId, {
125
+ status: "complete",
126
+ data: result.data,
127
+ interim: false
128
+ });
129
+ }
130
+ }
131
+ }, [pushObservation, startPolling]);
132
+ return react.useMemo(() => ({
133
+ handleActionRequest,
134
+ surfaceId: surface.id,
135
+ manifest: surface.toManifest(),
136
+ /** Manually stop polling for a specific action */
137
+ stopPolling: (actionId) => {
138
+ const timer = activePollers.current.get(actionId);
139
+ if (timer) {
140
+ clearInterval(timer);
141
+ activePollers.current.delete(actionId);
142
+ }
143
+ },
144
+ /** Stop all active pollers */
145
+ stopAllPolling: clearAllPollers
146
+ }), [handleActionRequest, surface, clearAllPollers]);
147
+ }
148
+ function useObservation(surfaceId, observationId, value, send) {
149
+ const prevValueRef = react.useRef("");
150
+ react.useEffect(() => {
151
+ const serialized = JSON.stringify(value);
152
+ if (serialized === prevValueRef.current) return;
153
+ prevValueRef.current = serialized;
154
+ const update = {
155
+ surfaceId,
156
+ observationId,
157
+ value,
158
+ timestamp: Date.now()
159
+ };
160
+ send({ type: "observation:update", payload: update });
161
+ }, [surfaceId, observationId, value, send]);
162
+ }
163
+
164
+ exports.useObservation = useObservation;
165
+ exports.useSurface = useSurface;
166
+ //# sourceMappingURL=chunk-2ON5IEXB.cjs.map
167
+ //# sourceMappingURL=chunk-2ON5IEXB.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/use-surface.ts"],"names":["useRef","useCallback","useEffect","useMemo"],"mappings":";;;;;AA8CO,SAAS,WAAqB,OAAA,EAAsC;AACzE,EAAA,MAAM,EAAE,OAAA,EAAS,OAAA,EAAS,IAAA,EAAM,MAAA,GAAS,MAAK,GAAI,OAAA;AAClD,EAAA,MAAM,UAAA,GAAaA,aAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,UAAA,GAAaA,aAAO,OAAO,CAAA;AACjC,EAAA,UAAA,CAAW,OAAA,GAAU,OAAA;AAErB,EAAA,MAAM,OAAA,GAAUA,aAAO,IAAI,CAAA;AAC3B,EAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAGlB,EAAA,MAAM,aAAA,GAAgBA,YAAA,iBAAoD,IAAI,GAAA,EAAK,CAAA;AAGnF,EAAA,MAAM,eAAA,GAAkBC,iBAAA,CAAY,CAAC,aAAA,EAAuB,KAAA,KAAmB;AAC7E,IAAA,MAAM,MAAA,GAA+B;AAAA,MACnC,SAAA,EAAW,WAAW,OAAA,CAAQ,EAAA;AAAA,MAC9B,aAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AACA,IAAA,OAAA,CAAQ,QAAQ,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAS,QAAQ,CAAA;AAAA,EACjE,CAAA,EAAG,EAAE,CAAA;AAGL,EAAA,MAAM,YAAA,GAAeA,iBAAA,CAAY,CAC/B,QAAA,EACA,eACA,cAAA,KACG;AACH,IAAA,MAAM,gBAAgB,CAAA,EAAG,UAAA,CAAW,OAAA,CAAQ,EAAE,IAAI,QAAQ,CAAA,OAAA,CAAA;AAC1D,IAAA,IAAI,QAAA,GAAW,CAAA;AACf,IAAA,MAAM,WAAA,GAAc,cAAc,WAAA,IAAe,QAAA;AACjD,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,WAAA,GAAc,cAAc,aAAA,IAAiB,QAAA;AAGnD,IAAA,IAAI,cAAc,SAAA,EAAW;AAC3B,MAAA,eAAA,CAAgB,eAAe,EAAE,MAAA,EAAQ,cAAc,cAAA,EAAgB,OAAA,EAAS,MAAM,CAAA;AACtF,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,CAAC,cAAc,OAAA,EAAS;AAC1B,MAAA,eAAA,CAAgB,eAAe,EAAE,MAAA,EAAQ,cAAc,cAAA,EAAgB,OAAA,EAAS,OAAO,CAAA;AACvF,MAAA;AAAA,IACF;AAGA,IAAA,eAAA,CAAgB,eAAe,EAAE,MAAA,EAAQ,WAAW,cAAA,EAAgB,OAAA,EAAS,MAAM,CAAA;AAEnF,IAAA,MAAM,KAAA,GAAQ,YAAY,YAAY;AACpC,MAAA,QAAA,EAAA;AACA,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAE7B,MAAA,IAAI,QAAA,GAAW,WAAA,IAAe,OAAA,GAAU,WAAA,EAAa;AACnD,QAAA,aAAA,CAAc,KAAK,CAAA;AACnB,QAAA,aAAA,CAAc,OAAA,CAAQ,OAAO,QAAQ,CAAA;AACrC,QAAA,eAAA,CAAgB,eAAe,EAAE,MAAA,EAAQ,WAAW,cAAA,EAAgB,OAAA,EAAS,OAAO,CAAA;AACpF,QAAA;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,MAAA,GAAS,WAAW,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,QAAQ,CAAA;AACrE,QAAA,IAAI,MAAA,EAAQ,SAAS,OAAA,EAAS;AAC5B,UAAA,MAAM,SAAS,MAAM,MAAA,CAAO,QAAQ,OAAA,CAAQ,cAAA,EAAgB,WAAW,OAAO,CAAA;AAC9E,UAAA,eAAA,CAAgB,aAAA,EAAe,EAAE,GAAG,MAAA,CAAO,MAAiC,OAAA,EAAS,CAAC,MAAA,CAAO,IAAA,EAAM,CAAA;AACnG,UAAA,IAAI,OAAO,IAAA,EAAM;AACf,YAAA,aAAA,CAAc,KAAK,CAAA;AACnB,YAAA,aAAA,CAAc,OAAA,CAAQ,OAAO,QAAQ,CAAA;AAAA,UACvC;AAAA,QACF;AAAA,MACF,SAAS,GAAA,EAAK;AAEZ,QAAA,eAAA,CAAgB,aAAA,EAAe,EAAE,MAAA,EAAQ,YAAA,EAAc,KAAA,EAAO,OAAO,GAAG,CAAA,EAAG,OAAA,EAAS,IAAA,EAAM,CAAA;AAAA,MAC5F;AAAA,IACF,CAAA,EAAG,cAAc,UAAU,CAAA;AAE3B,IAAA,aAAA,CAAc,OAAA,CAAQ,GAAA,CAAI,QAAA,EAAU,KAAK,CAAA;AAAA,EAC3C,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAGpB,EAAA,MAAM,eAAA,GAAkBA,kBAAY,MAAM;AACxC,IAAA,KAAA,MAAW,GAAG,KAAK,CAAA,IAAK,cAAc,OAAA,EAAS;AAC7C,MAAA,aAAA,CAAc,KAAK,CAAA;AAAA,IACrB;AACA,IAAA,aAAA,CAAc,QAAQ,KAAA,EAAM;AAAA,EAC9B,CAAA,EAAG,EAAE,CAAA;AAGL,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,IAAA,CAAK;AAAA,QACH,IAAA,EAAM,oBAAA;AAAA,QACN,OAAA,EAAS,EAAE,SAAA,EAAW,OAAA,CAAQ,IAAI,SAAA,EAAW,IAAA,CAAK,KAAI;AAAE,OACzD,CAAA;AACD,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK;AAAA,MACH,IAAA,EAAM,kBAAA;AAAA,MACN,OAAA,EAAS;AAAA,QACP,OAAA,EAAS,QAAQ,UAAA,EAAW;AAAA,QAC5B,SAAA,EAAW,KAAK,GAAA;AAAI;AACtB,KACD,CAAA;AAED,IAAA,OAAO,MAAM;AAEX,MAAA,eAAA,EAAgB;AAChB,MAAA,IAAA,CAAK;AAAA,QACH,IAAA,EAAM,oBAAA;AAAA,QACN,OAAA,EAAS,EAAE,SAAA,EAAW,OAAA,CAAQ,IAAI,SAAA,EAAW,IAAA,CAAK,KAAI;AAAE,OACzD,CAAA;AAAA,IACH,CAAA;AAAA,EACF,GAAG,CAAC,OAAA,CAAQ,IAAI,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAC,CAAA;AAG9C,EAAAA,eAAA,CAAU,MAAM;AACd,IAAA,OAAO,MAAM;AACX,MAAA,eAAA,EAAgB;AAAA,IAClB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,eAAe,CAAC,CAAA;AAGpB,EAAA,MAAM,mBAAA,GAAsBD,iBAAA,CAAY,OAAO,OAAA,KAA8B;AAC3E,IAAA,IAAI,OAAA,CAAQ,SAAA,KAAc,UAAA,CAAW,OAAA,CAAQ,EAAA,EAAI;AAEjD,IAAA,MAAM,EAAE,QAAA,EAAU,MAAA,EAAO,GAAI,OAAA;AAC7B,IAAA,MAAM,gBAAgB,CAAA,EAAG,UAAA,CAAW,OAAA,CAAQ,EAAE,IAAI,QAAQ,CAAA,OAAA,CAAA;AAG1D,IAAA,IAAI,MAAA;AACJ,IAAA,IAAI;AACF,MAAA,MAAA,GAAS,MAAM,UAAA,CAAW,OAAA,CAAQ,cAAc,QAAA,EAAU,MAAA,EAAQ,WAAW,OAAO,CAAA;AAAA,IACtF,SAAS,GAAA,EAAK;AAEZ,MAAA,eAAA,CAAgB,aAAA,EAAe;AAAA,QAC7B,MAAA,EAAQ,OAAA;AAAA,QACR,OAAO,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAAA,QACtD,OAAA,EAAS;AAAA,OACV,CAAA;AACD,MAAA;AAAA,IACF;AAGA,IAAA,eAAA,CAAgB,aAAA,EAAe;AAAA,MAC7B,MAAA,EAAQ,MAAA,CAAO,OAAA,GAAU,YAAA,GAAe,OAAA;AAAA,MACxC,MAAM,MAAA,CAAO,IAAA;AAAA,MACb,OAAO,MAAA,CAAO,KAAA;AAAA,MACd,OAAA,EAAS,CAAC,CAAC,MAAA,CAAO;AAAA,KACnB,CAAA;AAGD,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,MAAM,MAAA,GAAS,WAAW,OAAA,CAAQ,OAAA,CAAQ,KAAK,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,QAAQ,CAAA;AACrE,MAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,QAAA,YAAA,CAAa,UAAU,MAAA,CAAO,OAAA,EAAS,MAAA,CAAO,IAAA,IAAQ,OAAO,YAAY,CAAA;AAAA,MAC3E,CAAA,MAAO;AAEL,QAAA,eAAA,CAAgB,aAAA,EAAe;AAAA,UAC7B,MAAA,EAAQ,UAAA;AAAA,UACR,MAAM,MAAA,CAAO,IAAA;AAAA,UACb,OAAA,EAAS;AAAA,SACV,CAAA;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAA,EAAG,CAAC,eAAA,EAAiB,YAAY,CAAC,CAAA;AAGlC,EAAA,OAAOE,cAAQ,OAAO;AAAA,IACpB,mBAAA;AAAA,IACA,WAAW,OAAA,CAAQ,EAAA;AAAA,IACnB,QAAA,EAAU,QAAQ,UAAA,EAAW;AAAA;AAAA,IAE7B,WAAA,EAAa,CAAC,QAAA,KAAqB;AACjC,MAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,OAAA,CAAQ,GAAA,CAAI,QAAQ,CAAA;AAChD,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,aAAA,CAAc,KAAK,CAAA;AACnB,QAAA,aAAA,CAAc,OAAA,CAAQ,OAAO,QAAQ,CAAA;AAAA,MACvC;AAAA,IACF,CAAA;AAAA;AAAA,IAEA,cAAA,EAAgB;AAAA,GAClB,CAAA,EAAI,CAAC,mBAAA,EAAqB,OAAA,EAAS,eAAe,CAAC,CAAA;AACrD;AAKO,SAAS,cAAA,CACd,SAAA,EACA,aAAA,EACA,KAAA,EACA,IAAA,EACA;AACA,EAAA,MAAM,YAAA,GAAeH,aAAe,EAAE,CAAA;AAEtC,EAAAE,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA;AACvC,IAAA,IAAI,UAAA,KAAe,aAAa,OAAA,EAAS;AACzC,IAAA,YAAA,CAAa,OAAA,GAAU,UAAA;AAEvB,IAAA,MAAM,MAAA,GAA+B;AAAA,MACnC,SAAA;AAAA,MACA,aAAA;AAAA,MACA,KAAA;AAAA,MACA,SAAA,EAAW,KAAK,GAAA;AAAI,KACtB;AAEA,IAAA,IAAA,CAAK,EAAE,IAAA,EAAM,oBAAA,EAAsB,OAAA,EAAS,QAAQ,CAAA;AAAA,EACtD,GAAG,CAAC,SAAA,EAAW,aAAA,EAAe,KAAA,EAAO,IAAI,CAAC,CAAA;AAC5C","file":"chunk-2ON5IEXB.cjs","sourcesContent":["import { useEffect, useRef, useCallback, useMemo } from 'react';\nimport type { DefinedSurface, ActionHandlerResult, ActionPollingConfig } from '../core/index.js';\nimport type { OUIActionRequest, OUIObservationUpdate } from '../spec/index.js';\n\n/**\n * Configuration for the useSurface hook.\n */\nexport interface UseSurfaceOptions<TContext> {\n /** The defined surface (from defineSurface()) */\n surface: DefinedSurface<TContext>;\n\n /** The context passed to action handlers (app state, utilities, etc.) */\n context: TContext;\n\n /** Transport send function — how to send protocol events to the agent runtime */\n send: (event: { type: string; payload: unknown }) => void;\n\n /** Whether the surface is currently active (respects activation conditions) */\n active?: boolean;\n}\n\n/**\n * useSurface — registers an OUI surface with the agent runtime and handles\n * incoming action requests by executing them locally and pushing observation\n * updates. Actions are dispatched one-way (no request/response correlation).\n *\n * After an action handler executes, if the action has a polling config, the\n * hook starts a polling loop that pushes observation updates until the\n * operation completes or times out.\n *\n * @example\n * ```tsx\n * function DataVizPage() {\n * const { state, updateState, goToStep } = useDataVizWizard();\n *\n * useSurface({\n * surface: datavizSurface,\n * context: { state, updateState, goToStep },\n * send: agentTransport.send,\n * active: true,\n * });\n *\n * return <DataVizWizard />;\n * }\n * ```\n */\nexport function useSurface<TContext>(options: UseSurfaceOptions<TContext>) {\n const { surface, context, send, active = true } = options;\n const contextRef = useRef(context);\n contextRef.current = context;\n\n const surfaceRef = useRef(surface);\n surfaceRef.current = surface;\n\n const sendRef = useRef(send);\n sendRef.current = send;\n\n // Track active polling intervals for cleanup\n const activePollers = useRef<Map<string, ReturnType<typeof setInterval>>>(new Map());\n\n // Push an observation update to the agent runtime\n const pushObservation = useCallback((observationId: string, value: unknown) => {\n const update: OUIObservationUpdate = {\n surfaceId: surfaceRef.current.id,\n observationId,\n value,\n timestamp: Date.now(),\n };\n sendRef.current({ type: 'observation:update', payload: update });\n }, []);\n\n // Start the polling runtime for an async action\n const startPolling = useCallback((\n actionId: string,\n pollingConfig: ActionPollingConfig<TContext>,\n dispatchResult: unknown,\n ) => {\n const observationId = `${surfaceRef.current.id}:${actionId}:status`;\n let attempts = 0;\n const maxAttempts = pollingConfig.maxAttempts ?? Infinity;\n const startTime = Date.now();\n const maxDuration = pollingConfig.maxDurationMs ?? Infinity;\n\n // Subscribe mode — transport layer handles events, just push initial state\n if (pollingConfig.subscribe) {\n pushObservation(observationId, { status: 'dispatched', dispatchResult, interim: true });\n return;\n }\n\n // Polling mode — requires a resolve function\n if (!pollingConfig.resolve) {\n pushObservation(observationId, { status: 'dispatched', dispatchResult, interim: false });\n return;\n }\n\n // Push initial dispatched state\n pushObservation(observationId, { status: 'polling', dispatchResult, interim: true });\n\n const timer = setInterval(async () => {\n attempts++;\n const elapsed = Date.now() - startTime;\n\n if (attempts > maxAttempts || elapsed > maxDuration) {\n clearInterval(timer);\n activePollers.current.delete(actionId);\n pushObservation(observationId, { status: 'timeout', dispatchResult, interim: false });\n return;\n }\n\n try {\n const action = surfaceRef.current.actions.find(a => a.id === actionId);\n if (action?.polling?.resolve) {\n const result = await action.polling.resolve(dispatchResult, contextRef.current);\n pushObservation(observationId, { ...result.data as Record<string, unknown>, interim: !result.done });\n if (result.done) {\n clearInterval(timer);\n activePollers.current.delete(actionId);\n }\n }\n } catch (err) {\n // Don't stop polling on transient errors — push error state and continue\n pushObservation(observationId, { status: 'poll_error', error: String(err), interim: true });\n }\n }, pollingConfig.intervalMs);\n\n activePollers.current.set(actionId, timer);\n }, [pushObservation]);\n\n // Clear all active pollers\n const clearAllPollers = useCallback(() => {\n for (const [, timer] of activePollers.current) {\n clearInterval(timer);\n }\n activePollers.current.clear();\n }, []);\n\n // Register surface when it becomes active\n useEffect(() => {\n if (!active) {\n send({\n type: 'surface:deregister',\n payload: { surfaceId: surface.id, timestamp: Date.now() },\n });\n return;\n }\n\n send({\n type: 'surface:register',\n payload: {\n surface: surface.toManifest(),\n timestamp: Date.now(),\n },\n });\n\n return () => {\n // Clean up all active pollers on deregister\n clearAllPollers();\n send({\n type: 'surface:deregister',\n payload: { surfaceId: surface.id, timestamp: Date.now() },\n });\n };\n }, [surface.id, active, send, clearAllPollers]);\n\n // Clean up pollers on unmount (safety net)\n useEffect(() => {\n return () => {\n clearAllPollers();\n };\n }, [clearAllPollers]);\n\n // Handler for incoming action requests — fully async, no response correlation\n const handleActionRequest = useCallback(async (request: OUIActionRequest) => {\n if (request.surfaceId !== surfaceRef.current.id) return;\n\n const { actionId, params } = request;\n const observationId = `${surfaceRef.current.id}:${actionId}:status`;\n\n // Execute the handler locally\n let result: ActionHandlerResult;\n try {\n result = await surfaceRef.current.executeAction(actionId, params, contextRef.current);\n } catch (err) {\n // Execution failed — push error observation\n pushObservation(observationId, {\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n interim: false,\n });\n return;\n }\n\n // Push observation with dispatch result immediately\n pushObservation(observationId, {\n status: result.success ? 'dispatched' : 'error',\n data: result.data,\n error: result.error,\n interim: !!result.success,\n });\n\n // If action has polling config and dispatch succeeded, start polling runtime\n if (result.success) {\n const action = surfaceRef.current.actions.find(a => a.id === actionId);\n if (action?.polling) {\n startPolling(actionId, action.polling, result.data ?? result.dispatchMeta);\n } else {\n // Non-async action — mark as complete (not interim)\n pushObservation(observationId, {\n status: 'complete',\n data: result.data,\n interim: false,\n });\n }\n }\n }, [pushObservation, startPolling]);\n\n // Expose the handler for the transport layer to call\n return useMemo(() => ({\n handleActionRequest,\n surfaceId: surface.id,\n manifest: surface.toManifest(),\n /** Manually stop polling for a specific action */\n stopPolling: (actionId: string) => {\n const timer = activePollers.current.get(actionId);\n if (timer) {\n clearInterval(timer);\n activePollers.current.delete(actionId);\n }\n },\n /** Stop all active pollers */\n stopAllPolling: clearAllPollers,\n }), [handleActionRequest, surface, clearAllPollers]);\n}\n\n/**\n * Helper to push an observation update to the agent runtime.\n */\nexport function useObservation(\n surfaceId: string,\n observationId: string,\n value: unknown,\n send: (event: { type: string; payload: unknown }) => void,\n) {\n const prevValueRef = useRef<string>('');\n\n useEffect(() => {\n const serialized = JSON.stringify(value);\n if (serialized === prevValueRef.current) return;\n prevValueRef.current = serialized;\n\n const update: OUIObservationUpdate = {\n surfaceId,\n observationId,\n value,\n timestamp: Date.now(),\n };\n\n send({ type: 'observation:update', payload: update });\n }, [surfaceId, observationId, value, send]);\n}\n"]}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ // src/spec/types.ts
4
+ var OUI_PROTOCOL_EVENTS = {
5
+ SURFACE_REGISTER: "surface:register",
6
+ SURFACE_DEREGISTER: "surface:deregister",
7
+ ACTION_REQUEST: "action:request",
8
+ ACTION_RESULT: "action:result",
9
+ OBSERVATION_UPDATE: "observation:update"
10
+ };
11
+
12
+ exports.OUI_PROTOCOL_EVENTS = OUI_PROTOCOL_EVENTS;
13
+ //# sourceMappingURL=chunk-3DCIL4FV.cjs.map
14
+ //# sourceMappingURL=chunk-3DCIL4FV.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/types.ts"],"names":[],"mappings":";;;AAgSO,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-3DCIL4FV.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/**\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"]}
@@ -0,0 +1,207 @@
1
+ // src/transport/websocket.ts
2
+ function createWebSocketTransport(socket, config) {
3
+ const ns = config?.namespace ?? "oui";
4
+ const buffer = [];
5
+ const maxBuffer = config?.maxBufferSize ?? 100;
6
+ const shouldBuffer = config?.bufferWhileDisconnected ?? true;
7
+ let connected = socket.connected ?? false;
8
+ const connectionHandlers = [];
9
+ socket.on("connect", () => {
10
+ connected = true;
11
+ connectionHandlers.forEach((h) => h(true));
12
+ if (shouldBuffer) {
13
+ while (buffer.length > 0) {
14
+ const msg = buffer.shift();
15
+ socket.emit(msg.event, msg.data);
16
+ }
17
+ }
18
+ });
19
+ socket.on("disconnect", () => {
20
+ connected = false;
21
+ connectionHandlers.forEach((h) => h(false));
22
+ });
23
+ function emit(event, data) {
24
+ if (connected) {
25
+ socket.emit(event, data);
26
+ } else if (shouldBuffer && buffer.length < maxBuffer) {
27
+ buffer.push({ event, data });
28
+ }
29
+ }
30
+ return {
31
+ // ─── Dispatch Channel ───────────────────────────────────────
32
+ dispatch(surfaceId, actionId, params) {
33
+ emit(`${ns}:dispatch`, { surfaceId, actionId, params, timestamp: Date.now() });
34
+ },
35
+ onAction(handler) {
36
+ const listener = (data) => {
37
+ handler(data.surfaceId, data.actionId, data.params);
38
+ };
39
+ socket.on(`${ns}:dispatch`, listener);
40
+ return () => socket.off(`${ns}:dispatch`, listener);
41
+ },
42
+ // ─── Observation Channel ────────────────────────────────────
43
+ pushObservation(update) {
44
+ emit(`${ns}:observation`, update);
45
+ },
46
+ onObservation(handler) {
47
+ const listener = (data) => handler(data);
48
+ socket.on(`${ns}:observation`, listener);
49
+ return () => socket.off(`${ns}:observation`, listener);
50
+ },
51
+ // ─── Surface Lifecycle ──────────────────────────────────────
52
+ registerSurface(surface) {
53
+ emit(`${ns}:surface:register`, { surface, timestamp: Date.now() });
54
+ },
55
+ deregisterSurface(surfaceId) {
56
+ emit(`${ns}:surface:deregister`, { surfaceId, timestamp: Date.now() });
57
+ },
58
+ onSurfaceRegister(handler) {
59
+ const listener = (data) => handler(data.surface);
60
+ socket.on(`${ns}:surface:register`, listener);
61
+ return () => socket.off(`${ns}:surface:register`, listener);
62
+ },
63
+ onSurfaceDeregister(handler) {
64
+ const listener = (data) => handler(data.surfaceId);
65
+ socket.on(`${ns}:surface:deregister`, listener);
66
+ return () => socket.off(`${ns}:surface:deregister`, listener);
67
+ },
68
+ // ─── Connection ─────────────────────────────────────────────
69
+ get connected() {
70
+ return connected;
71
+ },
72
+ async connect() {
73
+ if (connected) return;
74
+ return new Promise((resolve, reject) => {
75
+ const timeout = setTimeout(() => {
76
+ reject(new Error(`OUI transport connect timeout (${config?.connectTimeoutMs ?? 1e4}ms)`));
77
+ }, config?.connectTimeoutMs ?? 1e4);
78
+ socket.once("connect", () => {
79
+ clearTimeout(timeout);
80
+ resolve();
81
+ });
82
+ if (typeof socket.connect === "function") {
83
+ socket.connect();
84
+ }
85
+ });
86
+ },
87
+ disconnect() {
88
+ if (typeof socket.disconnect === "function") {
89
+ socket.disconnect();
90
+ }
91
+ },
92
+ onConnectionChange(handler) {
93
+ connectionHandlers.push(handler);
94
+ return () => {
95
+ const idx = connectionHandlers.indexOf(handler);
96
+ if (idx >= 0) connectionHandlers.splice(idx, 1);
97
+ };
98
+ }
99
+ };
100
+ }
101
+
102
+ // src/transport/direct.ts
103
+ function createDirectTransportPair() {
104
+ const actionHandlers = [];
105
+ const observationHandlers = [];
106
+ const surfaceRegisterHandlers = [];
107
+ const surfaceDeregisterHandlers = [];
108
+ const server = {
109
+ dispatch(surfaceId, actionId, params) {
110
+ actionHandlers.forEach((h) => h(surfaceId, actionId, params));
111
+ },
112
+ onAction() {
113
+ return () => {
114
+ };
115
+ },
116
+ // Server doesn't receive dispatches
117
+ pushObservation() {
118
+ },
119
+ // Server doesn't push observations
120
+ onObservation(handler) {
121
+ observationHandlers.push(handler);
122
+ return () => {
123
+ const i = observationHandlers.indexOf(handler);
124
+ if (i >= 0) observationHandlers.splice(i, 1);
125
+ };
126
+ },
127
+ registerSurface() {
128
+ },
129
+ deregisterSurface() {
130
+ },
131
+ onSurfaceRegister(handler) {
132
+ surfaceRegisterHandlers.push(handler);
133
+ return () => {
134
+ const i = surfaceRegisterHandlers.indexOf(handler);
135
+ if (i >= 0) surfaceRegisterHandlers.splice(i, 1);
136
+ };
137
+ },
138
+ onSurfaceDeregister(handler) {
139
+ surfaceDeregisterHandlers.push(handler);
140
+ return () => {
141
+ const i = surfaceDeregisterHandlers.indexOf(handler);
142
+ if (i >= 0) surfaceDeregisterHandlers.splice(i, 1);
143
+ };
144
+ },
145
+ get connected() {
146
+ return true;
147
+ },
148
+ async connect() {
149
+ },
150
+ disconnect() {
151
+ },
152
+ onConnectionChange() {
153
+ return () => {
154
+ };
155
+ }
156
+ };
157
+ const client = {
158
+ dispatch() {
159
+ },
160
+ // Client doesn't dispatch
161
+ onAction(handler) {
162
+ actionHandlers.push(handler);
163
+ return () => {
164
+ const i = actionHandlers.indexOf(handler);
165
+ if (i >= 0) actionHandlers.splice(i, 1);
166
+ };
167
+ },
168
+ pushObservation(update) {
169
+ observationHandlers.forEach((h) => h(update));
170
+ },
171
+ onObservation() {
172
+ return () => {
173
+ };
174
+ },
175
+ // Client doesn't receive observations
176
+ registerSurface(surface) {
177
+ surfaceRegisterHandlers.forEach((h) => h(surface));
178
+ },
179
+ deregisterSurface(surfaceId) {
180
+ surfaceDeregisterHandlers.forEach((h) => h(surfaceId));
181
+ },
182
+ onSurfaceRegister() {
183
+ return () => {
184
+ };
185
+ },
186
+ onSurfaceDeregister() {
187
+ return () => {
188
+ };
189
+ },
190
+ get connected() {
191
+ return true;
192
+ },
193
+ async connect() {
194
+ },
195
+ disconnect() {
196
+ },
197
+ onConnectionChange() {
198
+ return () => {
199
+ };
200
+ }
201
+ };
202
+ return { server, client };
203
+ }
204
+
205
+ export { createDirectTransportPair, createWebSocketTransport };
206
+ //# sourceMappingURL=chunk-7EXSMPGR.js.map
207
+ //# sourceMappingURL=chunk-7EXSMPGR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/transport/websocket.ts","../src/transport/direct.ts"],"names":[],"mappings":";AAaO,SAAS,wBAAA,CACd,QACA,MAAA,EACc;AACd,EAAA,MAAM,EAAA,GAAK,QAAQ,SAAA,IAAa,KAAA;AAChC,EAAA,MAAM,SAAkD,EAAC;AACzD,EAAA,MAAM,SAAA,GAAY,QAAQ,aAAA,IAAiB,GAAA;AAC3C,EAAA,MAAM,YAAA,GAAe,QAAQ,uBAAA,IAA2B,IAAA;AAExD,EAAA,IAAI,SAAA,GAAY,OAAO,SAAA,IAAa,KAAA;AACpC,EAAA,MAAM,qBAAkD,EAAC;AAGzD,EAAA,MAAA,CAAO,EAAA,CAAG,WAAW,MAAM;AACzB,IAAA,SAAA,GAAY,IAAA;AACZ,IAAA,kBAAA,CAAmB,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAC,CAAA;AAEvC,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,OAAO,MAAA,CAAO,SAAS,CAAA,EAAG;AACxB,QAAA,MAAM,GAAA,GAAM,OAAO,KAAA,EAAM;AACzB,QAAA,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,GAAA,CAAI,IAAI,CAAA;AAAA,MACjC;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAA,CAAO,EAAA,CAAG,cAAc,MAAM;AAC5B,IAAA,SAAA,GAAY,KAAA;AACZ,IAAA,kBAAA,CAAmB,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,EAC1C,CAAC,CAAA;AAED,EAAA,SAAS,IAAA,CAAK,OAAe,IAAA,EAAe;AAC1C,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,MAAA,CAAO,IAAA,CAAK,OAAO,IAAI,CAAA;AAAA,IACzB,CAAA,MAAA,IAAW,YAAA,IAAgB,MAAA,CAAO,MAAA,GAAS,SAAA,EAAW;AACpD,MAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA,IAC7B;AAAA,EACF;AAEA,EAAA,OAAO;AAAA;AAAA,IAEL,QAAA,CAAS,SAAA,EAAW,QAAA,EAAU,MAAA,EAAQ;AACpC,MAAA,IAAA,CAAK,CAAA,EAAG,EAAE,CAAA,SAAA,CAAA,EAAa,EAAE,SAAA,EAAW,QAAA,EAAU,MAAA,EAAQ,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG,CAAA;AAAA,IAC/E,CAAA;AAAA,IAEA,SAAS,OAAA,EAA2B;AAClC,MAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAmF;AACnG,QAAA,OAAA,CAAQ,IAAA,CAAK,SAAA,EAAW,IAAA,CAAK,QAAA,EAAU,KAAK,MAAM,CAAA;AAAA,MACpD,CAAA;AACA,MAAA,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,CAAA,SAAA,CAAA,EAAa,QAAQ,CAAA;AACpC,MAAA,OAAO,MAAM,MAAA,CAAO,GAAA,CAAI,CAAA,EAAG,EAAE,aAAa,QAAQ,CAAA;AAAA,IACpD,CAAA;AAAA;AAAA,IAGA,gBAAgB,MAAA,EAA8B;AAC5C,MAAA,IAAA,CAAK,CAAA,EAAG,EAAE,CAAA,YAAA,CAAA,EAAgB,MAAM,CAAA;AAAA,IAClC,CAAA;AAAA,IAEA,cAAc,OAAA,EAAgC;AAC5C,MAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAA+B,OAAA,CAAQ,IAAI,CAAA;AAC7D,MAAA,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,CAAA,YAAA,CAAA,EAAgB,QAAQ,CAAA;AACvC,MAAA,OAAO,MAAM,MAAA,CAAO,GAAA,CAAI,CAAA,EAAG,EAAE,gBAAgB,QAAQ,CAAA;AAAA,IACvD,CAAA;AAAA;AAAA,IAGA,gBAAgB,OAAA,EAAqB;AACnC,MAAA,IAAA,CAAK,CAAA,EAAG,EAAE,CAAA,iBAAA,CAAA,EAAqB,EAAE,SAAS,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG,CAAA;AAAA,IACnE,CAAA;AAAA,IAEA,kBAAkB,SAAA,EAAmB;AACnC,MAAA,IAAA,CAAK,CAAA,EAAG,EAAE,CAAA,mBAAA,CAAA,EAAuB,EAAE,WAAW,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG,CAAA;AAAA,IACvE,CAAA;AAAA,IAEA,kBAAkB,OAAA,EAAwC;AACxD,MAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAkC,OAAA,CAAQ,KAAK,OAAO,CAAA;AACxE,MAAA,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,CAAA,iBAAA,CAAA,EAAqB,QAAQ,CAAA;AAC5C,MAAA,OAAO,MAAM,MAAA,CAAO,GAAA,CAAI,CAAA,EAAG,EAAE,qBAAqB,QAAQ,CAAA;AAAA,IAC5D,CAAA;AAAA,IAEA,oBAAoB,OAAA,EAAsC;AACxD,MAAA,MAAM,QAAA,GAAW,CAAC,IAAA,KAAgC,OAAA,CAAQ,KAAK,SAAS,CAAA;AACxE,MAAA,MAAA,CAAO,EAAA,CAAG,CAAA,EAAG,EAAE,CAAA,mBAAA,CAAA,EAAuB,QAAQ,CAAA;AAC9C,MAAA,OAAO,MAAM,MAAA,CAAO,GAAA,CAAI,CAAA,EAAG,EAAE,uBAAuB,QAAQ,CAAA;AAAA,IAC9D,CAAA;AAAA;AAAA,IAGA,IAAI,SAAA,GAAY;AAAE,MAAA,OAAO,SAAA;AAAA,IAAW,CAAA;AAAA,IAEpC,MAAM,OAAA,GAAU;AACd,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,OAAO,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC5C,QAAA,MAAM,OAAA,GAAU,WAAW,MAAM;AAC/B,UAAA,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,QAAQ,gBAAA,IAAoB,GAAK,KAAK,CAAC,CAAA;AAAA,QAC5F,CAAA,EAAG,MAAA,EAAQ,gBAAA,IAAoB,GAAK,CAAA;AAEpC,QAAA,MAAA,CAAO,IAAA,CAAK,WAAW,MAAM;AAC3B,UAAA,YAAA,CAAa,OAAO,CAAA;AACpB,UAAA,OAAA,EAAQ;AAAA,QACV,CAAC,CAAA;AAED,QAAA,IAAI,OAAO,MAAA,CAAO,OAAA,KAAY,UAAA,EAAY;AACxC,UAAA,MAAA,CAAO,OAAA,EAAQ;AAAA,QACjB;AAAA,MACF,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,UAAA,GAAa;AACX,MAAA,IAAI,OAAO,MAAA,CAAO,UAAA,KAAe,UAAA,EAAY;AAC3C,QAAA,MAAA,CAAO,UAAA,EAAW;AAAA,MACpB;AAAA,IACF,CAAA;AAAA,IAEA,mBAAmB,OAAA,EAA+B;AAChD,MAAA,kBAAA,CAAmB,KAAK,OAAO,CAAA;AAC/B,MAAA,OAAO,MAAM;AACX,QAAA,MAAM,GAAA,GAAM,kBAAA,CAAmB,OAAA,CAAQ,OAAO,CAAA;AAC9C,QAAA,IAAI,GAAA,IAAO,CAAA,EAAG,kBAAA,CAAmB,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,MAChD,CAAA;AAAA,IACF;AAAA,GACF;AACF;;;ACzHO,SAAS,yBAAA,GAA4E;AAC1F,EAAA,MAAM,iBAAqC,EAAC;AAC5C,EAAA,MAAM,sBAA+C,EAAC;AACtD,EAAA,MAAM,0BAA0D,EAAC;AACjE,EAAA,MAAM,4BAAyD,EAAC;AAEhE,EAAA,MAAM,MAAA,GAAuB;AAAA,IAC3B,QAAA,CAAS,SAAA,EAAW,QAAA,EAAU,MAAA,EAAQ;AAEpC,MAAA,cAAA,CAAe,QAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,SAAA,EAAW,QAAA,EAAU,MAAM,CAAC,CAAA;AAAA,IAC5D,CAAA;AAAA,IACA,QAAA,GAAW;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG,CAAA;AAAA;AAAA,IAC9B,eAAA,GAAkB;AAAA,IAAC,CAAA;AAAA;AAAA,IACnB,cAAc,OAAA,EAAS;AACrB,MAAA,mBAAA,CAAoB,KAAK,OAAO,CAAA;AAChC,MAAA,OAAO,MAAM;AAAE,QAAA,MAAM,CAAA,GAAI,mBAAA,CAAoB,OAAA,CAAQ,OAAO,CAAA;AAAG,QAAA,IAAI,CAAA,IAAK,CAAA,EAAG,mBAAA,CAAoB,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MAAG,CAAA;AAAA,IAC/G,CAAA;AAAA,IACA,eAAA,GAAkB;AAAA,IAAC,CAAA;AAAA,IACnB,iBAAA,GAAoB;AAAA,IAAC,CAAA;AAAA,IACrB,kBAAkB,OAAA,EAAS;AACzB,MAAA,uBAAA,CAAwB,KAAK,OAAO,CAAA;AACpC,MAAA,OAAO,MAAM;AAAE,QAAA,MAAM,CAAA,GAAI,uBAAA,CAAwB,OAAA,CAAQ,OAAO,CAAA;AAAG,QAAA,IAAI,CAAA,IAAK,CAAA,EAAG,uBAAA,CAAwB,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MAAG,CAAA;AAAA,IACvH,CAAA;AAAA,IACA,oBAAoB,OAAA,EAAS;AAC3B,MAAA,yBAAA,CAA0B,KAAK,OAAO,CAAA;AACtC,MAAA,OAAO,MAAM;AAAE,QAAA,MAAM,CAAA,GAAI,yBAAA,CAA0B,OAAA,CAAQ,OAAO,CAAA;AAAG,QAAA,IAAI,CAAA,IAAK,CAAA,EAAG,yBAAA,CAA0B,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MAAG,CAAA;AAAA,IAC3H,CAAA;AAAA,IACA,IAAI,SAAA,GAAY;AAAE,MAAA,OAAO,IAAA;AAAA,IAAM,CAAA;AAAA,IAC/B,MAAM,OAAA,GAAU;AAAA,IAAC,CAAA;AAAA,IACjB,UAAA,GAAa;AAAA,IAAC,CAAA;AAAA,IACd,kBAAA,GAAqB;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG;AAAA,GAC1C;AAEA,EAAA,MAAM,MAAA,GAAuB;AAAA,IAC3B,QAAA,GAAW;AAAA,IAAC,CAAA;AAAA;AAAA,IACZ,SAAS,OAAA,EAAS;AAChB,MAAA,cAAA,CAAe,KAAK,OAAO,CAAA;AAC3B,MAAA,OAAO,MAAM;AAAE,QAAA,MAAM,CAAA,GAAI,cAAA,CAAe,OAAA,CAAQ,OAAO,CAAA;AAAG,QAAA,IAAI,CAAA,IAAK,CAAA,EAAG,cAAA,CAAe,MAAA,CAAO,GAAG,CAAC,CAAA;AAAA,MAAG,CAAA;AAAA,IACrG,CAAA;AAAA,IACA,gBAAgB,MAAA,EAA8B;AAE5C,MAAA,mBAAA,CAAoB,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,MAAM,CAAC,CAAA;AAAA,IAC5C,CAAA;AAAA,IACA,aAAA,GAAgB;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG,CAAA;AAAA;AAAA,IACnC,gBAAgB,OAAA,EAAS;AACvB,MAAA,uBAAA,CAAwB,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,OAAO,CAAC,CAAA;AAAA,IACjD,CAAA;AAAA,IACA,kBAAkB,SAAA,EAAW;AAC3B,MAAA,yBAAA,CAA0B,OAAA,CAAQ,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,CAAC,CAAA;AAAA,IACrD,CAAA;AAAA,IACA,iBAAA,GAAoB;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG,CAAA;AAAA,IACvC,mBAAA,GAAsB;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG,CAAA;AAAA,IACzC,IAAI,SAAA,GAAY;AAAE,MAAA,OAAO,IAAA;AAAA,IAAM,CAAA;AAAA,IAC/B,MAAM,OAAA,GAAU;AAAA,IAAC,CAAA;AAAA,IACjB,UAAA,GAAa;AAAA,IAAC,CAAA;AAAA,IACd,kBAAA,GAAqB;AAAE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAAG;AAAA,GAC1C;AAEA,EAAA,OAAO,EAAE,QAAQ,MAAA,EAAO;AAC1B","file":"chunk-7EXSMPGR.js","sourcesContent":["import type { OUITransport, OUITransportConfig, OUIActionHandler, OUIObservationHandler } from './types.js';\nimport type { OUISurface, OUIObservationUpdate } 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 emitted:\n * {ns}:dispatch — action dispatch (server → client)\n * {ns}:observation — observation update (client → server)\n * {ns}:surface:register — surface registration (client → server)\n * {ns}:surface:deregister — surface deregistration (client → server)\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 }> = [];\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 // Track connection state\n socket.on('connect', () => {\n connected = true;\n connectionHandlers.forEach(h => h(true));\n // Flush buffer\n if (shouldBuffer) {\n while (buffer.length > 0) {\n const msg = buffer.shift()!;\n socket.emit(msg.event, msg.data);\n }\n }\n });\n\n socket.on('disconnect', () => {\n connected = false;\n connectionHandlers.forEach(h => h(false));\n });\n\n function emit(event: string, data: unknown) {\n if (connected) {\n socket.emit(event, data);\n } else if (shouldBuffer && buffer.length < maxBuffer) {\n buffer.push({ event, data });\n }\n }\n\n return {\n // ─── Dispatch Channel ───────────────────────────────────────\n dispatch(surfaceId, actionId, params) {\n emit(`${ns}:dispatch`, { surfaceId, actionId, params, timestamp: Date.now() });\n },\n\n onAction(handler: OUIActionHandler) {\n const listener = (data: { surfaceId: string; actionId: string; params: Record<string, unknown> }) => {\n handler(data.surfaceId, data.actionId, data.params);\n };\n socket.on(`${ns}:dispatch`, listener);\n return () => socket.off(`${ns}:dispatch`, listener);\n },\n\n // ─── Observation Channel ────────────────────────────────────\n pushObservation(update: OUIObservationUpdate) {\n emit(`${ns}:observation`, update);\n },\n\n onObservation(handler: OUIObservationHandler) {\n const listener = (data: OUIObservationUpdate) => handler(data);\n socket.on(`${ns}:observation`, listener);\n return () => socket.off(`${ns}:observation`, listener);\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 const listener = (data: { surface: OUISurface }) => handler(data.surface);\n socket.on(`${ns}:surface:register`, listener);\n return () => socket.off(`${ns}:surface:register`, listener);\n },\n\n onSurfaceDeregister(handler: (surfaceId: string) => void) {\n const listener = (data: { surfaceId: string }) => handler(data.surfaceId);\n socket.on(`${ns}:surface:deregister`, listener);\n return () => socket.off(`${ns}:surface:deregister`, listener);\n },\n\n // ─── Connection ─────────────────────────────────────────────\n get connected() { return connected; },\n\n async connect() {\n if (connected) return;\n return new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n reject(new Error(`OUI transport connect timeout (${config?.connectTimeoutMs ?? 10000}ms)`));\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}\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 emit(event: string, data: unknown): 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","import type { OUITransport, OUIActionHandler, OUIObservationHandler } from './types.js';\nimport type { OUISurface, OUIObservationUpdate } from '../spec/index.js';\n\n/**\n * Direct transport — for same-process communication.\n * Used for API surfaces (where the agent runtime and surface are in the same Lambda)\n * and for testing (where you want synchronous, in-memory communication).\n *\n * Creates a paired (server, client) transport where messages sent on one\n * side are immediately received on the other.\n */\nexport function createDirectTransportPair(): { server: OUITransport; client: OUITransport } {\n const actionHandlers: OUIActionHandler[] = [];\n const observationHandlers: OUIObservationHandler[] = [];\n const surfaceRegisterHandlers: Array<(s: OUISurface) => void> = [];\n const surfaceDeregisterHandlers: Array<(id: string) => void> = [];\n\n const server: OUITransport = {\n dispatch(surfaceId, actionId, params) {\n // Server dispatches → client receives\n actionHandlers.forEach(h => h(surfaceId, actionId, params));\n },\n onAction() { return () => {}; }, // Server doesn't receive dispatches\n pushObservation() {}, // Server doesn't push observations\n onObservation(handler) {\n observationHandlers.push(handler);\n return () => { const i = observationHandlers.indexOf(handler); if (i >= 0) observationHandlers.splice(i, 1); };\n },\n registerSurface() {},\n deregisterSurface() {},\n onSurfaceRegister(handler) {\n surfaceRegisterHandlers.push(handler);\n return () => { const i = surfaceRegisterHandlers.indexOf(handler); if (i >= 0) surfaceRegisterHandlers.splice(i, 1); };\n },\n onSurfaceDeregister(handler) {\n surfaceDeregisterHandlers.push(handler);\n return () => { const i = surfaceDeregisterHandlers.indexOf(handler); if (i >= 0) surfaceDeregisterHandlers.splice(i, 1); };\n },\n get connected() { return true; },\n async connect() {},\n disconnect() {},\n onConnectionChange() { return () => {}; },\n };\n\n const client: OUITransport = {\n dispatch() {}, // Client doesn't dispatch\n onAction(handler) {\n actionHandlers.push(handler);\n return () => { const i = actionHandlers.indexOf(handler); if (i >= 0) actionHandlers.splice(i, 1); };\n },\n pushObservation(update: OUIObservationUpdate) {\n // Client pushes → server receives\n observationHandlers.forEach(h => h(update));\n },\n onObservation() { return () => {}; }, // Client doesn't receive observations\n registerSurface(surface) {\n surfaceRegisterHandlers.forEach(h => h(surface));\n },\n deregisterSurface(surfaceId) {\n surfaceDeregisterHandlers.forEach(h => h(surfaceId));\n },\n onSurfaceRegister() { return () => {}; },\n onSurfaceDeregister() { return () => {}; },\n get connected() { return true; },\n async connect() {},\n disconnect() {},\n onConnectionChange() { return () => {}; },\n };\n\n return { server, client };\n}\n"]}
@@ -0,0 +1,12 @@
1
+ // src/spec/types.ts
2
+ var OUI_PROTOCOL_EVENTS = {
3
+ SURFACE_REGISTER: "surface:register",
4
+ SURFACE_DEREGISTER: "surface:deregister",
5
+ ACTION_REQUEST: "action:request",
6
+ ACTION_RESULT: "action:result",
7
+ OBSERVATION_UPDATE: "observation:update"
8
+ };
9
+
10
+ export { OUI_PROTOCOL_EVENTS };
11
+ //# sourceMappingURL=chunk-CVUUSPF4.js.map
12
+ //# sourceMappingURL=chunk-CVUUSPF4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/spec/types.ts"],"names":[],"mappings":";AAgSO,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-CVUUSPF4.js","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/**\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"]}
@@ -0,0 +1,88 @@
1
+ // src/core/define-surface.ts
2
+ function defineSurface(definition) {
3
+ const actionIds = /* @__PURE__ */ new Set();
4
+ for (const action of definition.actions) {
5
+ if (actionIds.has(action.id)) {
6
+ throw new Error(`[OUI] Duplicate action ID "${action.id}" in surface "${definition.id}"`);
7
+ }
8
+ actionIds.add(action.id);
9
+ }
10
+ if (definition.observations) {
11
+ const obsIds = /* @__PURE__ */ new Set();
12
+ for (const obs of definition.observations) {
13
+ if (obsIds.has(obs.id)) {
14
+ throw new Error(`[OUI] Duplicate observation ID "${obs.id}" in surface "${definition.id}"`);
15
+ }
16
+ obsIds.add(obs.id);
17
+ }
18
+ }
19
+ return {
20
+ ...definition,
21
+ /** Extract the manifest (schema only, no handlers) for transmission to the agent runtime */
22
+ toManifest() {
23
+ return {
24
+ id: definition.id,
25
+ name: definition.name,
26
+ description: definition.description,
27
+ version: definition.version,
28
+ activation: definition.activation,
29
+ actions: definition.actions.map(actionToManifest),
30
+ observations: definition.observations?.map(obsToManifest),
31
+ metadata: definition.metadata
32
+ };
33
+ },
34
+ /** Execute an action by ID with the given params and context */
35
+ async executeAction(actionId, params, context) {
36
+ const action = definition.actions.find((a) => a.id === actionId);
37
+ if (!action) {
38
+ return { success: false, error: { code: "ACTION_NOT_FOUND", message: `Action "${actionId}" not found on surface "${definition.id}"` } };
39
+ }
40
+ try {
41
+ return await action.handler(params, context);
42
+ } catch (err) {
43
+ return {
44
+ success: false,
45
+ error: {
46
+ code: "ACTION_EXECUTION_ERROR",
47
+ message: err instanceof Error ? err.message : String(err)
48
+ }
49
+ };
50
+ }
51
+ },
52
+ /** Get all action IDs */
53
+ getActionIds() {
54
+ return definition.actions.map((a) => a.id);
55
+ },
56
+ /** Get the polling config for an action (if async) */
57
+ getPollingConfig(actionId) {
58
+ const action = definition.actions.find((a) => a.id === actionId);
59
+ return action?.polling;
60
+ }
61
+ };
62
+ }
63
+ function actionToManifest(action) {
64
+ return {
65
+ id: action.id,
66
+ description: action.description,
67
+ input: action.input,
68
+ output: action.output,
69
+ confirm: action.confirm,
70
+ async: action.async,
71
+ polling: action.polling,
72
+ usage: action.usage,
73
+ preconditions: action.preconditions,
74
+ estimatedDuration: action.estimatedDuration,
75
+ tags: action.tags
76
+ };
77
+ }
78
+ function obsToManifest(obs) {
79
+ return {
80
+ id: obs.id,
81
+ description: obs.description,
82
+ schema: obs.schema
83
+ };
84
+ }
85
+
86
+ export { defineSurface };
87
+ //# sourceMappingURL=chunk-JO5LGOB6.js.map
88
+ //# sourceMappingURL=chunk-JO5LGOB6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/define-surface.ts"],"names":[],"mappings":"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","file":"chunk-JO5LGOB6.js","sourcesContent":["import type { OUISurface, OUIAction, OUIObservation, OUIActivation, OUIActionPolling, JSONSchema } from '../spec/index.js';\n\n/**\n * Configuration for defining an OUI surface with handlers.\n * This is the \"integration file\" an app provides — it declares both\n * the schema (what the agent sees) and the implementation (what executes).\n */\nexport interface SurfaceDefinition<TContext = unknown> {\n id: string;\n name: string;\n description: string;\n version?: string;\n activation?: OUIActivation;\n actions: ActionDefinition<TContext>[];\n observations?: ObservationDefinition[];\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Polling config with an optional resolve handler.\n * The spec's OUIActionPolling is the manifest shape (no functions).\n * The core extends it with the resolve implementation for the runtime.\n */\nexport interface ActionPollingConfig<TContext = unknown> extends OUIActionPolling {\n /**\n * Called on each poll interval. Receives the dispatch result from the handler\n * plus the current context. Return { done: true, data } to stop polling,\n * or { done: false, data } to continue.\n */\n resolve?: (dispatchResult: unknown, context: TContext) => Promise<{ done: boolean; data: unknown }>;\n}\n\n/**\n * An action definition includes both the schema (for the agent) and\n * the handler (for execution). This is the single source of truth —\n * no separate YAML, no separate listener, no drift possible.\n */\nexport interface ActionDefinition<TContext = unknown> {\n id: string;\n description: string;\n input: JSONSchema;\n output?: JSONSchema;\n confirm?: boolean;\n async?: boolean;\n\n /**\n * Polling/subscription config for async actions.\n * After the handler returns, the OUI runtime starts polling or subscribing\n * and pushes observation updates until the operation completes.\n * Extends the spec's OUIActionPolling with an optional resolve handler.\n */\n polling?: ActionPollingConfig<TContext>;\n\n usage?: string;\n preconditions?: string;\n estimatedDuration?: string;\n tags?: string[];\n\n /**\n * The handler that executes when the agent invokes this action.\n * Receives typed params and a context object (app-specific state/utilities).\n * Returns a result that the agent sees as the tool output.\n */\n handler: (params: Record<string, unknown>, context: TContext) => Promise<ActionHandlerResult>;\n}\n\nexport interface ActionHandlerResult {\n success: boolean;\n data?: unknown;\n error?: { code: string; message: string; details?: unknown };\n /** For async actions: metadata passed to the polling resolver */\n dispatchMeta?: Record<string, unknown>;\n}\n\nexport interface ObservationDefinition {\n id: string;\n description: string;\n schema: JSONSchema;\n /** Function that returns the current observation value */\n getValue?: () => unknown;\n}\n\n/**\n * Define an OUI surface — the single entry point for integrating\n * an application feature with the OUI protocol.\n *\n * This function validates the definition and returns a typed surface\n * object that can be registered with an agent runtime or transport layer.\n *\n * @example\n * ```typescript\n * const mySurface = defineSurface({\n * id: 'my-feature',\n * name: 'My Feature',\n * description: 'Does something cool',\n * actions: [\n * {\n * id: 'do_thing',\n * description: 'Does the thing',\n * input: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },\n * handler: async (params) => {\n * const result = await doTheThing(params.name);\n * return { success: true, data: result };\n * },\n * },\n * ],\n * });\n * ```\n */\nexport function defineSurface<TContext = unknown>(\n definition: SurfaceDefinition<TContext>,\n): DefinedSurface<TContext> {\n // Validate: no duplicate action IDs\n const actionIds = new Set<string>();\n for (const action of definition.actions) {\n if (actionIds.has(action.id)) {\n throw new Error(`[OUI] Duplicate action ID \"${action.id}\" in surface \"${definition.id}\"`);\n }\n actionIds.add(action.id);\n }\n\n // Validate: no duplicate observation IDs\n if (definition.observations) {\n const obsIds = new Set<string>();\n for (const obs of definition.observations) {\n if (obsIds.has(obs.id)) {\n throw new Error(`[OUI] Duplicate observation ID \"${obs.id}\" in surface \"${definition.id}\"`);\n }\n obsIds.add(obs.id);\n }\n }\n\n return {\n ...definition,\n\n /** Extract the manifest (schema only, no handlers) for transmission to the agent runtime */\n toManifest(): OUISurface {\n return {\n id: definition.id,\n name: definition.name,\n description: definition.description,\n version: definition.version,\n activation: definition.activation,\n actions: definition.actions.map(actionToManifest),\n observations: definition.observations?.map(obsToManifest),\n metadata: definition.metadata,\n };\n },\n\n /** Execute an action by ID with the given params and context */\n async executeAction(actionId: string, params: Record<string, unknown>, context: TContext): Promise<ActionHandlerResult> {\n const action = definition.actions.find(a => a.id === actionId);\n if (!action) {\n return { success: false, error: { code: 'ACTION_NOT_FOUND', message: `Action \"${actionId}\" not found on surface \"${definition.id}\"` } };\n }\n try {\n return await action.handler(params, context);\n } catch (err) {\n return {\n success: false,\n error: {\n code: 'ACTION_EXECUTION_ERROR',\n message: err instanceof Error ? err.message : String(err),\n },\n };\n }\n },\n\n /** Get all action IDs */\n getActionIds(): string[] {\n return definition.actions.map(a => a.id);\n },\n\n /** Get the polling config for an action (if async) */\n getPollingConfig(actionId: string): OUIActionPolling | undefined {\n const action = definition.actions.find(a => a.id === actionId);\n return action?.polling;\n },\n };\n}\n\n/** A fully defined surface with both manifest extraction and execution capabilities */\nexport interface DefinedSurface<TContext = unknown> extends SurfaceDefinition<TContext> {\n toManifest(): OUISurface;\n executeAction(actionId: string, params: Record<string, unknown>, context: TContext): Promise<ActionHandlerResult>;\n getActionIds(): string[];\n /** Get the polling config for an action (if async) */\n getPollingConfig(actionId: string): OUIActionPolling | undefined;\n}\n\n// ─── Internal helpers ────────────────────────────────────────────────────────\n\nfunction actionToManifest<T>(action: ActionDefinition<T>): OUIAction {\n return {\n id: action.id,\n description: action.description,\n input: action.input,\n output: action.output,\n confirm: action.confirm,\n async: action.async,\n polling: action.polling,\n usage: action.usage,\n preconditions: action.preconditions,\n estimatedDuration: action.estimatedDuration,\n tags: action.tags,\n };\n}\n\nfunction obsToManifest(obs: ObservationDefinition): OUIObservation {\n return {\n id: obs.id,\n description: obs.description,\n schema: obs.schema,\n };\n}\n"]}