Contract and use
CappClient is the application-facing coordinator. It discovers an existing owner, checks your application contract and exposes route/data/sync wrappers. It does not create native capabilities, authenticate a user or grant tool authority. The declarations below are generated from current SDK source with private members omitted; referenced contract types are supplied by the installed packages.
Runtime selection and one application boundary
Use one integration module and pass application and routePolicy deliberately. Do not create a different app identity for each component or copy another product's short name into a scoped connection.
| Option | Actual effect |
|---|---|
runtime | An explicit object pins the owner. Explicit null pins web fallback, even if window.capp exists. |
runtimeProvider | Resolves dynamically when no explicit runtime was supplied. Returning null falls back to window.capp; it is not an explicit denial. |
application | Declares requirements and supplies collection schemas; does not copy its routes into routePolicy. |
routePolicy | Supplies classification for the client; installed request transport still owns its compiled policy. |
baseUrl, fetchImpl, offlineHandlers | Control ordinary web fallback, not the native transport. |
getAssertion | An explicit assertion-provider seam, not a request to reveal a native signing key. |
capabilityTimeoutMs | Default 12,000 ms, with a 250 ms minimum; does not impose a deadline on calls made after discovery. |
Avoid creating a window watcher during server-side rendering. Use createCappClient({runtime: null}) in a deliberate web-only test rather than relying on whichever globals a test runner happens to expose.
Readiness and compatibility
descriptor() reports a snapshot; capability names may come from declared metadata as well as present bridge objects. ready() awaits the owner's current readiness promise when present, but this SDK suppresses rejection of that promise before evaluating the descriptor. It is not a health check and does not wait for every optional component to arrive later.
With no runtime, compatibility() returns web-fallback mode and does not fail required native capabilities. With a runtime, required missing/incompatible capabilities make assertCompatible() and ready() throw CAPP_RUNTIME_INCOMPATIBLE. Optional failures do not block the whole app; check the method/version needed for that feature.
has() is a presence convenience, not an assertion that every method exists. require() waits for a non-null exposed value, not for a service, account, GPU, wallet or model to become usable. In a non-window environment a missing capability fails immediately. For custom capability names, the SDK may return the owner runtime as the advertised object; do not invent a nested API from the name.
Use client.security.installIdentity() for the public install identity. In this alpha the generic security capability lookup is not a reliable typed substitute for that dedicated wrapper.
Observe an optional capability
This helper reports whether a Local AI connection entry point exists. It does not load a model or generate text. Call it after UI mounting and call the returned function on unmount.
import type { CappClient } from '@capp/app-sdk';
export function watchLocalConnection(
client: CappClient,
report: (available: boolean) => void,
): () => void {
return client.watchCapability('ai.local', (available, value) => {
const candidate = value as { connect?: unknown } | null | undefined;
report(available && typeof candidate?.connect === 'function');
});
}The watcher emits initially, then when the resolved object identity changes. It also polls while mounted. A property mutated on the same object, an expired session or a model becoming ready is not necessarily a new watcher event. Use the component's status/event contract for those transitions. Unsubscribing does not unload or destroy the shared capability.
Handle results without inventing success
Route requests return Response: check HTTP status and parse/validate the body separately. Data/sync/security wrappers return CappResult, which carries ok, optional value, receipt, source, error and raw.
normalizeCappResult() is an envelope convenience, not verification. It does not parse a Response body, authenticate a receipt or consistently unwrap every nested runtime result. In particular, normalizing undefined can produce ok: true without evidence of an operation. Some legacy optional data/sync methods can reach that path: check method support before acting and require the record/acknowledgement your workflow actually needs.
Keep raw for controlled diagnostics, not automatic logging of private payloads. Prefer record metadata for revision state, public auth state for a sign-in indicator, and an independently trusted server response for business effects.
Discovery errors and cleanup
| Result | Response by the application |
|---|---|
CAPP_RUNTIME_INCOMPATIBLE | Show the unsupported contract/version; do not bypass the check by relabelling your requirements. |
CAPP_CAPABILITY_UNAVAILABLE | No suitable owner in this environment. Keep the optional feature disabled. |
CAPP_CAPABILITY_TIMEOUT | The discovery deadline elapsed; offer an explicit later retry, not an endless loop. |
| Bridge exists but a method is absent | Treat that feature as unavailable; an SDK type is not proof of installed support. |
| View removed while a call is pending | Stop updating the detached UI. Use that component's scoped cancellation, where supported. |
A discovery timeout does not cancel a request already issued through a bridge. A new client object does not create another native engine, another user session or a second permission grant.
CappClientOptions
export type CappClientOptions = {
appId?: string;
application?: CappApplicationDefinition;
baseUrl?: string;
routePolicy?: CappRoutePolicyDocument;
runtime?: CappRuntimeBridge | null;
runtimeProvider?: () => CappRuntimeBridge | null;
fetchImpl?: typeof fetch;
offlineHandlers?: Record<string, OfflineRouteHandler>;
getAssertion?: (input: {
path: string;
method: string;
capability?: string;
bodySha256?: string;
}) => Promise<string> | string;
onlineState?: () => boolean;
capabilityTimeoutMs?: number;
strictWebRoutePolicy?: boolean;
};CappClient
export declare class CappClient {
readonly appId?: string;
readonly application?: CappApplicationDefinition;
readonly baseUrl?: string;
readonly routePolicy?: CappRoutePolicyDocument;
readonly fetchImpl: typeof fetch;
readonly offlineHandlers: Record<string, OfflineRouteHandler>;
readonly getAssertion?: CappClientOptions['getAssertion'];
readonly onlineState?: CappClientOptions['onlineState'];
readonly strictWebRoutePolicy: boolean;
readonly capabilityTimeoutMs: number;
readonly routes: CappRoutesClient;
readonly data: CappDataClient;
readonly sync: CappSyncClient;
readonly security: CappSecurityClient;
constructor(options?: CappClientOptions);
currentRuntime(): CappRuntimeBridge | null;
/**
* Return the host-attested Showcase demo descriptor when running inside an
* immutable `.capp-showcase` scenario. This is runtime context only: it does
* not grant capabilities or bypass route policy.
*/
showcaseDemo(): CappShowcaseRuntimeDemo | null;
isOnline(): boolean;
descriptor(): CappRuntimeDescriptor;
ready(): Promise<CappRuntimeDescriptor>;
compatibility(descriptor?: CappRuntimeDescriptor): CappCapabilityCompatibility & {
mode: 'capp-runtime' | 'web-fallback';
};
assertCompatible(descriptor?: CappRuntimeDescriptor): CappRuntimeDescriptor;
has(name: CappCapabilityName): boolean;
capability<K extends keyof CappKnownCapabilityMap>(name: K): CappKnownCapabilityMap[K] | undefined;
capability(name: CappCapabilityName): unknown;
require<K extends keyof CappKnownCapabilityMap>(name: K, timeoutMs?: number): Promise<CappKnownCapabilityMap[K]>;
require<T = unknown>(name: CappCapabilityName, timeoutMs?: number): Promise<T>;
watchCapability(name: CappCapabilityName, callback: (available: boolean, value: unknown, descriptor: CappRuntimeDescriptor) => void): () => void;
get auth(): CappAuthBridge | undefined;
get intent(): CappIntentBridge | undefined;
get ai(): {
local?: CappLocalAiBridge;
};
get context(): CappMcpContextBridge | undefined;
get guide(): unknown;
get services(): unknown;
get system(): unknown;
}createCappClient
export declare function createCappClient(options?: CappClientOptions): CappClient;CappApplicationDefinition
export type CappApplicationDefinition = {
protocol?: typeof CAPP_APPLICATION_DEFINITION_PROTOCOL | string;
appId: string;
requires?: Record<string, string>;
optional?: Record<string, string>;
routes?: readonly CappRoutePolicyEntry[];
data?: import('./data.js').CappDataContractDefinition;
metadata?: Record<string, unknown>;
};CappRuntimeDescriptor
export type CappRuntimeDescriptor = {
protocol: typeof CAPP_APP_SDK_PROTOCOL | string;
sdkVersion?: string;
runtimeProtocol?: string;
appId?: string | null;
ready: boolean;
phase?: string;
capabilities: readonly string[];
capabilityVersions?: Record<string, string>;
};CappCapabilityCompatibility
export type CappCapabilityCompatibility = {
ok: boolean;
missing: string[];
incompatible: Array<{
capability: string;
required: string;
actual?: string;
}>;
optionalUnavailable: string[];
};CappRuntimeBridge
export type CappRuntimeBridge = {
links?: CappLinksBridge;
ready?: Promise<unknown>;
/** Host-owned runtime descriptor. Showcase exposes immutable demo identity here without granting authority. */
runtime?: CappRuntimeSurfaceDescriptor;
sdk?: Partial<CappRuntimeDescriptor> & {
capabilities?: readonly string[];
};
auth?: CappAuthBridge;
sidecars?: CappShowcaseSidecarsBridge;
intent?: CappIntentBridge;
intents?: CappIntentBridge;
route?: CappRoutesBridge;
routes?: CappRoutesBridge;
data?: CappDataBridge;
sync?: CappSyncBridge;
services?: unknown;
system?: unknown;
security?: CappSecurityBridge;
guide?: unknown;
ai?: {
local?: CappLocalAiBridge;
context?: CappMcpContextBridge;
};
mcp?: {
context?: CappMcpContextBridge;
};
};CappKnownCapabilityMap
export type CappKnownCapabilityMap = {
links: CappLinksBridge;
auth: CappAuthBridge;
intent: CappIntentBridge;
routes: CappRoutesBridge;
data: CappDataBridge;
sync: CappSyncBridge;
security: CappSecurityBridge;
'ai.local': CappLocalAiBridge;
'mcp.context': CappMcpContextBridge;
guide: unknown;
services: unknown;
system: unknown;
};CappResult
export type CappResult<T = unknown> = {
ok: boolean;
value?: T;
receipt?: unknown;
source?: string;
error?: {
code?: string;
message: string;
status?: number;
detail?: unknown;
};
raw?: unknown;
};CappSdkError
export declare class CappSdkError extends Error {
readonly code: string;
readonly statusCode?: number;
readonly detail?: unknown;
constructor(code: string, message: string, statusCode?: number, detail?: unknown);
}defineCappApplication
export declare function defineCappApplication<T extends CappApplicationDefinition>(definition: T): T & {
protocol: typeof CAPP_APPLICATION_DEFINITION_PROTOCOL;
};normalizeCappResult
export declare function normalizeCappResult<T = unknown>(raw: unknown): CappResult<T>;