Skip to content

Handler signature

Every exported async function with a (ctx, args) signature becomes a manifest entry at build time; the host registers from the manifest, never by introspecting the code.

src/api/docs-editor.ts
import type { GatewayContext } from "../../.embabel/gateway";
/**
* Return the heading outline of a Google Doc. PREFER this over
* `gateway.docs.documentsGet` when you only need structure.
*/
export async function getOutline(
ctx: GatewayContext,
args: { documentId: string },
): Promise<{ revisionId: string; spans: Array<{ anchor: string; level: number; text: string }> }> {
const doc = await ctx.docs.documentsGet({ documentId: args.documentId });
return { revisionId: doc.revisionId ?? "", spans: /* walk doc.body.content */ [] };
}

The host’s manifest extractor walks the TypeScript AST, converts the args parameter type and the unwrapped return type to JSON Schema, and writes them to dist/manifest.json. The first JSDoc paragraph becomes the LLM-visible description — invest in JSDoc: it’s how the LLM picks the right method when multiple gateway surfaces overlap.

Authoring before sync — GenericGatewayContext

Section titled “Authoring before sync — GenericGatewayContext”

embabel-realm sync (which generates .embabel/gateway.d.ts with the host’s fully-typed GatewayContext) is still in flight. Until it lands, a realm whose handlers only need to call gateway ops — not the static types of their results — can type ctx as GenericGatewayContext from @embabel/runtime-types (a loose Record<string, Record<string, (args) => Promise<unknown>>>). The manifest extractor reads each handler’s args and return types, not ctx, so the typed LLM surface is identical either way.

A namespace function that only needs to call gateway ops can type ctx loosely:

src/api/weather.ts
import type { GenericGatewayContext } from "@embabel/runtime-types";
/** Current conditions for a city. */
export async function current(
ctx: GenericGatewayContext,
args: { city: string },
): Promise<unknown> {
return ctx.openWeather.getCurrent({ q: args.city });
}

Caveat: the Record index access trips noUncheckedIndexedAccess, so leave that flag off in tsconfig.json when using GenericGatewayContext (the generated GatewayContext has concrete properties and doesn’t need it). Once sync lands, swap GenericGatewayContext → GatewayContext for full result typing — nothing else changes. The same applies to a type method’s this.gateway.