@ggui-ai/protocol 0.1.0-rc.1

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 (222) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +46 -0
  3. package/dist/bridge/invoke-agent.d.ts +65 -0
  4. package/dist/bridge/invoke-agent.d.ts.map +1 -0
  5. package/dist/bridge/invoke-agent.js +113 -0
  6. package/dist/envelope-adapters.d.ts +24 -0
  7. package/dist/envelope-adapters.d.ts.map +1 -0
  8. package/dist/envelope-adapters.js +14 -0
  9. package/dist/envelopes/builders.d.ts +145 -0
  10. package/dist/envelopes/builders.d.ts.map +1 -0
  11. package/dist/envelopes/builders.js +113 -0
  12. package/dist/errors/unknown-permission-name.d.ts +12 -0
  13. package/dist/errors/unknown-permission-name.d.ts.map +1 -0
  14. package/dist/errors/unknown-permission-name.js +29 -0
  15. package/dist/errors/version-mismatch.d.ts +55 -0
  16. package/dist/errors/version-mismatch.d.ts.map +1 -0
  17. package/dist/errors/version-mismatch.js +52 -0
  18. package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
  19. package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
  20. package/dist/gadgets/resolve-contract-gadgets.js +119 -0
  21. package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
  22. package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
  23. package/dist/gadgets/stdlib-gadgets.js +161 -0
  24. package/dist/iframe-bridge.d.ts +63 -0
  25. package/dist/iframe-bridge.d.ts.map +1 -0
  26. package/dist/iframe-bridge.js +166 -0
  27. package/dist/index.d.ts +62 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +79 -0
  30. package/dist/integrations/mcp-apps.d.ts +1218 -0
  31. package/dist/integrations/mcp-apps.d.ts.map +1 -0
  32. package/dist/integrations/mcp-apps.js +427 -0
  33. package/dist/navigation/index.d.ts +3 -0
  34. package/dist/navigation/index.d.ts.map +1 -0
  35. package/dist/navigation/index.js +1 -0
  36. package/dist/navigation/stack-navigation.d.ts +55 -0
  37. package/dist/navigation/stack-navigation.d.ts.map +1 -0
  38. package/dist/navigation/stack-navigation.js +80 -0
  39. package/dist/recommended-prompts.d.ts +56 -0
  40. package/dist/recommended-prompts.d.ts.map +1 -0
  41. package/dist/recommended-prompts.js +55 -0
  42. package/dist/registry/blueprint-key.d.ts +9 -0
  43. package/dist/registry/blueprint-key.d.ts.map +1 -0
  44. package/dist/registry/blueprint-key.js +28 -0
  45. package/dist/registry/canonicalize-contract.d.ts +35 -0
  46. package/dist/registry/canonicalize-contract.d.ts.map +1 -0
  47. package/dist/registry/canonicalize-contract.js +166 -0
  48. package/dist/registry/summarize-contract.d.ts +46 -0
  49. package/dist/registry/summarize-contract.d.ts.map +1 -0
  50. package/dist/registry/summarize-contract.js +63 -0
  51. package/dist/schema-learning/derive-contract.d.ts +67 -0
  52. package/dist/schema-learning/derive-contract.d.ts.map +1 -0
  53. package/dist/schema-learning/derive-contract.js +117 -0
  54. package/dist/schema-learning/merge.d.ts +32 -0
  55. package/dist/schema-learning/merge.d.ts.map +1 -0
  56. package/dist/schema-learning/merge.js +146 -0
  57. package/dist/schemas/blueprint.d.ts +32 -0
  58. package/dist/schemas/blueprint.d.ts.map +1 -0
  59. package/dist/schemas/blueprint.js +92 -0
  60. package/dist/schemas/data-contract.d.ts +750 -0
  61. package/dist/schemas/data-contract.d.ts.map +1 -0
  62. package/dist/schemas/data-contract.js +663 -0
  63. package/dist/schemas/gadget-name-grammar.d.ts +29 -0
  64. package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
  65. package/dist/schemas/gadget-name-grammar.js +28 -0
  66. package/dist/schemas/handshake-suggestion.d.ts +46 -0
  67. package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
  68. package/dist/schemas/handshake-suggestion.js +107 -0
  69. package/dist/schemas/invoke.d.ts +337 -0
  70. package/dist/schemas/invoke.d.ts.map +1 -0
  71. package/dist/schemas/invoke.js +169 -0
  72. package/dist/schemas/mcp.d.ts +301 -0
  73. package/dist/schemas/mcp.d.ts.map +1 -0
  74. package/dist/schemas/mcp.js +373 -0
  75. package/dist/schemas/ops-blueprint.d.ts +176 -0
  76. package/dist/schemas/ops-blueprint.d.ts.map +1 -0
  77. package/dist/schemas/ops-blueprint.js +259 -0
  78. package/dist/schemas/sync-check.d.ts +11 -0
  79. package/dist/schemas/sync-check.d.ts.map +1 -0
  80. package/dist/schemas/sync-check.js +60 -0
  81. package/dist/screen-blueprints/define.d.ts +22 -0
  82. package/dist/screen-blueprints/define.d.ts.map +1 -0
  83. package/dist/screen-blueprints/define.js +3 -0
  84. package/dist/screen-blueprints/index.d.ts +4 -0
  85. package/dist/screen-blueprints/index.d.ts.map +1 -0
  86. package/dist/screen-blueprints/index.js +3 -0
  87. package/dist/screen-blueprints/match.d.ts +35 -0
  88. package/dist/screen-blueprints/match.d.ts.map +1 -0
  89. package/dist/screen-blueprints/match.js +51 -0
  90. package/dist/screen-blueprints/types.d.ts +164 -0
  91. package/dist/screen-blueprints/types.d.ts.map +1 -0
  92. package/dist/screen-blueprints/types.js +1 -0
  93. package/dist/stream/stream-parser.d.ts +62 -0
  94. package/dist/stream/stream-parser.d.ts.map +1 -0
  95. package/dist/stream/stream-parser.js +199 -0
  96. package/dist/transport/websocket.d.ts +178 -0
  97. package/dist/transport/websocket.d.ts.map +1 -0
  98. package/dist/transport/websocket.js +1 -0
  99. package/dist/types/app-config.d.ts +61 -0
  100. package/dist/types/app-config.d.ts.map +1 -0
  101. package/dist/types/app-config.js +1 -0
  102. package/dist/types/auth.d.ts +61 -0
  103. package/dist/types/auth.d.ts.map +1 -0
  104. package/dist/types/auth.js +1 -0
  105. package/dist/types/blueprint.d.ts +206 -0
  106. package/dist/types/blueprint.d.ts.map +1 -0
  107. package/dist/types/blueprint.js +1 -0
  108. package/dist/types/canvas-lifecycle.d.ts +105 -0
  109. package/dist/types/canvas-lifecycle.d.ts.map +1 -0
  110. package/dist/types/canvas-lifecycle.js +38 -0
  111. package/dist/types/capabilities.d.ts +40 -0
  112. package/dist/types/capabilities.d.ts.map +1 -0
  113. package/dist/types/capabilities.js +19 -0
  114. package/dist/types/contract-inference.d.ts +401 -0
  115. package/dist/types/contract-inference.d.ts.map +1 -0
  116. package/dist/types/contract-inference.js +44 -0
  117. package/dist/types/credential.d.ts +41 -0
  118. package/dist/types/credential.d.ts.map +1 -0
  119. package/dist/types/credential.js +32 -0
  120. package/dist/types/data-bindings.d.ts +322 -0
  121. package/dist/types/data-bindings.d.ts.map +1 -0
  122. package/dist/types/data-bindings.js +29 -0
  123. package/dist/types/data-contract.d.ts +1296 -0
  124. package/dist/types/data-contract.d.ts.map +1 -0
  125. package/dist/types/data-contract.js +111 -0
  126. package/dist/types/events.d.ts +182 -0
  127. package/dist/types/events.d.ts.map +1 -0
  128. package/dist/types/events.js +8 -0
  129. package/dist/types/feedback.d.ts +24 -0
  130. package/dist/types/feedback.d.ts.map +1 -0
  131. package/dist/types/feedback.js +7 -0
  132. package/dist/types/gadget.d.ts +121 -0
  133. package/dist/types/gadget.d.ts.map +1 -0
  134. package/dist/types/gadget.js +24 -0
  135. package/dist/types/handshake-suggestion.d.ts +264 -0
  136. package/dist/types/handshake-suggestion.d.ts.map +1 -0
  137. package/dist/types/handshake-suggestion.js +70 -0
  138. package/dist/types/host-context.d.ts +163 -0
  139. package/dist/types/host-context.d.ts.map +1 -0
  140. package/dist/types/host-context.js +142 -0
  141. package/dist/types/interface-context.d.ts +105 -0
  142. package/dist/types/interface-context.d.ts.map +1 -0
  143. package/dist/types/interface-context.js +115 -0
  144. package/dist/types/invoke.d.ts +28 -0
  145. package/dist/types/invoke.d.ts.map +1 -0
  146. package/dist/types/invoke.js +7 -0
  147. package/dist/types/live-channel.d.ts +613 -0
  148. package/dist/types/live-channel.d.ts.map +1 -0
  149. package/dist/types/live-channel.js +1 -0
  150. package/dist/types/llm.d.ts +61 -0
  151. package/dist/types/llm.d.ts.map +1 -0
  152. package/dist/types/llm.js +186 -0
  153. package/dist/types/mcp-proxy.d.ts +67 -0
  154. package/dist/types/mcp-proxy.d.ts.map +1 -0
  155. package/dist/types/mcp-proxy.js +46 -0
  156. package/dist/types/mcp.d.ts +637 -0
  157. package/dist/types/mcp.d.ts.map +1 -0
  158. package/dist/types/mcp.js +30 -0
  159. package/dist/types/openrouter-models.d.ts +22 -0
  160. package/dist/types/openrouter-models.d.ts.map +1 -0
  161. package/dist/types/openrouter-models.js +4843 -0
  162. package/dist/types/region.d.ts +26 -0
  163. package/dist/types/region.d.ts.map +1 -0
  164. package/dist/types/region.js +36 -0
  165. package/dist/types/session.d.ts +419 -0
  166. package/dist/types/session.d.ts.map +1 -0
  167. package/dist/types/session.js +1 -0
  168. package/dist/types/thread.d.ts +207 -0
  169. package/dist/types/thread.d.ts.map +1 -0
  170. package/dist/types/thread.js +57 -0
  171. package/dist/types/ui-generator.d.ts +100 -0
  172. package/dist/types/ui-generator.d.ts.map +1 -0
  173. package/dist/types/ui-generator.js +53 -0
  174. package/dist/validation/ajv-runtime.d.ts +140 -0
  175. package/dist/validation/ajv-runtime.d.ts.map +1 -0
  176. package/dist/validation/ajv-runtime.js +452 -0
  177. package/dist/validation/content-hash.d.ts +3 -0
  178. package/dist/validation/content-hash.d.ts.map +1 -0
  179. package/dist/validation/content-hash.js +21 -0
  180. package/dist/validation/contract-validator.d.ts +244 -0
  181. package/dist/validation/contract-validator.d.ts.map +1 -0
  182. package/dist/validation/contract-validator.js +711 -0
  183. package/dist/validation/cross-references.d.ts +105 -0
  184. package/dist/validation/cross-references.d.ts.map +1 -0
  185. package/dist/validation/cross-references.js +164 -0
  186. package/dist/validation/hygiene-rules.d.ts +250 -0
  187. package/dist/validation/hygiene-rules.d.ts.map +1 -0
  188. package/dist/validation/hygiene-rules.js +564 -0
  189. package/dist/validation/lint-contract.d.ts +130 -0
  190. package/dist/validation/lint-contract.d.ts.map +1 -0
  191. package/dist/validation/lint-contract.js +225 -0
  192. package/dist/validation/name-invariants.d.ts +117 -0
  193. package/dist/validation/name-invariants.d.ts.map +1 -0
  194. package/dist/validation/name-invariants.js +172 -0
  195. package/dist/validation/reserved-channels.d.ts +156 -0
  196. package/dist/validation/reserved-channels.d.ts.map +1 -0
  197. package/dist/validation/reserved-channels.js +356 -0
  198. package/dist/validation/resolve-stream-channel.d.ts +78 -0
  199. package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
  200. package/dist/validation/resolve-stream-channel.js +64 -0
  201. package/dist/validation/sanitize-error.d.ts +46 -0
  202. package/dist/validation/sanitize-error.d.ts.map +1 -0
  203. package/dist/validation/sanitize-error.js +88 -0
  204. package/dist/validation/schema-compat-invariants.d.ts +140 -0
  205. package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
  206. package/dist/validation/schema-compat-invariants.js +220 -0
  207. package/dist/validation/schema-meta-validation.d.ts +60 -0
  208. package/dist/validation/schema-meta-validation.d.ts.map +1 -0
  209. package/dist/validation/schema-meta-validation.js +131 -0
  210. package/dist/validation/schema-subset.d.ts +165 -0
  211. package/dist/validation/schema-subset.d.ts.map +1 -0
  212. package/dist/validation/schema-subset.js +295 -0
  213. package/dist/validation/ui-security.d.ts +54 -0
  214. package/dist/validation/ui-security.d.ts.map +1 -0
  215. package/dist/validation/ui-security.js +138 -0
  216. package/dist/validation/zod-to-json-schema.d.ts +63 -0
  217. package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
  218. package/dist/validation/zod-to-json-schema.js +126 -0
  219. package/dist/version.d.ts +1458 -0
  220. package/dist/version.d.ts.map +1 -0
  221. package/dist/version.js +1459 -0
  222. package/package.json +113 -0
@@ -0,0 +1,156 @@
1
+ import type { ValidationResult } from './contract-validator';
2
+ /** Prefix that marks a channel as server-owned (reserved from agents). */
3
+ export declare const RESERVED_CHANNEL_PREFIX = "_ggui:";
4
+ /**
5
+ * Reserved channel for provisional A2UI assembly streams emitted by
6
+ * the server during fresh-gen `ggui_push` flows. The agent never
7
+ * authors messages on this channel; the renderer subscribes implicitly
8
+ * and dispatches the A2UI payload through its preview surface.
9
+ */
10
+ export declare const PREVIEW_CHANNEL = "_ggui:preview";
11
+ /**
12
+ * Reserved channel for canonical contract-error envelopes emitted by
13
+ * the wiredActionRouter. Body shape is `ContractErrorPayload` (see
14
+ * `types/data-contract.ts`). Agent-authored `streamSpec`
15
+ * MUST NOT declare this channel — structural validation rejects it
16
+ * alongside every other reserved-prefix name.
17
+ *
18
+ * If future work adds richer contract observability (e.g., separate
19
+ * `_ggui:wired-tool-invoked` success trace), each new name joins this
20
+ * module with its own constant and the {@link KNOWN_RESERVED_CHANNELS}
21
+ * set below.
22
+ */
23
+ export declare const CONTRACT_ERROR_CHANNEL = "_ggui:contract-error";
24
+ /**
25
+ * Reserved channel for canvas-mode session
26
+ * lifecycle envelopes — handshake / push / consume lifecycle signals
27
+ * that drive the ggui-animator's state machine.
28
+ *
29
+ * Body shape: `CanvasLifecyclePayload` (discriminated on `kind`). The
30
+ * server emits; canvas iframes (subscribed session-wide) consume.
31
+ * Inline iframes (pinned to a single stack item) do not receive
32
+ * envelopes on this channel — delivery is gated by subscription scope.
33
+ *
34
+ * Agent-authored `streamSpec` MUST NOT declare this channel; the
35
+ * structural validator rejects it alongside every other reserved-
36
+ * prefix name.
37
+ */
38
+ export declare const LIFECYCLE_CHANNEL = "_ggui:lifecycle";
39
+ /**
40
+ * Closed set of RECOGNIZED reserved channel names. Emissions and
41
+ * delivery validation consult this set — NOT the broader prefix
42
+ * predicate — so a typo inside the reserved namespace
43
+ * (`_ggui:preveiw`) cannot silently pass validation the way the
44
+ * unbounded prefix check did. A typo now falls through to the normal
45
+ * "unknown channel" rejection, surfacing the bug at its source instead
46
+ * of turning it into a silent no-op delivery.
47
+ *
48
+ * Adding a new reserved channel requires two edits: add the constant
49
+ * above, and add it to this set. The audit rule for future reviewers
50
+ * is "if a constant in this file is not in {@link KNOWN_RESERVED_CHANNELS},
51
+ * it is not a recognized delivery target".
52
+ */
53
+ export declare const KNOWN_RESERVED_CHANNELS: ReadonlySet<string>;
54
+ /**
55
+ * Returns `true` when `name` falls inside the server-owned reserved
56
+ * NAMESPACE (prefix `_ggui:`). Used exclusively by
57
+ * {@link validateContractStructure} to reject agent-authored
58
+ * `streamSpec` entries that try to declare ANY channel in the reserved
59
+ * namespace — regardless of whether the server currently recognizes
60
+ * the specific name. Broader than {@link isKnownReservedChannel} by
61
+ * design.
62
+ */
63
+ export declare function isReservedChannelName(name: string): boolean;
64
+ /**
65
+ * Returns `true` when `name` is a RECOGNIZED reserved channel the
66
+ * server-side runtime emits on today (see
67
+ * {@link KNOWN_RESERVED_CHANNELS}). Narrower than
68
+ * {@link isReservedChannelName} by design — a typo inside the reserved
69
+ * prefix (`_ggui:preveiw`) returns `false` here, which is the whole
70
+ * point: delivery validators and reserved-channel storage policies
71
+ * consult THIS predicate so typos surface as normal "unknown channel"
72
+ * rejections instead of silent no-op passes.
73
+ */
74
+ export declare function isKnownReservedChannel(name: string): boolean;
75
+ /**
76
+ * Validator signature for reserved-channel payload shape checks.
77
+ *
78
+ * Reserved-channel payload validation is split into TWO pools, joined
79
+ * at delivery time by {@link validateStreamData}:
80
+ *
81
+ * 1. `BUILTIN_RESERVED_VALIDATORS` — the PROTOCOL-OWNED payloads.
82
+ * Shipped in `@ggui-ai/protocol` because the protocol defines the
83
+ * shape (`ContractErrorPayload` is authored here; the validator
84
+ * belongs here too). Always active, no composition needed.
85
+ * 2. `extraReservedValidators` — INJECTION POINT for payloads whose
86
+ * shape the protocol does NOT own. Primary consumer today:
87
+ * `_ggui:preview` carries an A2UI-shaped `ServerMessage` from
88
+ * `@ggui-ai/preview-a2ui`. The A2UI boundary package exports the
89
+ * schema; hosting implementations compose a
90
+ * {@link ReservedChannelValidator} adapter and pass it in.
91
+ *
92
+ * This split is the Protocol #6 (vendor-neutral separation)
93
+ * preservation: `@ggui-ai/protocol` ships zero imports of
94
+ * `@ggui-ai/preview-a2ui`, so third-party implementations that don't
95
+ * adopt A2UI can still build on the protocol without pulling in a
96
+ * preview-specific dep graph. Servers that DO use A2UI (the ggui
97
+ * first-party `@ggui-ai/mcp-server`) inject the validator at
98
+ * composition time.
99
+ */
100
+ export type ReservedChannelValidator = (payload: unknown) => ValidationResult;
101
+ /**
102
+ * Structural validator for {@link ContractErrorPayload} — the body the
103
+ * server emits on `_ggui:contract-error`. PROTOCOL-OWNED shape; ships
104
+ * as a built-in (see {@link BUILTIN_RESERVED_VALIDATORS}).
105
+ *
106
+ * Semantics:
107
+ * - Payload MUST be a non-null object (arrays rejected).
108
+ * - Required fields: `toolName: string`, `error.code: string`,
109
+ * `error.message: string`, `timestamp: string`.
110
+ * - Optional fields: `actionName: string`, `sourceAction: {type:
111
+ * string, dispatchedAt: string}`, `error.causedBy: string`,
112
+ * `schemaVersion: string`.
113
+ * - `error.code` accepts ANY string — the {@link ContractErrorCode}
114
+ * type is extensibly-closed per Item 2 (`(string & {})` branch),
115
+ * so forward-compat codes like `BOOTSTRAP_FAILED` /
116
+ * `RATE_LIMIT_EXCEEDED` MUST NOT be rejected at this layer.
117
+ * - `sourceAction.type` accepts ANY string — per F6 extensibility
118
+ * (`'wired-action' | 'refresh-stream' | (string & {})`).
119
+ *
120
+ * Returns `{valid: true, violations: []}` on conformance. Reject sets
121
+ * `valid: false` with one violation per missing-or-mistyped field.
122
+ */
123
+ export declare function validateContractErrorPayload(payload: unknown): ValidationResult;
124
+ /**
125
+ * The PROTOCOL-OWNED reserved-channel validator registry.
126
+ *
127
+ * Only channels whose payload shape is authored inside
128
+ * `@ggui-ai/protocol` appear here. `_ggui:preview` is intentionally
129
+ * ABSENT — its payload is A2UI-shaped (authored in
130
+ * `@ggui-ai/preview-a2ui`), and shipping a validator for it in this
131
+ * package would couple the protocol to a preview-specific dep graph,
132
+ * breaking Protocol #6 (vendor-neutral separation).
133
+ *
134
+ * Hosting implementations that compose preview support inject their
135
+ * own `_ggui:preview` validator via
136
+ * `validateStreamData(..., extraReservedValidators)` — lookup order:
137
+ * extras first, then this built-in map, then fall-through to valid.
138
+ *
139
+ * Readonly `ReadonlyMap` so consumers can't mutate the global registry.
140
+ */
141
+ export declare const BUILTIN_RESERVED_VALIDATORS: ReadonlyMap<string, ReservedChannelValidator>;
142
+ /**
143
+ * Structural validator for {@link LIFECYCLE_CHANNEL} payloads. The
144
+ * wire shape is the closed discriminated union
145
+ * {@link CanvasLifecyclePayload}; we narrow on `kind` and check the
146
+ * required fields per variant. Defines the failure mode the protocol
147
+ * bar requires for reserved channels.
148
+ *
149
+ * Rejects:
150
+ * - non-object / null / array payloads
151
+ * - missing or non-string `kind`
152
+ * - unknown `kind` values (closed union — new kinds bump protocol)
153
+ * - missing or wrong-typed variant-specific fields
154
+ */
155
+ export declare function validateCanvasLifecyclePayload(payload: unknown): ValidationResult;
156
+ //# sourceMappingURL=reserved-channels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-channels.d.ts","sourceRoot":"","sources":["../../src/validation/reserved-channels.ts"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAAqB,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEhF,0EAA0E;AAC1E,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAC;AAE/C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAE7D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAItD,CAAC;AAEH;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,OAAO,EAAE,OAAO,KACb,gBAAgB,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,OAAO,GACf,gBAAgB,CAuIlB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,EAAE,WAAW,CACnD,MAAM,EACN,wBAAwB,CAKxB,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,OAAO,GACf,gBAAgB,CA4FlB"}
@@ -0,0 +1,356 @@
1
+ /** Prefix that marks a channel as server-owned (reserved from agents). */
2
+ export const RESERVED_CHANNEL_PREFIX = '_ggui:';
3
+ /**
4
+ * Reserved channel for provisional A2UI assembly streams emitted by
5
+ * the server during fresh-gen `ggui_push` flows. The agent never
6
+ * authors messages on this channel; the renderer subscribes implicitly
7
+ * and dispatches the A2UI payload through its preview surface.
8
+ */
9
+ export const PREVIEW_CHANNEL = '_ggui:preview';
10
+ /**
11
+ * Reserved channel for canonical contract-error envelopes emitted by
12
+ * the wiredActionRouter. Body shape is `ContractErrorPayload` (see
13
+ * `types/data-contract.ts`). Agent-authored `streamSpec`
14
+ * MUST NOT declare this channel — structural validation rejects it
15
+ * alongside every other reserved-prefix name.
16
+ *
17
+ * If future work adds richer contract observability (e.g., separate
18
+ * `_ggui:wired-tool-invoked` success trace), each new name joins this
19
+ * module with its own constant and the {@link KNOWN_RESERVED_CHANNELS}
20
+ * set below.
21
+ */
22
+ export const CONTRACT_ERROR_CHANNEL = '_ggui:contract-error';
23
+ /**
24
+ * Reserved channel for canvas-mode session
25
+ * lifecycle envelopes — handshake / push / consume lifecycle signals
26
+ * that drive the ggui-animator's state machine.
27
+ *
28
+ * Body shape: `CanvasLifecyclePayload` (discriminated on `kind`). The
29
+ * server emits; canvas iframes (subscribed session-wide) consume.
30
+ * Inline iframes (pinned to a single stack item) do not receive
31
+ * envelopes on this channel — delivery is gated by subscription scope.
32
+ *
33
+ * Agent-authored `streamSpec` MUST NOT declare this channel; the
34
+ * structural validator rejects it alongside every other reserved-
35
+ * prefix name.
36
+ */
37
+ export const LIFECYCLE_CHANNEL = '_ggui:lifecycle';
38
+ /**
39
+ * Closed set of RECOGNIZED reserved channel names. Emissions and
40
+ * delivery validation consult this set — NOT the broader prefix
41
+ * predicate — so a typo inside the reserved namespace
42
+ * (`_ggui:preveiw`) cannot silently pass validation the way the
43
+ * unbounded prefix check did. A typo now falls through to the normal
44
+ * "unknown channel" rejection, surfacing the bug at its source instead
45
+ * of turning it into a silent no-op delivery.
46
+ *
47
+ * Adding a new reserved channel requires two edits: add the constant
48
+ * above, and add it to this set. The audit rule for future reviewers
49
+ * is "if a constant in this file is not in {@link KNOWN_RESERVED_CHANNELS},
50
+ * it is not a recognized delivery target".
51
+ */
52
+ export const KNOWN_RESERVED_CHANNELS = new Set([
53
+ PREVIEW_CHANNEL,
54
+ CONTRACT_ERROR_CHANNEL,
55
+ LIFECYCLE_CHANNEL,
56
+ ]);
57
+ /**
58
+ * Returns `true` when `name` falls inside the server-owned reserved
59
+ * NAMESPACE (prefix `_ggui:`). Used exclusively by
60
+ * {@link validateContractStructure} to reject agent-authored
61
+ * `streamSpec` entries that try to declare ANY channel in the reserved
62
+ * namespace — regardless of whether the server currently recognizes
63
+ * the specific name. Broader than {@link isKnownReservedChannel} by
64
+ * design.
65
+ */
66
+ export function isReservedChannelName(name) {
67
+ return name.startsWith(RESERVED_CHANNEL_PREFIX);
68
+ }
69
+ /**
70
+ * Returns `true` when `name` is a RECOGNIZED reserved channel the
71
+ * server-side runtime emits on today (see
72
+ * {@link KNOWN_RESERVED_CHANNELS}). Narrower than
73
+ * {@link isReservedChannelName} by design — a typo inside the reserved
74
+ * prefix (`_ggui:preveiw`) returns `false` here, which is the whole
75
+ * point: delivery validators and reserved-channel storage policies
76
+ * consult THIS predicate so typos surface as normal "unknown channel"
77
+ * rejections instead of silent no-op passes.
78
+ */
79
+ export function isKnownReservedChannel(name) {
80
+ return KNOWN_RESERVED_CHANNELS.has(name);
81
+ }
82
+ /**
83
+ * Structural validator for {@link ContractErrorPayload} — the body the
84
+ * server emits on `_ggui:contract-error`. PROTOCOL-OWNED shape; ships
85
+ * as a built-in (see {@link BUILTIN_RESERVED_VALIDATORS}).
86
+ *
87
+ * Semantics:
88
+ * - Payload MUST be a non-null object (arrays rejected).
89
+ * - Required fields: `toolName: string`, `error.code: string`,
90
+ * `error.message: string`, `timestamp: string`.
91
+ * - Optional fields: `actionName: string`, `sourceAction: {type:
92
+ * string, dispatchedAt: string}`, `error.causedBy: string`,
93
+ * `schemaVersion: string`.
94
+ * - `error.code` accepts ANY string — the {@link ContractErrorCode}
95
+ * type is extensibly-closed per Item 2 (`(string & {})` branch),
96
+ * so forward-compat codes like `BOOTSTRAP_FAILED` /
97
+ * `RATE_LIMIT_EXCEEDED` MUST NOT be rejected at this layer.
98
+ * - `sourceAction.type` accepts ANY string — per F6 extensibility
99
+ * (`'wired-action' | 'refresh-stream' | (string & {})`).
100
+ *
101
+ * Returns `{valid: true, violations: []}` on conformance. Reject sets
102
+ * `valid: false` with one violation per missing-or-mistyped field.
103
+ */
104
+ export function validateContractErrorPayload(payload) {
105
+ const violations = [];
106
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
107
+ return {
108
+ valid: false,
109
+ violations: [
110
+ {
111
+ field: 'payload',
112
+ message: `${CONTRACT_ERROR_CHANNEL} payload must be a non-null object`,
113
+ expected: 'object',
114
+ received: payload === null ? 'null' : Array.isArray(payload) ? 'array' : typeof payload,
115
+ },
116
+ ],
117
+ };
118
+ }
119
+ const p = payload;
120
+ // ── Required: toolName ──
121
+ if (typeof p.toolName !== 'string') {
122
+ violations.push({
123
+ field: 'toolName',
124
+ message: "Required field 'toolName' must be a string",
125
+ expected: 'string',
126
+ received: p.toolName === undefined ? 'undefined' : typeof p.toolName,
127
+ });
128
+ }
129
+ // ── Optional: actionName ──
130
+ if (p.actionName !== undefined && typeof p.actionName !== 'string') {
131
+ violations.push({
132
+ field: 'actionName',
133
+ message: "Optional field 'actionName' must be a string when present",
134
+ expected: 'string',
135
+ received: typeof p.actionName,
136
+ });
137
+ }
138
+ // ── Optional: sourceAction ──
139
+ if (p.sourceAction !== undefined) {
140
+ if (typeof p.sourceAction !== 'object' ||
141
+ p.sourceAction === null ||
142
+ Array.isArray(p.sourceAction)) {
143
+ violations.push({
144
+ field: 'sourceAction',
145
+ message: "Optional field 'sourceAction' must be an object when present",
146
+ expected: 'object',
147
+ received: p.sourceAction === null ? 'null' : Array.isArray(p.sourceAction) ? 'array' : typeof p.sourceAction,
148
+ });
149
+ }
150
+ else {
151
+ const sa = p.sourceAction;
152
+ if (typeof sa.type !== 'string') {
153
+ // Accepts any string — extensibility per F6. Type presence is
154
+ // required, VALUE is open.
155
+ violations.push({
156
+ field: 'sourceAction.type',
157
+ message: "Field 'sourceAction.type' must be a string",
158
+ expected: 'string',
159
+ received: sa.type === undefined ? 'undefined' : typeof sa.type,
160
+ });
161
+ }
162
+ if (typeof sa.dispatchedAt !== 'string') {
163
+ violations.push({
164
+ field: 'sourceAction.dispatchedAt',
165
+ message: "Field 'sourceAction.dispatchedAt' must be a string (ISO 8601)",
166
+ expected: 'string',
167
+ received: sa.dispatchedAt === undefined ? 'undefined' : typeof sa.dispatchedAt,
168
+ });
169
+ }
170
+ }
171
+ }
172
+ // ── Required: error.code + error.message ──
173
+ if (typeof p.error !== 'object' || p.error === null || Array.isArray(p.error)) {
174
+ violations.push({
175
+ field: 'error',
176
+ message: "Required field 'error' must be a non-null object",
177
+ expected: 'object',
178
+ received: p.error === undefined ? 'undefined' : p.error === null ? 'null' : Array.isArray(p.error) ? 'array' : typeof p.error,
179
+ });
180
+ }
181
+ else {
182
+ const err = p.error;
183
+ if (typeof err.code !== 'string') {
184
+ // Accepts any string — ContractErrorCode is extensibly-closed per
185
+ // Item 2. Rejecting by name-set here would force a version bump
186
+ // every time a new code ships.
187
+ violations.push({
188
+ field: 'error.code',
189
+ message: "Required field 'error.code' must be a string",
190
+ expected: 'string',
191
+ received: err.code === undefined ? 'undefined' : typeof err.code,
192
+ });
193
+ }
194
+ if (typeof err.message !== 'string') {
195
+ violations.push({
196
+ field: 'error.message',
197
+ message: "Required field 'error.message' must be a string",
198
+ expected: 'string',
199
+ received: err.message === undefined ? 'undefined' : typeof err.message,
200
+ });
201
+ }
202
+ if (err.causedBy !== undefined && typeof err.causedBy !== 'string') {
203
+ violations.push({
204
+ field: 'error.causedBy',
205
+ message: "Optional field 'error.causedBy' must be a string when present",
206
+ expected: 'string',
207
+ received: typeof err.causedBy,
208
+ });
209
+ }
210
+ }
211
+ // ── Required: timestamp ──
212
+ if (typeof p.timestamp !== 'string') {
213
+ violations.push({
214
+ field: 'timestamp',
215
+ message: "Required field 'timestamp' must be a string (ISO 8601)",
216
+ expected: 'string',
217
+ received: p.timestamp === undefined ? 'undefined' : typeof p.timestamp,
218
+ });
219
+ }
220
+ // ── Optional: schemaVersion ──
221
+ if (p.schemaVersion !== undefined && typeof p.schemaVersion !== 'string') {
222
+ violations.push({
223
+ field: 'schemaVersion',
224
+ message: "Optional field 'schemaVersion' must be a string when present",
225
+ expected: 'string',
226
+ received: typeof p.schemaVersion,
227
+ });
228
+ }
229
+ return { valid: violations.length === 0, violations };
230
+ }
231
+ /**
232
+ * The PROTOCOL-OWNED reserved-channel validator registry.
233
+ *
234
+ * Only channels whose payload shape is authored inside
235
+ * `@ggui-ai/protocol` appear here. `_ggui:preview` is intentionally
236
+ * ABSENT — its payload is A2UI-shaped (authored in
237
+ * `@ggui-ai/preview-a2ui`), and shipping a validator for it in this
238
+ * package would couple the protocol to a preview-specific dep graph,
239
+ * breaking Protocol #6 (vendor-neutral separation).
240
+ *
241
+ * Hosting implementations that compose preview support inject their
242
+ * own `_ggui:preview` validator via
243
+ * `validateStreamData(..., extraReservedValidators)` — lookup order:
244
+ * extras first, then this built-in map, then fall-through to valid.
245
+ *
246
+ * Readonly `ReadonlyMap` so consumers can't mutate the global registry.
247
+ */
248
+ export const BUILTIN_RESERVED_VALIDATORS = new Map([
249
+ [CONTRACT_ERROR_CHANNEL, validateContractErrorPayload],
250
+ [LIFECYCLE_CHANNEL, validateCanvasLifecyclePayload],
251
+ // PREVIEW_CHANNEL intentionally absent — injected at composition time.
252
+ ]);
253
+ /**
254
+ * Structural validator for {@link LIFECYCLE_CHANNEL} payloads. The
255
+ * wire shape is the closed discriminated union
256
+ * {@link CanvasLifecyclePayload}; we narrow on `kind` and check the
257
+ * required fields per variant. Defines the failure mode the protocol
258
+ * bar requires for reserved channels.
259
+ *
260
+ * Rejects:
261
+ * - non-object / null / array payloads
262
+ * - missing or non-string `kind`
263
+ * - unknown `kind` values (closed union — new kinds bump protocol)
264
+ * - missing or wrong-typed variant-specific fields
265
+ */
266
+ export function validateCanvasLifecyclePayload(payload) {
267
+ const violations = [];
268
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
269
+ return {
270
+ valid: false,
271
+ violations: [
272
+ {
273
+ field: 'payload',
274
+ message: `${LIFECYCLE_CHANNEL} payload must be a non-null object`,
275
+ expected: 'object',
276
+ received: payload === null ? 'null' : Array.isArray(payload) ? 'array' : typeof payload,
277
+ },
278
+ ],
279
+ };
280
+ }
281
+ const p = payload;
282
+ if (typeof p.kind !== 'string') {
283
+ return {
284
+ valid: false,
285
+ violations: [
286
+ {
287
+ field: 'kind',
288
+ message: "Required field 'kind' must be a string",
289
+ expected: 'string',
290
+ received: p.kind === undefined ? 'undefined' : typeof p.kind,
291
+ },
292
+ ],
293
+ };
294
+ }
295
+ const requireString = (field) => {
296
+ if (typeof p[field] !== 'string') {
297
+ violations.push({
298
+ field,
299
+ message: `Required field '${field}' must be a string`,
300
+ expected: 'string',
301
+ received: p[field] === undefined ? 'undefined' : typeof p[field],
302
+ });
303
+ }
304
+ };
305
+ switch (p.kind) {
306
+ case 'handshake_started':
307
+ requireString('handshakeId');
308
+ requireString('intent');
309
+ break;
310
+ case 'handshake_completed':
311
+ requireString('handshakeId');
312
+ if (p.outcome !== 'accepted' &&
313
+ p.outcome !== 'amended' &&
314
+ p.outcome !== 'declined' &&
315
+ p.outcome !== 'cached') {
316
+ violations.push({
317
+ field: 'outcome',
318
+ message: "Required field 'outcome' must be 'accepted' | 'amended' | 'declined' | 'cached'",
319
+ expected: "'accepted' | 'amended' | 'declined' | 'cached'",
320
+ received: p.outcome === undefined ? 'undefined' : String(p.outcome),
321
+ });
322
+ }
323
+ if (typeof p.genExpected !== 'boolean') {
324
+ violations.push({
325
+ field: 'genExpected',
326
+ message: "Required field 'genExpected' must be a boolean",
327
+ expected: 'boolean',
328
+ received: p.genExpected === undefined ? 'undefined' : typeof p.genExpected,
329
+ });
330
+ }
331
+ break;
332
+ case 'push_started':
333
+ requireString('stackItemId');
334
+ requireString('intent');
335
+ break;
336
+ case 'consume_polling':
337
+ requireString('stackItemId');
338
+ if (p.state !== 'open') {
339
+ violations.push({
340
+ field: 'state',
341
+ message: "Required field 'state' must be 'open'",
342
+ expected: "'open'",
343
+ received: p.state === undefined ? 'undefined' : String(p.state),
344
+ });
345
+ }
346
+ break;
347
+ default:
348
+ violations.push({
349
+ field: 'kind',
350
+ message: `Unknown lifecycle kind '${p.kind}'. Closed union; new kinds bump protocol version.`,
351
+ expected: "'handshake_started' | 'handshake_completed' | 'push_started' | 'consume_polling'",
352
+ received: p.kind,
353
+ });
354
+ }
355
+ return { valid: violations.length === 0, violations };
356
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `resolveStreamChannel` — the single source of truth for applying
3
+ * per-channel defaults to a {@link StreamSpec} entry.
4
+ *
5
+ * Four runtime roles reason about stream channels today (hosted
6
+ * Lambda fan-out, OSS `/ws` fan-out, `@ggui-ai/react` data receipt,
7
+ * `@ggui-ai/react-native` data receipt). Each of them needs the same
8
+ * question answered: "what are the effective semantics of channel X
9
+ * on this spec?" Without a shared helper, each role applies the
10
+ * `DEFAULT_STREAM_*` constants inline and those defaults drift.
11
+ *
12
+ * This helper is a THIN lookup + default-application pass. It is
13
+ * explicitly NOT:
14
+ *
15
+ * - a payload validator (that remains `validateStreamData` — the
16
+ * shape check is orthogonal to the semantics lookup).
17
+ * - a permission/authorization gate (channels are declared, not
18
+ * granted).
19
+ * - a replay-buffer read (no server-side buffer infrastructure
20
+ * exists yet; `replay` comes back as a DECLARATION, not a
21
+ * guarantee — see streamSpec design-lock for the honest stop).
22
+ *
23
+ * Returns `undefined` for two distinct cases, both of which are
24
+ * "nothing to enforce" at call sites:
25
+ *
26
+ * - `spec === undefined` — the stack item has no stream contract.
27
+ * - `spec.channels[channelName]` is missing — the channel isn't
28
+ * declared. Callers MUST NOT assume this means "permissive";
29
+ * rejection is the downstream responsibility of
30
+ * `validateStreamData` (which reports the undeclared-channel
31
+ * violation), not this helper.
32
+ */
33
+ import { type JsonSchema, type JsonValue, type StreamChannelMode, type StreamReplayPolicy, type StreamSpec } from '../types/data-contract.js';
34
+ /**
35
+ * A channel's fully-resolved runtime semantics. Every optional field
36
+ * on the raw {@link import('../types/data-contract.js').StreamChannelEntry}
37
+ * has been defaulted per the locked `DEFAULT_STREAM_*` constants, so
38
+ * consumers that honor channel semantics never need to re-check for
39
+ * `undefined` on `mode` / `replay` / `complete`.
40
+ */
41
+ export interface ResolvedStreamChannel {
42
+ /** Channel name (the lookup key used against `spec.channels`). */
43
+ readonly name: string;
44
+ /** Payload schema — the authoritative contract for deliveries. */
45
+ readonly schema: JsonSchema;
46
+ /** State-folding mode (defaulted). */
47
+ readonly mode: StreamChannelMode;
48
+ /** Replay policy (defaulted; advisory until replay infra ships). */
49
+ readonly replay: StreamReplayPolicy;
50
+ /** Whether this channel has a terminal completion marker (defaulted). */
51
+ readonly complete: boolean;
52
+ /** Optional passthrough — channel's human-readable description. */
53
+ readonly description?: string;
54
+ /** Optional passthrough — channel's example payload. */
55
+ readonly example?: JsonValue;
56
+ /** Optional passthrough — refresh tool declared for this channel.
57
+ * Server-side action dispatch (WS-direct agent-less deployments)
58
+ * fires this after a wired action succeeds; absence means "no
59
+ * refresh fires." Distinct from the `source` poll/push feed on
60
+ * `StreamChannelEntry`. See `StreamChannelEntry.tool`. */
61
+ readonly tool?: string;
62
+ }
63
+ /**
64
+ * Look up a channel's declared semantics in a {@link StreamSpec} and
65
+ * return the fully-resolved view. Optional fields on the raw entry
66
+ * (`mode` / `replay` / `complete`) are filled with their locked
67
+ * defaults.
68
+ *
69
+ * @param spec The active stack item's stream contract, or undefined
70
+ * when the item has no `streamSpec` at all.
71
+ * @param channelName The channel name to resolve — typically read
72
+ * from the outbound envelope's `channel` field.
73
+ * @returns `ResolvedStreamChannel` when the channel is declared;
74
+ * `undefined` otherwise (either the spec is absent or the
75
+ * channel isn't in `spec.channels`).
76
+ */
77
+ export declare function resolveStreamChannel(spec: StreamSpec | undefined, channelName: string): ResolvedStreamChannel | undefined;
78
+ //# sourceMappingURL=resolve-stream-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-stream-channel.d.ts","sourceRoot":"","sources":["../../src/validation/resolve-stream-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAIL,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,UAAU,EAChB,MAAM,2BAA2B,CAAC;AAEnC;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B;;;;8DAI0D;IAC1D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,WAAW,EAAE,MAAM,GAClB,qBAAqB,GAAG,SAAS,CAcnC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * `resolveStreamChannel` — the single source of truth for applying
3
+ * per-channel defaults to a {@link StreamSpec} entry.
4
+ *
5
+ * Four runtime roles reason about stream channels today (hosted
6
+ * Lambda fan-out, OSS `/ws` fan-out, `@ggui-ai/react` data receipt,
7
+ * `@ggui-ai/react-native` data receipt). Each of them needs the same
8
+ * question answered: "what are the effective semantics of channel X
9
+ * on this spec?" Without a shared helper, each role applies the
10
+ * `DEFAULT_STREAM_*` constants inline and those defaults drift.
11
+ *
12
+ * This helper is a THIN lookup + default-application pass. It is
13
+ * explicitly NOT:
14
+ *
15
+ * - a payload validator (that remains `validateStreamData` — the
16
+ * shape check is orthogonal to the semantics lookup).
17
+ * - a permission/authorization gate (channels are declared, not
18
+ * granted).
19
+ * - a replay-buffer read (no server-side buffer infrastructure
20
+ * exists yet; `replay` comes back as a DECLARATION, not a
21
+ * guarantee — see streamSpec design-lock for the honest stop).
22
+ *
23
+ * Returns `undefined` for two distinct cases, both of which are
24
+ * "nothing to enforce" at call sites:
25
+ *
26
+ * - `spec === undefined` — the stack item has no stream contract.
27
+ * - `spec.channels[channelName]` is missing — the channel isn't
28
+ * declared. Callers MUST NOT assume this means "permissive";
29
+ * rejection is the downstream responsibility of
30
+ * `validateStreamData` (which reports the undeclared-channel
31
+ * violation), not this helper.
32
+ */
33
+ import { DEFAULT_STREAM_CHANNEL_COMPLETE, DEFAULT_STREAM_CHANNEL_MODE, DEFAULT_STREAM_REPLAY_POLICY, } from '../types/data-contract.js';
34
+ /**
35
+ * Look up a channel's declared semantics in a {@link StreamSpec} and
36
+ * return the fully-resolved view. Optional fields on the raw entry
37
+ * (`mode` / `replay` / `complete`) are filled with their locked
38
+ * defaults.
39
+ *
40
+ * @param spec The active stack item's stream contract, or undefined
41
+ * when the item has no `streamSpec` at all.
42
+ * @param channelName The channel name to resolve — typically read
43
+ * from the outbound envelope's `channel` field.
44
+ * @returns `ResolvedStreamChannel` when the channel is declared;
45
+ * `undefined` otherwise (either the spec is absent or the
46
+ * channel isn't in `spec.channels`).
47
+ */
48
+ export function resolveStreamChannel(spec, channelName) {
49
+ if (!spec)
50
+ return undefined;
51
+ const entry = spec[channelName];
52
+ if (!entry)
53
+ return undefined;
54
+ return {
55
+ name: channelName,
56
+ schema: entry.schema,
57
+ mode: entry.mode ?? DEFAULT_STREAM_CHANNEL_MODE,
58
+ replay: entry.replay ?? DEFAULT_STREAM_REPLAY_POLICY,
59
+ complete: entry.complete ?? DEFAULT_STREAM_CHANNEL_COMPLETE,
60
+ ...(entry.description !== undefined ? { description: entry.description } : {}),
61
+ ...(entry.example !== undefined ? { example: entry.example } : {}),
62
+ ...(entry.tool !== undefined ? { tool: entry.tool } : {}),
63
+ };
64
+ }