Frontend SDK Reference
@ces/app-sdk is the supported frontend boundary for Cessy Custom Pages. The
host provides it through the page import map, so page bundles import it directly
and do not install or bundle an npm package.
For machine-readable discovery in Design MCP v1, call get_frontend_sdk or
read ces://design/capabilities/frontend-sdk. The tool response includes an
authoringCatalog with every supported SDK runtime and type export plus every
default @app/ui/* component module and its exported names. Inspect the Page
Design System when app- or Workspace-owned aliases can override or extend that
default UI catalog. In Design MCP vNext, read the exact page-source
Artifact-kind Resource and follow its app-scoped link to
ces://design/vnext/apps/{appId}/capabilities/page-runtime. Both SDK surfaces
serve the same platform-owned export contract summarized here; vNext does not
require another SDK tool or a second schema.
The vNext page-source contract also includes compact examples for a modeled
Projection list, useCesNavigation() into a bound detail Page,
useParams()/useDocument(), and an awaited useCommand() action. Interactive
Evaluation currently requires a static route root: use
returns/[returnId], not [returnId].
Choose the correct boundary
Do not hand-wire /api/apps/..., page-chat, attachment, workspace, or other
platform-internal routes. They are host implementation details, not public
integration contracts. Custom Pages use the SDK; external clients fetch the
app-specific OpenAPI document and use Runtime REST.
Host context, authentication, and runtime source
Cessy mounts each hosted page inside CesPageProvider. The host supplies the
tenant and app identity, current user, page parameters and binding, navigation,
an authenticated fetch function, the selected runtime source, and a typed
session capability.
The host renders the default export as <PageComponent /> and does not pass
Next.js params, searchParams, a bound document, or other component props.
The default component must therefore be callable without required props. Read
route and query values with useParams(), bound records with useDocument(),
and the remaining hosted context through the SDK hooks below.
Page code never receives or stores a bearer token. Data, command, file,
useAgents, and channel-chat helpers preserve the provider’s Production or
Workspace source. The shell selects that source; page code should not add
workspaceId itself.
useAgentChat currently resolves its chat target from the published app
configuration. useAgents can show a Workspace-only agent while previewing,
but that agent becomes a valid useAgentChat target only after promotion. This
limitation is also declared on the machine-readable SDK contract. Do not bypass
it by calling an internal page-chat route.
useCesContext().fetch is an authenticated low-level fetch function, not a
general public API client and not an automatic runtime-source URL rewriter.
Prefer the typed SDK hooks. If a supported public HTTP operation is needed from
an external client, use the generated Runtime REST contract instead.
User-delegated chat requires an authenticated operator. Do not assume that
useAgentChat or useChannelChat is available to an anonymous public page
share merely because projection or command access was granted to its constrained
public principal.
Host-owned sign-out
Standalone pages have no Operate account menu, so render an accessible control
with useSession() when users need to end their Cessy session:
The result exposes surface, supported, state, error, and signOut().
Supported physical Operate and standalone sessions transition through idle,
pending, and signed-out or failed. The host validates the app and calling
surface, clears the physical identity session together with selected-tenant,
impersonation, and stale Agent Page Session state, then redirects to the fixed
/auth/login path. Page code never supplies an identity, tenant, redirect, or
cookie name.
Agent Page Session, public-share, preview, Evaluation, and unauthenticated
surfaces return supported: false; signOut() returns a typed unsupported
result without a network request or navigation. Do not hand-wire the private
host endpoint or treat an unsupported surface as a physical login session.
Durable file access
useUploadFile() stores a browser File or Blob and returns a CesFileRef.
When access is omitted, the host binds the file to the authenticated
principal; page code cannot name a different principal. Choose the broader
access: 'app' compatibility scope only when every authenticated operator with
app access should be able to download the object. Use
{ kind: 'projection-document', projection, documentId } when the file should
reuse a modeled document’s projection authorization.
Policy-side ctx.files.put() and ctx.files.importUrl() do not have an
implicit scope: they must supply access, or both projection and
documentId. Browser and policy metadata, roles, tags, filenames, and MIME
types never grant download authority.
useFileUrl() resolves the selected Production or Workspace source, but does
not weaken the stored access rule. Both browser GET and HEAD fail with the
same 404 contract for an unknown file and a known file that the current
principal cannot read. This prevents a copied file id or URL from acting as a
capability. Authorized inline-safe files can also be rendered from the returned
URL in a same-origin iframe, including browser PDF previews. The response blocks
cross-origin framing.
Agent selection and page chat
useAgents returns only the safe app agent catalog. Each item has this shape:
The catalog does not expose prompts, models, MCP configuration, credentials,
container settings, provider secrets, scope policy, or other private runtime
configuration. In Production, or for an agent already present in the published
configuration, a selected id can be passed directly to useAgentChat.
When useAgentChat has no active conversation, the first send creates a fresh
channel-backed DM conversation. Selecting a conversation from its history
continues that selected conversation. startNewConversation() clears the
active selection so the next send creates another fresh conversation. The chat
target remains the published agent configuration even while the surrounding
page previews Workspace data.
Use useChannelChat instead when the channel itself is the domain conversation.
It can bind by channelId or by an entity reference and will not create a
parallel page-agent DM.
Chat attachments and agent access
Both chat hooks upload through the same private Platform attachment boundary. Upload first, then pass the returned attachment metadata with the message:
A message accepts at most four attachments and each upload is capped at 8 MiB.
The host preserves the sanitized filename, MIME type, size, attachment id, and
authenticated UI URL in channel history. The selected runtime agent receives
only a compact channelId/attachmentId pointer in its current turn and later
session history. When it needs the contents, Runtime MCP authorizes and serves
them with runtime_channel_attachment_read; page code does not need to copy
bytes into text, page context, or agentTurnContext.
The read capability is channel-scoped. The agent must be a participant, and the
attachment must have been committed by its uploader in that exact channel.
Knowing an id or URL does not grant cross-channel or cross-tenant access.
deleteAttachment(id) removes the underlying upload and future agent reads
then fail closed, although the historical message can retain its display
metadata.
Browser downloads follow the same boundary. The uploader can read a pending or
standalone attachment. Another human can read it only after the uploader’s
exact metadata is durably committed in a message in a channel where that human
is a current participant. A known unauthorized attachment and an unknown
attachment produce the same GET/HEAD 404 behavior.
Value exports
ProjectionOptions supports id, filter, limit, offset, and
fallbackData. useChannelChat accepts either channelId or entityRef; set
createIfMissing only when explicitly creating an entity channel is intended.
When a Custom Page runs against a Workspace source, useProjection list and
document requests evaluate that exact Workspace candidate’s projection
authorization rules. Published projection rules are not used as a fallback.
The first read lazily activates the exact Workspace runtime scope before either
an inherited or newly added projection collection is opened. If activation is
unavailable, the HTTP error includes structured reason, workspaceId, and
recoveryAction details instead of an opaque internal-server response; follow
the returned Workspace recovery action and retry.
Pass agentId to route each user send to that existing app-agent participant
without changing the visible message. Set mentionAgent: true only when the
technical agent mention should also appear in the channel message.
Projection freshness and explicit retry
useCommand.execute() does not resolve immediately after the command response.
It first awaits every mounted projection query for the selected runtime source,
including filtered lists that did not contain a document before the command
created it. Keep the action disabled from isLoading while awaiting execute();
when the promise resolves, the new projection state is ready for the same render
cycle.
Build derived values from the documented response fields rather than the hook
response wrapper. For example, use projection.data as the dependency of a
memoized list:
To force or retry the current query explicitly, call the bound no-argument SWR mutator and await it:
This is the supported explicit revalidation path. Do not add a refreshKey or
other ad hoc option to useProjection; Workspace page validation diagnoses
unknown object-literal options instead of silently ignoring them. Validation
also checks named imports and re-exports against the actual SDK inventory, so an
invented useNavigation import fails immediately; the supported hook is
useCesNavigation. During ChangeSet validation and publication, these SDK
diagnostics block changed or page-resource-affected sources. Existing debt on an
unchanged, unaffected page is reported as a warning and does not block an
unrelated ChangeSet; syntax, imports, resources, and Design System component
contracts remain globally enforced.
Command errors
UseCommandResult.error is a CesCommandError. Its ordinary message is
bounded and safe to render in an operator-facing banner. Guard expressions,
command payloads, state snapshots, modeled values, and internal troubleshooting
text stay in protected execution traces and are not attached to the page error.
Both useCommand.execute() and Operate Extension
ctx.executeCommand(name, payload, options?) validate the server confirmation
before resolving: it must confirm success for the requested command and intent,
with consistent event evidence. A valid command may emit zero events. A 2xx
status alone is not a command confirmation. Missing, malformed, mismatched or
contradictory confirmations reject with COMMAND_CONFIRMATION_INVALID and
effectState: outcome_unknown; no success continuation or cache refresh runs.
Keep the form input and show the error until the action is confirmed.
Every command call sends a client-owned UUID as Idempotency-Key. If a
connection fails or an unclassified 5xx leaves the outcome unknown, repeating
the same payload through the mounted hook or extension reuses that intent. The runtime then
returns the original canonical receipt if the command committed, without a
second handler dispatch or event append. To continue the same intent after the
component or extension remounts, pass the error’s intent explicitly:
Do not change the payload while reusing an intent. The runtime rejects that as
COMMAND_IDEMPOTENCY_CONFLICT instead of risking a different effect under the
same identity.
The error exposes a stable code, an intentId, and an effectState:
rejected— validation, authorization, or a domain rule rejected the command; no command effect was appended.rolled_back— the request boundary closed before append and the runtime authoritatively confirmed no command effect. A later operator retry is a new intent.committed— the command committed, but a later client step such as projection revalidation failed. Do not resend the command.outcome_unknown— the response did not prove whether the command committed. Retry the identical payload with the sameintentId.
When execution started, executionId and correlationId are also available
for support lookup. Those identifiers are separate from message:
COMMAND_REVALIDATION_FAILED means the command completed but page data could
not be refreshed. Call the relevant projection’s await mutate() retry path;
do not immediately send the command a second time.
Type exports
The module also exports these public TypeScript contracts:
- data and actions:
ProjectionOptions,UseProjectionResult,UseDocumentResult,CommandPayload,CommandExecuteOptions,CesCommandError,CesCommandErrorCode,CesCommandEffectState,UseCommandResult,UseAdapterOperationResult - agent catalog:
PublicAgentSummary,UseAgentsResult - page chat:
AgentChatActiveConversation,AgentChatAgent,AgentChatConversation,AgentChatMessage,AgentChatMessageMetadata,AgentChatPushStatus,AgentChatSendMessageInput,AgentChatSendResult,AgentChatUploadOptions,UseAgentChatOptions,UseAgentChatResult - channel chat:
ChannelChatAgentOptions,ChannelChatBindingStatus,ChannelChatChannel,ChannelChatEntityRef,ChannelChatMessage,ChannelChatMessageMetadata,ChannelChatPushStatus,ChannelChatSendMessageInput,ChannelChatSendResult,ChannelChatSendStatus,ChannelChatUploadOptions,UseChannelChatOptions,UseChannelChatResult - page context and files:
CesAdapterCallResponse,CesAdapterOperationInput,CesFileAccess,CesUploadFileAccess,CesFileRef,CesFileRefInput,CesPageContext,CesPageSessionControl,CesPageSessionSurface,CesPageSessionUnsupportedReason,CesSessionActionState,CesSessionSignOutResult,CesUploadFileOptions,CesUser,ResolvedBinding,UseSessionResult
The hosted module is the runtime boundary. The public docs and Design MCP contract describe its types, but this work does not publish a standalone npm package or promise a local SDK development server.
External frontend and BFF equivalent
An external frontend cannot use @ces/app-sdk. Its server-side BFF should:
- fetch
/api/rest/<tenant-id>/<app-id>/openapi - list safe agent metadata with
GET /api/rest/<tenant-id>/<app-id>/agents - obtain a delegated OAuth token for the signed-in user
- create a fresh conversation with
POST /channels/dm - continue it through the returned channel id
The agent catalog uses normal Runtime REST authentication. Channel and user channel-view operations are user-delegated and reject API keys and agent service tokens. See BFF Pattern for the complete OAuth flow and Runtime API for the generated HTTP contract.