@ggui-ai/protocol 0.12.0 → 0.14.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.
- package/dist/gadgets/stdlib-gadgets.d.ts +1 -1
- package/dist/gadgets/stdlib-gadgets.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/integrations/mcp-apps.d.ts +63 -11
- package/dist/integrations/mcp-apps.d.ts.map +1 -1
- package/dist/integrations/mcp-apps.js +73 -3
- package/dist/schemas/app-theme.d.ts +5 -0
- package/dist/schemas/app-theme.d.ts.map +1 -1
- package/dist/schemas/app-theme.js +23 -0
- package/dist/schemas/data-contract.d.ts +3 -1
- package/dist/schemas/data-contract.d.ts.map +1 -1
- package/dist/schemas/data-contract.js +2 -0
- package/dist/schemas/invoke.d.ts +2 -2
- package/dist/schemas/mcp.d.ts +482 -24
- package/dist/schemas/mcp.d.ts.map +1 -1
- package/dist/schemas/mcp.js +455 -26
- package/dist/schemas/render-input-envelope.d.ts +20 -0
- package/dist/schemas/render-input-envelope.d.ts.map +1 -0
- package/dist/schemas/render-input-envelope.js +56 -0
- package/dist/types/ggui-session-event.d.ts +4 -5
- package/dist/types/ggui-session-event.d.ts.map +1 -1
- package/dist/types/ggui-session-event.js +0 -37
- package/dist/types/host-context.d.ts +17 -16
- package/dist/types/host-context.d.ts.map +1 -1
- package/dist/types/host-context.js +7 -0
- package/dist/types/llm-route.d.ts +2 -2
- package/dist/types/llm-route.d.ts.map +1 -1
- package/dist/types/llm-route.js +11 -3
- package/dist/types/llm.d.ts +397 -7
- package/dist/types/llm.d.ts.map +1 -1
- package/dist/types/llm.js +111 -10
- package/dist/types/mcp.d.ts +16 -53
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/types/readonly.d.ts +11 -0
- package/dist/types/readonly.d.ts.map +1 -0
- package/dist/types/readonly.js +1 -0
- package/dist/types/refusal-codes.d.ts +319 -0
- package/dist/types/refusal-codes.d.ts.map +1 -0
- package/dist/types/refusal-codes.js +329 -0
- package/dist/version.d.ts +79 -5
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +78 -4
- package/package.json +7 -3
package/dist/schemas/mcp.js
CHANGED
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { z } from 'zod';
|
|
27
27
|
import { blueprintDraftSchema, handshakeSuggestionSchema, } from './handshake-suggestion.js';
|
|
28
|
-
import { dataContractSchema } from './data-contract.js';
|
|
29
|
-
import { blueprintVarianceSchema } from './blueprint.js';
|
|
28
|
+
import { dataContractSchema, jsonObjectSchema } from './data-contract.js';
|
|
29
|
+
import { blueprintVarianceSchema, blueprintSourceSchema } from './blueprint.js';
|
|
30
|
+
import { REFUSAL_RETRIES, RENDER_GATE_REFUSAL_CODES, } from '../types/refusal-codes.js';
|
|
30
31
|
// ── Shared Sub-Schemas ──
|
|
31
32
|
export const viewportSchema = z.object({
|
|
32
33
|
width: z.number(),
|
|
@@ -325,9 +326,14 @@ export const renderInputShape = {
|
|
|
325
326
|
/**
|
|
326
327
|
* Typed `infra` envelope. Today carries one field (`model`); future
|
|
327
328
|
* expansion (temperature, max_tokens, provider hints) lands here
|
|
328
|
-
* additively. `model` MUST
|
|
329
|
-
*
|
|
330
|
-
*
|
|
329
|
+
* additively. `model` MUST parse as a model route in either wire form —
|
|
330
|
+
* canonical `provider:model` or LiteLLM `provider/model` (aliases resolve
|
|
331
|
+
* in both). The mechanism is `renderInputEnvelopeSchema`
|
|
332
|
+
* (`schemas/render-input-envelope.ts`), which the render handler parses
|
|
333
|
+
* BEFORE its pre-generation gate; this registered shape stays
|
|
334
|
+
* parser-free by design so a browser bundle never carries the route
|
|
335
|
+
* tables (ggui#818). A bound generator may also accept generator-specific
|
|
336
|
+
* prefixes for alternate transports.
|
|
331
337
|
*
|
|
332
338
|
* Strict — extra keys at `infra.*` are not silently dropped, so a
|
|
333
339
|
* typo (`infra.modelId`) surfaces as a clear zod path instead of a
|
|
@@ -339,7 +345,7 @@ export const renderInputShape = {
|
|
|
339
345
|
.string()
|
|
340
346
|
.min(1)
|
|
341
347
|
.optional()
|
|
342
|
-
.describe('
|
|
348
|
+
.describe('Model route in either wire form — canonical `anthropic:claude-haiku-4-5-20251001` or LiteLLM `anthropic/claude-haiku-4-5` (aliases resolve in both); generator-specific prefixes (e.g. `bedrock/...`) route to that transport. A value that parses in neither form fails the handler input parse at `infra.model`, before any pre-generation gate; `model_not_in_tier` is only ever a well-formed route with no rate row on the effective tier.'),
|
|
343
349
|
})
|
|
344
350
|
.strict()
|
|
345
351
|
.optional(),
|
|
@@ -445,6 +451,130 @@ export const renderErrorSchema = z.object({
|
|
|
445
451
|
.string()
|
|
446
452
|
.describe('Human-readable failure detail — fold into the next attempt or surface to the operator.'),
|
|
447
453
|
});
|
|
454
|
+
/**
|
|
455
|
+
* The three outcomes a `ggui_render` result can report (SPEC §7.1,
|
|
456
|
+
* ggui#786). A reader branches on this rather than guessing from which
|
|
457
|
+
* fields happen to be present:
|
|
458
|
+
*
|
|
459
|
+
* - `rendered` — the render ran and produced an interface. The
|
|
460
|
+
* identity fields (`sessionId`, `action`, `contractHash`,
|
|
461
|
+
* `blueprintId`, `variantKey`, `cache`) are all present.
|
|
462
|
+
* - `failed` — generation RAN and did not produce a component. The
|
|
463
|
+
* error session IS committed, so the identity fields are present
|
|
464
|
+
* and `error` carries the classification. The handshake is
|
|
465
|
+
* consumed.
|
|
466
|
+
* - `refused` — the deployment declined the call BEFORE it did any
|
|
467
|
+
* work: no state read, nothing committed, no spend. (The SDK has
|
|
468
|
+
* already checked the call against the declared `inputSchema` —
|
|
469
|
+
* the claim is nothing READ, not nothing validated.) The identity
|
|
470
|
+
* fields are structurally ABSENT and `refusal` carries the whole
|
|
471
|
+
* story; the handshake is INTACT, so the same id is valid on a
|
|
472
|
+
* retry.
|
|
473
|
+
*
|
|
474
|
+
* Declared HERE rather than in `types/mcp.ts` (which re-exports the
|
|
475
|
+
* inferred type) so `types/render.ts` can reference it without a
|
|
476
|
+
* type-only import cycle through `types/mcp.ts` — same convention as
|
|
477
|
+
* {@link renderErrorCodeSchema}.
|
|
478
|
+
*/
|
|
479
|
+
export const renderOutcomeSchema = z.enum(['rendered', 'failed', 'refused']);
|
|
480
|
+
/**
|
|
481
|
+
* In-result PRE-GENERATION REFUSAL marker (SPEC §7.1's refused arm,
|
|
482
|
+
* ggui#786). Present iff `outcome: 'refused'`.
|
|
483
|
+
*
|
|
484
|
+
* `code` draws from the closed render-gate subset of
|
|
485
|
+
* `PRE_GENERATION_REFUSAL_CODES` — a code that is not registered fails
|
|
486
|
+
* this enum at the transport, loudly. It is a bug, never a wire state.
|
|
487
|
+
*
|
|
488
|
+
* An agent MUST NOT auto-retry an `after-fix` refusal whose registry
|
|
489
|
+
* row names a `fixBy` other than `caller`: the fix belongs to the app's
|
|
490
|
+
* owner, the tenant, or the operator, and retrying does not perform it.
|
|
491
|
+
*/
|
|
492
|
+
export const renderRefusalSchema = z.object({
|
|
493
|
+
code: z
|
|
494
|
+
.enum(RENDER_GATE_REFUSAL_CODES)
|
|
495
|
+
.describe("Registered refusal state. Look the code up in the protocol's refusal registry for its retry class and which party can act; the accompanying `fix` names the one recovery step."),
|
|
496
|
+
message: z
|
|
497
|
+
.string()
|
|
498
|
+
.describe('Precise diagnostic — what was checked, and against what. Surface it to the operator; do not parse it.'),
|
|
499
|
+
fix: z
|
|
500
|
+
.string()
|
|
501
|
+
.describe('The one recovery step, addressed to the party that can take it. Retry the same call only when that party is the caller.'),
|
|
502
|
+
retry: z
|
|
503
|
+
.enum(REFUSAL_RETRIES)
|
|
504
|
+
.describe("How the call becomes possible again. 'after-fix': a named party acts and the same call then succeeds. 'next-period': time restores it at the next period boundary. 'later': transient — retry after a short delay. 'never': no caller action restores it under this identity."),
|
|
505
|
+
handshake: z
|
|
506
|
+
.literal('intact')
|
|
507
|
+
.describe('The handshake was NOT consumed — nothing was read. The same handshakeId is valid on a retry.'),
|
|
508
|
+
balanceCentsAtCheck: z
|
|
509
|
+
.number()
|
|
510
|
+
.int()
|
|
511
|
+
.optional()
|
|
512
|
+
.describe('Present only when the refusing check read a balance: its value at the moment of the check.'),
|
|
513
|
+
});
|
|
514
|
+
/**
|
|
515
|
+
* The COMPLETE structuredContent of a refused tool result — the whole
|
|
516
|
+
* payload, not a slice of it. Strict on purpose: a refusal commits
|
|
517
|
+
* nothing, so ANY other key (a `sessionId`, a `resourceUri`, an
|
|
518
|
+
* `error`) means the projection leaked state that does not exist.
|
|
519
|
+
*
|
|
520
|
+
* Today `ggui_render` is the only tool that carries a refusing gate;
|
|
521
|
+
* {@link renderOutputSchema} delegates its refused arm here rather than
|
|
522
|
+
* restating the rules, so there is exactly ONE declaration of them.
|
|
523
|
+
*
|
|
524
|
+
* NOT reusable verbatim by a second refusing tool, despite the strict
|
|
525
|
+
* shape reading as generic: {@link renderRefusalSchema} REQUIRES
|
|
526
|
+
* `handshake: 'intact'`, and that field is render-only by
|
|
527
|
+
* construction — a mutation consumes no handshake, so it has nothing
|
|
528
|
+
* to report intact. A mutation arm therefore lands WITH its first
|
|
529
|
+
* emitter, sharing the facts this envelope carries (`code`, `message`,
|
|
530
|
+
* `fix`, `retry`, one closed registry) and carrying no `handshake`
|
|
531
|
+
* field at all (ggui#798).
|
|
532
|
+
*/
|
|
533
|
+
export const refusedOutputSchema = z.strictObject({
|
|
534
|
+
outcome: z.literal('refused'),
|
|
535
|
+
refusal: renderRefusalSchema,
|
|
536
|
+
});
|
|
537
|
+
/**
|
|
538
|
+
* The refused arm's presence rule: the WHOLE payload must be
|
|
539
|
+
* {@link refusedOutputSchema}. Delegating here rather than restating
|
|
540
|
+
* the rule inline keeps one declaration of the RENDER refused
|
|
541
|
+
* envelope. A mutation arm cannot delegate to it — that schema
|
|
542
|
+
* REQUIRES `handshake: 'intact'` — so it mints its own with its first
|
|
543
|
+
* emitter, sharing the facts and carrying no `handshake` (ggui#798).
|
|
544
|
+
*/
|
|
545
|
+
function refineRefusedArm(value, ctx) {
|
|
546
|
+
if (refusedOutputSchema.safeParse(value).success)
|
|
547
|
+
return;
|
|
548
|
+
ctx.addIssue({
|
|
549
|
+
code: 'custom',
|
|
550
|
+
message: "outcome 'refused' MUST carry a registered `refusal` and nothing else — a refusal commits nothing, so no identity field and no `error` may appear beside it.",
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Report a field the committed arms (`rendered` / `failed`) require but
|
|
555
|
+
* the payload omits. The fields are optional at the schema level only
|
|
556
|
+
* to make room for the refused arm; demoting them must not weaken the
|
|
557
|
+
* committed arms, which is what this restores.
|
|
558
|
+
*/
|
|
559
|
+
function requireOnCommittedArm(present, field, outcome, ctx) {
|
|
560
|
+
if (present)
|
|
561
|
+
return;
|
|
562
|
+
ctx.addIssue({
|
|
563
|
+
code: 'custom',
|
|
564
|
+
path: [field],
|
|
565
|
+
message: `outcome '${outcome}' MUST carry \`${field}\` — a committed render reports its full identity.`,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
/** `refusal` rides the refused arm only. */
|
|
569
|
+
function rejectRefusalOnCommittedArm(refusal, outcome, ctx) {
|
|
570
|
+
if (refusal === undefined)
|
|
571
|
+
return;
|
|
572
|
+
ctx.addIssue({
|
|
573
|
+
code: 'custom',
|
|
574
|
+
path: ['refusal'],
|
|
575
|
+
message: `\`refusal\` is present only on outcome 'refused', not '${outcome}'.`,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
448
578
|
/**
|
|
449
579
|
* Canonical failure codes for a `resources/read` on a render locator
|
|
450
580
|
* (`ui://ggui/render/{sessionId}/{blueprintKey}`). Closed enum.
|
|
@@ -498,9 +628,10 @@ export const resourceReadErrorSchema = z.object({
|
|
|
498
628
|
.describe('Extra diagnostic context for operators. Dropped on NOT_FOUND so a denied read cannot be told apart from a miss.'),
|
|
499
629
|
});
|
|
500
630
|
/**
|
|
501
|
-
* Wire-output shape
|
|
502
|
-
*
|
|
503
|
-
*
|
|
631
|
+
* Wire-output shape. `outcome` is the discriminant and the only
|
|
632
|
+
* unconditionally required field; which of the others exist follows
|
|
633
|
+
* from it, per the THREE OUTCOMES section below — that section is the
|
|
634
|
+
* single summary of this shape, so do not restate it here.
|
|
504
635
|
* The handler carries `shortCode`, `codeReady`, `handshakeId`,
|
|
505
636
|
* `decision`, `contract`, `codeUrl`, `codeHash`
|
|
506
637
|
* on its internal `RenderOutput` TS shape for telemetry / post-classify
|
|
@@ -514,17 +645,38 @@ export const resourceReadErrorSchema = z.object({
|
|
|
514
645
|
* `render-resource/...`). Leaving a dead URL on the wire had the model
|
|
515
646
|
* hallucinating links that resolve nowhere.
|
|
516
647
|
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
648
|
+
* THREE OUTCOMES (SPEC §7.1, ggui#786). Every result carries
|
|
649
|
+
* {@link renderOutcomeSchema} on `outcome`, and the identity fields are
|
|
650
|
+
* present IFF something was committed — which is why they are optional
|
|
651
|
+
* at the schema level and pinned by the presence refinement below:
|
|
652
|
+
*
|
|
653
|
+
* - `rendered` — identity fields present; `resourceUri` present iff
|
|
654
|
+
* mountable; no `error`, no `refusal`.
|
|
655
|
+
* - `failed` — generation ran and produced nothing. Identity fields
|
|
656
|
+
* present (the error GguiSession IS committed, so `sessionId`
|
|
657
|
+
* remains a live handle into the session channel), `error`
|
|
658
|
+
* present, `resourceUri` absent, no `_meta` on the result. The
|
|
659
|
+
* handshake is consumed.
|
|
660
|
+
* - `refused` — the deployment declined before doing any work.
|
|
661
|
+
* Identity fields ABSENT, `refusal` present, no `error`, no
|
|
662
|
+
* `nextStep`, no `_meta`. Nothing was committed and the handshake
|
|
663
|
+
* is intact. The refused arm's whole envelope is
|
|
664
|
+
* {@link refusedOutputSchema}.
|
|
665
|
+
*
|
|
666
|
+
* The root stays ONE object with a discriminant field rather than a
|
|
667
|
+
* discriminated union: the MCP spec's `Tool.outputSchema` root MUST be
|
|
668
|
+
* a JSON Schema of type `object`, and the SDK registers zod raw shapes.
|
|
669
|
+
* TypeScript narrowing is via the guards at the bottom of this file
|
|
670
|
+
* ({@link isRenderedOutput} / {@link isFailedRenderOutput} /
|
|
671
|
+
* {@link isRefusedRenderOutput}), never a parallel union type.
|
|
522
672
|
*
|
|
523
673
|
* Post-Phase-B the `'compose'` action enum value is gone — there is no
|
|
524
674
|
* stack of N renders to compose against.
|
|
525
675
|
*/
|
|
526
676
|
export const renderOutputSchema = z.object({
|
|
527
|
-
|
|
677
|
+
outcome: renderOutcomeSchema.describe("Which of the three outcomes this result reports. 'rendered': an interface was produced. 'failed': generation ran and produced none — the session is committed and `error` classifies it. 'refused': the deployment declined before doing any work — `refusal` carries the state, nothing was committed, and the handshake is still valid."),
|
|
678
|
+
/** Present iff something was committed — absent on a refusal. */
|
|
679
|
+
sessionId: z.string().optional(),
|
|
528
680
|
/**
|
|
529
681
|
* Spec-canonical MCP-Apps entry-point — same `ui://ggui/render/{id}`
|
|
530
682
|
* URI surfaced on `_meta.ui.resourceUri`. Surfacing it on the LLM-
|
|
@@ -541,17 +693,24 @@ export const renderOutputSchema = z.object({
|
|
|
541
693
|
.string()
|
|
542
694
|
.optional()
|
|
543
695
|
.describe('MCP-Apps mount URI (ui://ggui/render/{id}). Present iff the render is mountable; absent on a failed render.'),
|
|
544
|
-
action: z
|
|
696
|
+
action: z
|
|
697
|
+
.enum(['create', 'reuse', 'update', 'replace', 'declined'])
|
|
698
|
+
.optional(),
|
|
545
699
|
contractHash: z
|
|
546
700
|
.string()
|
|
701
|
+
.optional()
|
|
547
702
|
.describe('Canonical hash of the rendered data contract (shape only — fields, types, specs). Same hash ⟺ same data flow.'),
|
|
548
703
|
blueprintId: z
|
|
549
704
|
.string()
|
|
705
|
+
.optional()
|
|
550
706
|
.describe('Opaque id of the materialised component for this render. On the handshake-decided reuse paths (accept a cache-origin proposal, or a variance re-aim that resolves to an existing variant) it is the stored id — equal ids across renders mean the same stored component. override.contract always generates cold and mints a fresh id, even for an identical contract.'),
|
|
551
707
|
variantKey: z
|
|
552
708
|
.string()
|
|
709
|
+
.optional()
|
|
553
710
|
.describe('Canonical hash of the design-time variance (persona, aesthetic, seed prompt, context). With contractHash it forms the reuse key: the same pair reuses one component; a different variant of the same contract gets its own.'),
|
|
554
|
-
cache: renderCacheMarkerSchema
|
|
711
|
+
cache: renderCacheMarkerSchema
|
|
712
|
+
.optional()
|
|
713
|
+
.describe('Reuse outcome for this render: whether a stored component was served, its similarity, the matched component id, and how many generation calls that avoided.'),
|
|
555
714
|
/**
|
|
556
715
|
* In-result failure marker — present iff the tool result is
|
|
557
716
|
* `isError: true`. The structuredContent stays schema-conformant on
|
|
@@ -561,6 +720,14 @@ export const renderOutputSchema = z.object({
|
|
|
561
720
|
error: renderErrorSchema
|
|
562
721
|
.optional()
|
|
563
722
|
.describe('Present iff the tool result is isError — canonical {code, message} for a failed/rejected generation. Absent on success.'),
|
|
723
|
+
/**
|
|
724
|
+
* In-result PRE-GENERATION REFUSAL marker — present iff
|
|
725
|
+
* `outcome: 'refused'`, and then it is the ONLY field besides
|
|
726
|
+
* `outcome` (see {@link refusedOutputSchema}).
|
|
727
|
+
*/
|
|
728
|
+
refusal: renderRefusalSchema
|
|
729
|
+
.optional()
|
|
730
|
+
.describe('Present iff outcome is refused — the registered state the deployment declined on, plus the one recovery step. Nothing was committed and the handshake is still valid.'),
|
|
564
731
|
/**
|
|
565
732
|
* Wire-shape recovery hint for the next call. Emitted ONLY when the
|
|
566
733
|
* rendered contract has a non-empty `actionSpec` — i.e. the agent will
|
|
@@ -586,6 +753,56 @@ export const renderOutputSchema = z.object({
|
|
|
586
753
|
timeout: z.number(),
|
|
587
754
|
}),
|
|
588
755
|
}).optional().describe('Required-next-call hint — when the rendered contract has actions, points the agent at ggui_consume({sessionId, timeout}) for the inbound action loop. Absent for pure-display renders.'),
|
|
756
|
+
}).superRefine((value, ctx) => {
|
|
757
|
+
// Present-iff-committed. NOTE for implementors: this refinement is
|
|
758
|
+
// attached to the COMPOSED schema. A consumer that decomposes the
|
|
759
|
+
// schema to its raw shape and rebuilds it (`z.object(schema.shape)`,
|
|
760
|
+
// which is how the MCP SDK registers a tool's outputSchema) loses
|
|
761
|
+
// these rules — which is why the transport validates a refused
|
|
762
|
+
// payload against `refusedOutputSchema` directly. See SPEC §7.1.
|
|
763
|
+
if (value.outcome === 'refused') {
|
|
764
|
+
refineRefusedArm(value, ctx);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
requireOnCommittedArm(value.sessionId !== undefined, 'sessionId', value.outcome, ctx);
|
|
768
|
+
requireOnCommittedArm(value.action !== undefined, 'action', value.outcome, ctx);
|
|
769
|
+
requireOnCommittedArm(value.contractHash !== undefined, 'contractHash', value.outcome, ctx);
|
|
770
|
+
requireOnCommittedArm(value.blueprintId !== undefined, 'blueprintId', value.outcome, ctx);
|
|
771
|
+
requireOnCommittedArm(value.variantKey !== undefined, 'variantKey', value.outcome, ctx);
|
|
772
|
+
requireOnCommittedArm(value.cache !== undefined, 'cache', value.outcome, ctx);
|
|
773
|
+
rejectRefusalOnCommittedArm(value.refusal, value.outcome, ctx);
|
|
774
|
+
// `resourceUri` present IFF `rendered` — the mountability rule, the
|
|
775
|
+
// one presence rule the six identity fields do not cover. A `failed`
|
|
776
|
+
// render commits an error GguiSession but exposes no mount, so a URI
|
|
777
|
+
// beside it points a host at a render that does not exist.
|
|
778
|
+
if (value.outcome === 'rendered' && value.resourceUri === undefined) {
|
|
779
|
+
ctx.addIssue({
|
|
780
|
+
code: 'custom',
|
|
781
|
+
path: ['resourceUri'],
|
|
782
|
+
message: "outcome 'rendered' MUST carry `resourceUri` — a rendered result is mountable, and the URI is how a host mounts it.",
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
if (value.outcome === 'failed' && value.resourceUri !== undefined) {
|
|
786
|
+
ctx.addIssue({
|
|
787
|
+
code: 'custom',
|
|
788
|
+
path: ['resourceUri'],
|
|
789
|
+
message: "`resourceUri` is present only on outcome 'rendered' — a failed render exposes no mount affordance.",
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (value.outcome === 'failed' && value.error === undefined) {
|
|
793
|
+
ctx.addIssue({
|
|
794
|
+
code: 'custom',
|
|
795
|
+
path: ['error'],
|
|
796
|
+
message: "outcome 'failed' MUST carry `error` — the classification is what distinguishes it from a render that succeeded.",
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (value.outcome === 'rendered' && value.error !== undefined) {
|
|
800
|
+
ctx.addIssue({
|
|
801
|
+
code: 'custom',
|
|
802
|
+
path: ['error'],
|
|
803
|
+
message: "`error` is present only on outcome 'failed', not 'rendered'.",
|
|
804
|
+
});
|
|
805
|
+
}
|
|
589
806
|
});
|
|
590
807
|
/**
|
|
591
808
|
* `ggui_update` — refresh the rendered UI with new state.
|
|
@@ -654,9 +871,22 @@ export const amendInputSchema = z.discriminatedUnion('kind', [
|
|
|
654
871
|
}).strict(),
|
|
655
872
|
]);
|
|
656
873
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
874
|
+
* Agent-facing description of the schema-attestation hash, shared by
|
|
875
|
+
* both mutation tools (ggui#560). It is not a comment: `.describe()`
|
|
876
|
+
* ships as JSON-Schema `description` in the tool declaration, so this
|
|
877
|
+
* string is what an agent reads out of `tools/list` when it has to
|
|
878
|
+
* decide whether a hash mismatch means the contract moved under it.
|
|
879
|
+
* Declared once so the two tools cannot say different things about the
|
|
880
|
+
* same field.
|
|
881
|
+
*/
|
|
882
|
+
const PROPS_SCHEMA_HASH_DESCRIPTION = 'sha256 (lowercase hex) over the RFC 8785 canonical form of the enforced props schema this mutation was validated against — the same schema the paired handshake disclosed. Present when the session declares a propsSpec. Equal to the handshake propsSchemaHash by the session-continuity guarantee; a mismatch means the contract changed under you.';
|
|
883
|
+
/** Agent-facing description of the grammar-profile classifier. Same rationale as {@link PROPS_SCHEMA_HASH_DESCRIPTION}. */
|
|
884
|
+
const PROPS_SCHEMA_PROFILE_DESCRIPTION = "Grammar profile of the enforced props schema: 'grammar-safe' or 'full'. Present with propsSchemaHash; treat unrecognized values as 'full'.";
|
|
885
|
+
/**
|
|
886
|
+
* Wire-output shape — minimal acknowledgement. This schema IS the
|
|
887
|
+
* handler's declared output: `ggui_update` registers `.shape` and its
|
|
888
|
+
* return type is `GguiUpdateOutput` (`z.infer` of this schema), so
|
|
889
|
+
* there is no second declaration to keep in step (ggui#798).
|
|
660
890
|
*
|
|
661
891
|
* Every REAL update (`updated: true`) mints a new history record and
|
|
662
892
|
* its result carries the `ai.ggui/render` slice as a FULL bootable
|
|
@@ -666,6 +896,15 @@ export const amendInputSchema = z.discriminatedUnion('kind', [
|
|
|
666
896
|
* history when its higher-epoch `props_update` frame lands; the
|
|
667
897
|
* in-place repaint over the live-channel ladder (WS / SSE / polling /
|
|
668
898
|
* bridge-pull) is `ggui_amend`'s job.
|
|
899
|
+
*
|
|
900
|
+
* NO refusal arm (ggui#786): the pre-generation refusal envelope rides
|
|
901
|
+
* `ggui_render` only. There is no pre-state POLICY gate on the
|
|
902
|
+
* mutation tools today — nothing on this path can decline a call for
|
|
903
|
+
* deployment-policy reasons (a contract error still rejects a
|
|
904
|
+
* malformed mutation before any store read; that is a parse result,
|
|
905
|
+
* not a refusal) — and `handshake: 'intact'` would assert something
|
|
906
|
+
* meaningless on a tool that consumes no handshake. The arm lands with its first
|
|
907
|
+
* emitter (ggui#798), not as a mechanical port of the render one.
|
|
669
908
|
*/
|
|
670
909
|
export const updateOutputSchema = z.object({
|
|
671
910
|
sessionId: z.string(),
|
|
@@ -676,8 +915,7 @@ export const updateOutputSchema = z.object({
|
|
|
676
915
|
* no-op (`updated: false`) no record is minted and this is the bare
|
|
677
916
|
* live-head URI. Mirrored on the LLM-visible structuredContent so
|
|
678
917
|
* SDKs that strip `_meta` from tool_results can still reach the
|
|
679
|
-
* mount URI.
|
|
680
|
-
* this export and the handler's inline schema must not drift.
|
|
918
|
+
* mount URI.
|
|
681
919
|
*/
|
|
682
920
|
resourceUri: z.string(),
|
|
683
921
|
/**
|
|
@@ -707,9 +945,15 @@ export const updateOutputSchema = z.object({
|
|
|
707
945
|
* session-continuity guarantee, so a mismatch is the observable form
|
|
708
946
|
* of a contract changing mid-session.
|
|
709
947
|
*/
|
|
710
|
-
propsSchemaHash: z
|
|
948
|
+
propsSchemaHash: z
|
|
949
|
+
.string()
|
|
950
|
+
.optional()
|
|
951
|
+
.describe(PROPS_SCHEMA_HASH_DESCRIPTION),
|
|
711
952
|
/** Present with `propsSchemaHash`; same profile classifier as the handshake's. */
|
|
712
|
-
propsSchemaProfile: z
|
|
953
|
+
propsSchemaProfile: z
|
|
954
|
+
.string()
|
|
955
|
+
.optional()
|
|
956
|
+
.describe(PROPS_SCHEMA_PROFILE_DESCRIPTION),
|
|
713
957
|
});
|
|
714
958
|
/**
|
|
715
959
|
* `ggui_amend` wire output (#483) — acknowledgement only. `resourceUri`
|
|
@@ -717,17 +961,49 @@ export const updateOutputSchema = z.object({
|
|
|
717
961
|
* mints a record, so there is no pinned URI to return and no epoch
|
|
718
962
|
* field — the history number is untouched by construction). The
|
|
719
963
|
* mounted card receives the new props over the live channels.
|
|
964
|
+
*
|
|
965
|
+
* As with {@link updateOutputSchema}, this schema IS the handler's
|
|
966
|
+
* declared output — `ggui_amend` registers `.shape` and returns
|
|
967
|
+
* `GguiAmendOutput` (ggui#798).
|
|
720
968
|
*/
|
|
721
969
|
export const amendOutputSchema = z.object({
|
|
722
970
|
sessionId: z.string(),
|
|
723
971
|
updated: z.boolean(),
|
|
972
|
+
/**
|
|
973
|
+
* The BARE live-head URI — amend targets the mounted card and never
|
|
974
|
+
* mints a record, so there is no pinned URI to return and no epoch
|
|
975
|
+
* field (the history number is untouched by construction).
|
|
976
|
+
*
|
|
977
|
+
* NORMATIVE re-anchor reference (SPEC §7.1.2.1, ggui#652 /
|
|
978
|
+
* guuey#535): together with `sessionId` this is the durable record
|
|
979
|
+
* that an in-place repaint touched this session at this turn. A
|
|
980
|
+
* host's persistence layer MAY consume it as a locator-only
|
|
981
|
+
* re-anchor — `resources/read`-resolvable, stable for the session's
|
|
982
|
+
* lifetime — so a restored transcript re-positions the card at its
|
|
983
|
+
* latest referencing turn and rehydrates CURRENT state instead of a
|
|
984
|
+
* stale earlier snapshot. It rides `structuredContent` (LLM-visible,
|
|
985
|
+
* same rationale as ggui_update's resourceUri: consumers that strip
|
|
986
|
+
* `_meta` still reach it) and MUST NOT move to a result `_meta`
|
|
987
|
+
* slice — any result `_meta` on this tool makes view-minting hosts
|
|
988
|
+
* break the in-place semantics.
|
|
989
|
+
*
|
|
990
|
+
* Both this field and `sessionId` are REQUIRED, which is what makes
|
|
991
|
+
* SPEC §7.1.2.1's "structurally guaranteed" true rather than a
|
|
992
|
+
* convention.
|
|
993
|
+
*/
|
|
724
994
|
resourceUri: z.string(),
|
|
725
995
|
/** Same no-op feedback channel as ggui_update's `warning`. */
|
|
726
996
|
warning: z.string().optional(),
|
|
727
997
|
/** Schema attestation (ggui#560) — same semantics as ggui_update's. */
|
|
728
|
-
propsSchemaHash: z
|
|
998
|
+
propsSchemaHash: z
|
|
999
|
+
.string()
|
|
1000
|
+
.optional()
|
|
1001
|
+
.describe(PROPS_SCHEMA_HASH_DESCRIPTION),
|
|
729
1002
|
/** Present with `propsSchemaHash`. */
|
|
730
|
-
propsSchemaProfile: z
|
|
1003
|
+
propsSchemaProfile: z
|
|
1004
|
+
.string()
|
|
1005
|
+
.optional()
|
|
1006
|
+
.describe(PROPS_SCHEMA_PROFILE_DESCRIPTION),
|
|
731
1007
|
});
|
|
732
1008
|
/**
|
|
733
1009
|
* `ggui_runtime_declare_tool_catalog` — the host runtime declares its
|
|
@@ -951,3 +1227,156 @@ export const runtimeTelemetryInputShape = {
|
|
|
951
1227
|
export const runtimeTelemetryInputSchema = z.object(runtimeTelemetryInputShape);
|
|
952
1228
|
/** `ggui_runtime_telemetry` output — bare acknowledgement. */
|
|
953
1229
|
export const runtimeTelemetryOutputSchema = z.object({ ok: z.literal(true) });
|
|
1230
|
+
/** A render that produced an interface — identity fields present. */
|
|
1231
|
+
export function isRenderedOutput(output) {
|
|
1232
|
+
return output.outcome === 'rendered';
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* A generation that RAN and produced nothing. The error session is
|
|
1236
|
+
* committed, so `sessionId` is a live handle and `error` classifies it.
|
|
1237
|
+
*/
|
|
1238
|
+
export function isFailedRenderOutput(output) {
|
|
1239
|
+
return output.outcome === 'failed';
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* A PRE-GENERATION refusal — nothing read and nothing committed, so
|
|
1243
|
+
* every identity field is absent and `refusal` carries the state. (Not
|
|
1244
|
+
* "nothing parsed": the SDK has already checked the call against the
|
|
1245
|
+
* tool's declared `inputSchema` by the time a gate can refuse it.)
|
|
1246
|
+
*/
|
|
1247
|
+
export function isRefusedRenderOutput(output) {
|
|
1248
|
+
return output.outcome === 'refused';
|
|
1249
|
+
}
|
|
1250
|
+
// ============================================================================
|
|
1251
|
+
// Tool output schemas the protocol owns (#817 part C). A handler registers
|
|
1252
|
+
// `<schema>.shape` as its MCP `outputSchema` and derives its output type from
|
|
1253
|
+
// the schema — never a parallel interface. No `.readonly()` here: zod 4
|
|
1254
|
+
// projects it as `readOnly` into the advertised JSON Schema, and the wire is
|
|
1255
|
+
// mutable JSON; readonly is applied at the seam (`DeepReadonly` on the
|
|
1256
|
+
// derived types). Every shape is closed: the transport strip-parses against
|
|
1257
|
+
// `.shape`, so the shape IS the wire.
|
|
1258
|
+
// Honest exception, pre-existing: three `.readonly()` calls in
|
|
1259
|
+
// `schemas/data-contract.ts` (gadget requires/connect, app public env) reach
|
|
1260
|
+
// the registered render + handshake inputs through `dataContractSchema` and
|
|
1261
|
+
// advertise `readOnly` on tools/list today — ggui#824 removes them.
|
|
1262
|
+
// ============================================================================
|
|
1263
|
+
/** Display modes an MCP Apps host can render a view in (ext-apps vocabulary). */
|
|
1264
|
+
export const mcpUiDisplayModeSchema = z.enum(['inline', 'fullscreen', 'pip']);
|
|
1265
|
+
/**
|
|
1266
|
+
* The host-context projection the iframe-runtime observes and `ggui_consume`
|
|
1267
|
+
* echoes (`client.hostContext`). Every field optional: a host that never
|
|
1268
|
+
* reports one leaves it absent — absent ⇒ the documented default.
|
|
1269
|
+
*/
|
|
1270
|
+
export const hostContextProjectionSchema = z.object({
|
|
1271
|
+
availableDisplayModes: z.array(mcpUiDisplayModeSchema).optional(),
|
|
1272
|
+
currentDisplayMode: mcpUiDisplayModeSchema.optional(),
|
|
1273
|
+
containerDimensions: z
|
|
1274
|
+
.object({
|
|
1275
|
+
width: z.number().optional(),
|
|
1276
|
+
maxWidth: z.number().optional(),
|
|
1277
|
+
height: z.number().optional(),
|
|
1278
|
+
maxHeight: z.number().optional(),
|
|
1279
|
+
})
|
|
1280
|
+
.optional(),
|
|
1281
|
+
platform: z.enum(['web', 'desktop', 'mobile']).optional(),
|
|
1282
|
+
deviceCapabilities: z
|
|
1283
|
+
.object({ touch: z.boolean().optional(), hover: z.boolean().optional() })
|
|
1284
|
+
.optional(),
|
|
1285
|
+
locale: z.string().optional(),
|
|
1286
|
+
timeZone: z.string().optional(),
|
|
1287
|
+
});
|
|
1288
|
+
/** `ggui_consume`'s `client` slice — what the runtime observed about its host. */
|
|
1289
|
+
export const clientObservationsSchema = z.object({
|
|
1290
|
+
hostContext: hostContextProjectionSchema.optional(),
|
|
1291
|
+
});
|
|
1292
|
+
/** One row of `ggui_list_sessions` — eight closed keys; nothing passes through. */
|
|
1293
|
+
export const gguiSessionSummaryWireSchema = z.object({
|
|
1294
|
+
sessionId: z.string(),
|
|
1295
|
+
hostName: z.string().optional(),
|
|
1296
|
+
hostSessionId: z.string().optional(),
|
|
1297
|
+
createdAt: z.string(),
|
|
1298
|
+
lastActivityAt: z.string(),
|
|
1299
|
+
status: z.string(),
|
|
1300
|
+
wsToken: z.string().optional(),
|
|
1301
|
+
wsTokenExpiresAt: z.string().optional(),
|
|
1302
|
+
});
|
|
1303
|
+
/**
|
|
1304
|
+
* `ggui_get_session`'s wire: the store row's six base fields plus the mount
|
|
1305
|
+
* variant — for EVERY session. An MCP-Apps mount is locator-only on the
|
|
1306
|
+
* render object, but its store row carries the base fields, so the
|
|
1307
|
+
* projection reads them from the row and the wire never fails on that
|
|
1308
|
+
* variant. The locator itself is not on this wire (MCP-Apps resources have
|
|
1309
|
+
* their own paths).
|
|
1310
|
+
*/
|
|
1311
|
+
export const gguiGetSessionOutputSchema = z.object({
|
|
1312
|
+
variant: z.enum(['render', 'mcpApps']),
|
|
1313
|
+
id: z.string(),
|
|
1314
|
+
appId: z.string(),
|
|
1315
|
+
eventSequence: z.number().int().nonnegative(),
|
|
1316
|
+
createdAt: z.number().int().nonnegative(),
|
|
1317
|
+
lastActivityAt: z.number().int().nonnegative(),
|
|
1318
|
+
expiresAt: z.number().int().nonnegative(),
|
|
1319
|
+
/**
|
|
1320
|
+
* The last-known value of every declared contextSpec slot, as
|
|
1321
|
+
* `ggui_runtime_sync_context` wrote it onto the row — the read path a
|
|
1322
|
+
* raw MCP client (no widget-context mirror) has for contextSpec values.
|
|
1323
|
+
* Present iff the row carries one; never an empty placeholder.
|
|
1324
|
+
*/
|
|
1325
|
+
contextSnapshot: jsonObjectSchema.optional(),
|
|
1326
|
+
});
|
|
1327
|
+
/** The three sequential gates of `ggui_protocol_validate_blueprint`. */
|
|
1328
|
+
export const blueprintValidationTierSchema = z.enum(['compile', 'selfCheck', 'runtime']);
|
|
1329
|
+
export const blueprintValidationIssueSchema = z.object({
|
|
1330
|
+
tier: blueprintValidationTierSchema,
|
|
1331
|
+
code: z.string(),
|
|
1332
|
+
message: z.string(),
|
|
1333
|
+
fix: z.string().optional(),
|
|
1334
|
+
});
|
|
1335
|
+
/** `ggui_protocol_validate_blueprint`'s result envelope: `failedAt` names the tier that stopped, or null. */
|
|
1336
|
+
export const blueprintValidationResultSchema = z.object({
|
|
1337
|
+
valid: z.boolean(),
|
|
1338
|
+
failedAt: blueprintValidationTierSchema.nullable(),
|
|
1339
|
+
errors: z.array(blueprintValidationIssueSchema),
|
|
1340
|
+
warnings: z.array(blueprintValidationIssueSchema),
|
|
1341
|
+
});
|
|
1342
|
+
/** A provider row — what `ggui_list_featured_blueprints` returns per blueprint. */
|
|
1343
|
+
export const blueprintEntryWireSchema = z.object({
|
|
1344
|
+
id: z.string(),
|
|
1345
|
+
name: z.string(),
|
|
1346
|
+
description: z.string().optional(),
|
|
1347
|
+
source: blueprintSourceSchema,
|
|
1348
|
+
updatedAt: z.string(),
|
|
1349
|
+
tags: z.array(z.string()).optional(),
|
|
1350
|
+
});
|
|
1351
|
+
export const gguiListFeaturedBlueprintsOutputSchema = z.object({
|
|
1352
|
+
blueprints: z.array(blueprintEntryWireSchema),
|
|
1353
|
+
total: z.number().int().nonnegative(),
|
|
1354
|
+
});
|
|
1355
|
+
/** One `ggui_search_blueprints` hit — a scored row plus the registry-only keys. */
|
|
1356
|
+
export const gguiSearchBlueprintsResultSchema = z.object({
|
|
1357
|
+
id: z.string(),
|
|
1358
|
+
name: z.string(),
|
|
1359
|
+
description: z.string(),
|
|
1360
|
+
category: z.string(),
|
|
1361
|
+
props: z.array(z.object({ name: z.string(), type: z.string(), required: z.boolean(), description: z.string() })),
|
|
1362
|
+
callbacks: z.array(z.string()),
|
|
1363
|
+
featured: z.boolean(),
|
|
1364
|
+
relevance: z.literal('match'),
|
|
1365
|
+
score: z.number(),
|
|
1366
|
+
origin: z.literal('registry').optional(),
|
|
1367
|
+
artifactId: z.string().optional(),
|
|
1368
|
+
version: z.string().optional(),
|
|
1369
|
+
mcpTools: z.array(z.object({ server: z.string().optional(), tool: z.string() })).optional(),
|
|
1370
|
+
scopeVerification: z.enum(['verified', 'unverified']).optional(),
|
|
1371
|
+
});
|
|
1372
|
+
export const gguiSearchBlueprintsOutputSchema = z.object({
|
|
1373
|
+
results: z.array(gguiSearchBlueprintsResultSchema),
|
|
1374
|
+
total: z.number(),
|
|
1375
|
+
query: z.string(),
|
|
1376
|
+
degradedSources: z
|
|
1377
|
+
.array(z.object({
|
|
1378
|
+
source: z.literal('registry'),
|
|
1379
|
+
reason: z.enum(['unreachable', 'timeout', 'invalid_response']),
|
|
1380
|
+
}))
|
|
1381
|
+
.optional(),
|
|
1382
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const renderInputEnvelopeSchema: z.ZodObject<{
|
|
3
|
+
handshakeId: z.ZodString;
|
|
4
|
+
props: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
5
|
+
themeId: z.ZodOptional<z.ZodString>;
|
|
6
|
+
infra: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
model: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strict>>;
|
|
9
|
+
override: z.ZodOptional<z.ZodObject<{
|
|
10
|
+
contract: z.ZodOptional<z.ZodType<import("..").DataContract, unknown, z.core.$ZodTypeInternals<import("..").DataContract, unknown>>>;
|
|
11
|
+
variance: z.ZodOptional<z.ZodType<import("..").BlueprintVariance, unknown, z.core.$ZodTypeInternals<import("..").BlueprintVariance, unknown>>>;
|
|
12
|
+
}, z.core.$strict>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export declare const renderInputRouteGuardSchema: z.ZodObject<{
|
|
15
|
+
infra: z.ZodOptional<z.ZodObject<{
|
|
16
|
+
model: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, z.core.$loose>>;
|
|
18
|
+
}, z.core.$loose>;
|
|
19
|
+
export type RenderInputEnvelope = z.infer<typeof renderInputEnvelopeSchema>;
|
|
20
|
+
//# sourceMappingURL=render-input-envelope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-input-envelope.d.ts","sourceRoot":"","sources":["../../src/schemas/render-input-envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAiCxB,eAAO,MAAM,yBAAyB;;;;;;;;;;;iBAA6C,CAAC;AA6BpF,eAAO,MAAM,2BAA2B;;;;iBAA+C,CAAC;AAExF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|