@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.
- package/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/bridge/invoke-agent.d.ts +65 -0
- package/dist/bridge/invoke-agent.d.ts.map +1 -0
- package/dist/bridge/invoke-agent.js +113 -0
- package/dist/envelope-adapters.d.ts +24 -0
- package/dist/envelope-adapters.d.ts.map +1 -0
- package/dist/envelope-adapters.js +14 -0
- package/dist/envelopes/builders.d.ts +145 -0
- package/dist/envelopes/builders.d.ts.map +1 -0
- package/dist/envelopes/builders.js +113 -0
- package/dist/errors/unknown-permission-name.d.ts +12 -0
- package/dist/errors/unknown-permission-name.d.ts.map +1 -0
- package/dist/errors/unknown-permission-name.js +29 -0
- package/dist/errors/version-mismatch.d.ts +55 -0
- package/dist/errors/version-mismatch.d.ts.map +1 -0
- package/dist/errors/version-mismatch.js +52 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
- package/dist/gadgets/resolve-contract-gadgets.js +119 -0
- package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
- package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
- package/dist/gadgets/stdlib-gadgets.js +161 -0
- package/dist/iframe-bridge.d.ts +63 -0
- package/dist/iframe-bridge.d.ts.map +1 -0
- package/dist/iframe-bridge.js +166 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/integrations/mcp-apps.d.ts +1218 -0
- package/dist/integrations/mcp-apps.d.ts.map +1 -0
- package/dist/integrations/mcp-apps.js +427 -0
- package/dist/navigation/index.d.ts +3 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/stack-navigation.d.ts +55 -0
- package/dist/navigation/stack-navigation.d.ts.map +1 -0
- package/dist/navigation/stack-navigation.js +80 -0
- package/dist/recommended-prompts.d.ts +56 -0
- package/dist/recommended-prompts.d.ts.map +1 -0
- package/dist/recommended-prompts.js +55 -0
- package/dist/registry/blueprint-key.d.ts +9 -0
- package/dist/registry/blueprint-key.d.ts.map +1 -0
- package/dist/registry/blueprint-key.js +28 -0
- package/dist/registry/canonicalize-contract.d.ts +35 -0
- package/dist/registry/canonicalize-contract.d.ts.map +1 -0
- package/dist/registry/canonicalize-contract.js +166 -0
- package/dist/registry/summarize-contract.d.ts +46 -0
- package/dist/registry/summarize-contract.d.ts.map +1 -0
- package/dist/registry/summarize-contract.js +63 -0
- package/dist/schema-learning/derive-contract.d.ts +67 -0
- package/dist/schema-learning/derive-contract.d.ts.map +1 -0
- package/dist/schema-learning/derive-contract.js +117 -0
- package/dist/schema-learning/merge.d.ts +32 -0
- package/dist/schema-learning/merge.d.ts.map +1 -0
- package/dist/schema-learning/merge.js +146 -0
- package/dist/schemas/blueprint.d.ts +32 -0
- package/dist/schemas/blueprint.d.ts.map +1 -0
- package/dist/schemas/blueprint.js +92 -0
- package/dist/schemas/data-contract.d.ts +750 -0
- package/dist/schemas/data-contract.d.ts.map +1 -0
- package/dist/schemas/data-contract.js +663 -0
- package/dist/schemas/gadget-name-grammar.d.ts +29 -0
- package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
- package/dist/schemas/gadget-name-grammar.js +28 -0
- package/dist/schemas/handshake-suggestion.d.ts +46 -0
- package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
- package/dist/schemas/handshake-suggestion.js +107 -0
- package/dist/schemas/invoke.d.ts +337 -0
- package/dist/schemas/invoke.d.ts.map +1 -0
- package/dist/schemas/invoke.js +169 -0
- package/dist/schemas/mcp.d.ts +301 -0
- package/dist/schemas/mcp.d.ts.map +1 -0
- package/dist/schemas/mcp.js +373 -0
- package/dist/schemas/ops-blueprint.d.ts +176 -0
- package/dist/schemas/ops-blueprint.d.ts.map +1 -0
- package/dist/schemas/ops-blueprint.js +259 -0
- package/dist/schemas/sync-check.d.ts +11 -0
- package/dist/schemas/sync-check.d.ts.map +1 -0
- package/dist/schemas/sync-check.js +60 -0
- package/dist/screen-blueprints/define.d.ts +22 -0
- package/dist/screen-blueprints/define.d.ts.map +1 -0
- package/dist/screen-blueprints/define.js +3 -0
- package/dist/screen-blueprints/index.d.ts +4 -0
- package/dist/screen-blueprints/index.d.ts.map +1 -0
- package/dist/screen-blueprints/index.js +3 -0
- package/dist/screen-blueprints/match.d.ts +35 -0
- package/dist/screen-blueprints/match.d.ts.map +1 -0
- package/dist/screen-blueprints/match.js +51 -0
- package/dist/screen-blueprints/types.d.ts +164 -0
- package/dist/screen-blueprints/types.d.ts.map +1 -0
- package/dist/screen-blueprints/types.js +1 -0
- package/dist/stream/stream-parser.d.ts +62 -0
- package/dist/stream/stream-parser.d.ts.map +1 -0
- package/dist/stream/stream-parser.js +199 -0
- package/dist/transport/websocket.d.ts +178 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +1 -0
- package/dist/types/app-config.d.ts +61 -0
- package/dist/types/app-config.d.ts.map +1 -0
- package/dist/types/app-config.js +1 -0
- package/dist/types/auth.d.ts +61 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/blueprint.d.ts +206 -0
- package/dist/types/blueprint.d.ts.map +1 -0
- package/dist/types/blueprint.js +1 -0
- package/dist/types/canvas-lifecycle.d.ts +105 -0
- package/dist/types/canvas-lifecycle.d.ts.map +1 -0
- package/dist/types/canvas-lifecycle.js +38 -0
- package/dist/types/capabilities.d.ts +40 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +19 -0
- package/dist/types/contract-inference.d.ts +401 -0
- package/dist/types/contract-inference.d.ts.map +1 -0
- package/dist/types/contract-inference.js +44 -0
- package/dist/types/credential.d.ts +41 -0
- package/dist/types/credential.d.ts.map +1 -0
- package/dist/types/credential.js +32 -0
- package/dist/types/data-bindings.d.ts +322 -0
- package/dist/types/data-bindings.d.ts.map +1 -0
- package/dist/types/data-bindings.js +29 -0
- package/dist/types/data-contract.d.ts +1296 -0
- package/dist/types/data-contract.d.ts.map +1 -0
- package/dist/types/data-contract.js +111 -0
- package/dist/types/events.d.ts +182 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/feedback.d.ts +24 -0
- package/dist/types/feedback.d.ts.map +1 -0
- package/dist/types/feedback.js +7 -0
- package/dist/types/gadget.d.ts +121 -0
- package/dist/types/gadget.d.ts.map +1 -0
- package/dist/types/gadget.js +24 -0
- package/dist/types/handshake-suggestion.d.ts +264 -0
- package/dist/types/handshake-suggestion.d.ts.map +1 -0
- package/dist/types/handshake-suggestion.js +70 -0
- package/dist/types/host-context.d.ts +163 -0
- package/dist/types/host-context.d.ts.map +1 -0
- package/dist/types/host-context.js +142 -0
- package/dist/types/interface-context.d.ts +105 -0
- package/dist/types/interface-context.d.ts.map +1 -0
- package/dist/types/interface-context.js +115 -0
- package/dist/types/invoke.d.ts +28 -0
- package/dist/types/invoke.d.ts.map +1 -0
- package/dist/types/invoke.js +7 -0
- package/dist/types/live-channel.d.ts +613 -0
- package/dist/types/live-channel.d.ts.map +1 -0
- package/dist/types/live-channel.js +1 -0
- package/dist/types/llm.d.ts +61 -0
- package/dist/types/llm.d.ts.map +1 -0
- package/dist/types/llm.js +186 -0
- package/dist/types/mcp-proxy.d.ts +67 -0
- package/dist/types/mcp-proxy.d.ts.map +1 -0
- package/dist/types/mcp-proxy.js +46 -0
- package/dist/types/mcp.d.ts +637 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +30 -0
- package/dist/types/openrouter-models.d.ts +22 -0
- package/dist/types/openrouter-models.d.ts.map +1 -0
- package/dist/types/openrouter-models.js +4843 -0
- package/dist/types/region.d.ts +26 -0
- package/dist/types/region.d.ts.map +1 -0
- package/dist/types/region.js +36 -0
- package/dist/types/session.d.ts +419 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +1 -0
- package/dist/types/thread.d.ts +207 -0
- package/dist/types/thread.d.ts.map +1 -0
- package/dist/types/thread.js +57 -0
- package/dist/types/ui-generator.d.ts +100 -0
- package/dist/types/ui-generator.d.ts.map +1 -0
- package/dist/types/ui-generator.js +53 -0
- package/dist/validation/ajv-runtime.d.ts +140 -0
- package/dist/validation/ajv-runtime.d.ts.map +1 -0
- package/dist/validation/ajv-runtime.js +452 -0
- package/dist/validation/content-hash.d.ts +3 -0
- package/dist/validation/content-hash.d.ts.map +1 -0
- package/dist/validation/content-hash.js +21 -0
- package/dist/validation/contract-validator.d.ts +244 -0
- package/dist/validation/contract-validator.d.ts.map +1 -0
- package/dist/validation/contract-validator.js +711 -0
- package/dist/validation/cross-references.d.ts +105 -0
- package/dist/validation/cross-references.d.ts.map +1 -0
- package/dist/validation/cross-references.js +164 -0
- package/dist/validation/hygiene-rules.d.ts +250 -0
- package/dist/validation/hygiene-rules.d.ts.map +1 -0
- package/dist/validation/hygiene-rules.js +564 -0
- package/dist/validation/lint-contract.d.ts +130 -0
- package/dist/validation/lint-contract.d.ts.map +1 -0
- package/dist/validation/lint-contract.js +225 -0
- package/dist/validation/name-invariants.d.ts +117 -0
- package/dist/validation/name-invariants.d.ts.map +1 -0
- package/dist/validation/name-invariants.js +172 -0
- package/dist/validation/reserved-channels.d.ts +156 -0
- package/dist/validation/reserved-channels.d.ts.map +1 -0
- package/dist/validation/reserved-channels.js +356 -0
- package/dist/validation/resolve-stream-channel.d.ts +78 -0
- package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
- package/dist/validation/resolve-stream-channel.js +64 -0
- package/dist/validation/sanitize-error.d.ts +46 -0
- package/dist/validation/sanitize-error.d.ts.map +1 -0
- package/dist/validation/sanitize-error.js +88 -0
- package/dist/validation/schema-compat-invariants.d.ts +140 -0
- package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
- package/dist/validation/schema-compat-invariants.js +220 -0
- package/dist/validation/schema-meta-validation.d.ts +60 -0
- package/dist/validation/schema-meta-validation.d.ts.map +1 -0
- package/dist/validation/schema-meta-validation.js +131 -0
- package/dist/validation/schema-subset.d.ts +165 -0
- package/dist/validation/schema-subset.d.ts.map +1 -0
- package/dist/validation/schema-subset.js +295 -0
- package/dist/validation/ui-security.d.ts +54 -0
- package/dist/validation/ui-security.d.ts.map +1 -0
- package/dist/validation/ui-security.js +138 -0
- package/dist/validation/zod-to-json-schema.d.ts +63 -0
- package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
- package/dist/validation/zod-to-json-schema.js +126 -0
- package/dist/version.d.ts +1458 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1459 -0
- 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
|
+
}
|