/** * createVendoClient — typed fetch/SSE bindings for every wire route (09 §3). * Exposed for non-React consumers; every hook rides this. * * The interface is the coordination artifact between lanes; the * implementation lives in client-impl.ts (lane A). */ import { type AccessLevel, type AppDocument, type AppGrantRecord, type AppId, type ApprovalDecision, type ApprovalId, type ApprovalRequest, type AuditEvent, type AutomationId, type GrantId, type Json, type PermissionGrant, type RunId, type ThreadId, type ToolOutcome } from "../core/index.js"; import type { UIMessage } from "ai"; import type { ApprovalResolution, AutomationEntry, ConnectableToolkit, ConnectionAccount, EnableResult, InitiatedConnection, PlacementEntry, RunPlan, RunRecord, RunStatus, SlotEntry, Thread, ThreadSummary, UploadedFile, VendoStatus } from "../core/index.js"; import type { AppListRow, EditResult, OpenSurface, PendingSurface, VersionEntry } from "../core/apps/index.js"; export interface VendoClientConfig { /** Wire mount point. Default "/api/vendo". */ baseUrl?: string; headers?: Record; } export interface VendoClient { readonly baseUrl: string; readonly headers: Record; threads: { /** POST /threads — one conversational turn; the ai-SDK UI message stream (SSE) Response. */ stream(input: { threadId?: ThreadId; message: UIMessage; }): Promise; list(): Promise; get(id: ThreadId): Promise; delete(id: ThreadId): Promise; /** POST /threads/warm — prime the provider's prompt cache so the first * real message reads a warm prefix. Best-effort; fire when the chat * surface opens and ignore failures. */ warm(): Promise; }; /** The signed-in user's own files. A file put here outlives the conversation * it was shared in, so the message that follows carries only the reference. */ files: { /** POST /files — the file's raw bytes under its own media type, never * multipart. Fetch-only, and deliberately without a progress callback: the * door caps an upload at 5 MiB, which is not long enough to be news. */ upload(file: File): Promise; }; approvals: { pending(): Promise; /** Batch-capable: POST /approvals/decide { ids, decision }. `options.grantSetId` (additive) names the grant SET the ids settle so the decided announcement can resume a thread parked on the set from ANY surface — it never rides the wire. */ decide(ids: ApprovalId | ApprovalId[], decision: ApprovalDecision, options?: { grantSetId?: string; }): Promise; /** Existing-agents — GET /approvals/:id, the per-approval state `` polls (pending/executed/declined/expired). */ get(id: ApprovalId): Promise; }; grants: { list(): Promise; revoke(id: GrantId): Promise; }; /** 04-actions §3 — per-principal connected accounts (Composio broker). */ connections: { list(): Promise; /** POST /connections/initiate — returns the broker's OAuth redirect URL. */ initiate(input: { toolkit: string; connector?: string; callbackUrl?: string; }): Promise; /** GET /connections/:id — poll while the user completes the redirect. */ status(id: string, connector?: string): Promise; disconnect(id: string, connector?: string): Promise; /** GET /connections/catalog — the host-level connectable toolkits; feeds the connect dock when no explicit `connectors` prop is passed. */ catalog(): Promise; }; apps: { list(): Promise; create(input: { prompt: string; }): Promise; get(id: AppId): Promise; delete(id: AppId): Promise; open(id: AppId): Promise; /** Existing-agents polish — the embed's build-window poll: with `pending: true` a not-yet-servable app answers `{ kind: "pending" }` over HTTP 200 instead of the contracted 404, so the poll never logs browser console errors while the build streams. */ open(id: AppId, options: { pending: true; }): Promise; call(id: AppId, ref: string, args: Json): Promise; edit(id: AppId, instruction: string): Promise; history(id: AppId): Promise; exportApp(id: AppId): Promise; importApp(bytes: Uint8Array): Promise; fork(id: AppId): Promise; /** * Build contract §9.2 — the ✦ share toggle's transport. `grants` reads the * app's grant list, the caller's own level, AND the caller's memberships * (projected off the ctx), so ONE round trip tells the menu which tenant to * name and whether the share is already on. */ grants(id: AppId): Promise<{ level: AccessLevel | null; grants: AppGrantRecord[]; orgs: { org: string; display?: string; }[]; }>; share(id: AppId, principal: string, level: AccessLevel): Promise<{ grants: AppGrantRecord[]; }>; unshare(id: AppId, principal: string): Promise<{ grants: AppGrantRecord[]; }>; /** POST /apps/:id/reseed — rebuild the remix against the host's current * version of the component (06 §8) by replaying EVERY wish the seed * recorded, oldest first. A wish the new version cannot take is kept and * reported (`seed.unapplied`), never dropped. */ reseed(id: AppId): Promise; /** * POST /apps/seed — the ✦ gesture (06 §8). There are no bare forks: the * gesture collects the `instruction` first, and the fork plus that first edit * are ONE operation whose answer is an ordinary screen app carrying the * remix's provenance. */ seedFrom(input: { component: string; slot?: string; instruction: string; }): Promise; /** * POST /apps/:id/props — the COURIER. The live serializable props the host's * page is passing the component this remix stands in for, shipped on mount * and again whenever they change. * * A ported screen renders FROM its props, and they exist in no source it * could read, so this call is the only way the page's own state reaches the * remix; without it the server paints the remix on the values `vendo sync` * captured and it shows that number forever. Provenance, not a content edit: * it mints no version, so calling it on every real change is the intent. * * The server keeps only the props the captured baseline declares. */ courierProps(id: AppId, props: Record): Promise; /** * `GET /apps/:id/bundle/:entry` — where a SEALED bundle's document lives. * * A url rather than a fetch, because the browser is what asks: it is an * iframe's `src`, so the response's own CSP header (`default-src 'none'`, * `frame-ancestors 'self'`) is what governs the document — which is exactly * why the bundle is not inlined as `srcdoc`. `entry` is the content hash, so * the url never goes stale. */ bundleUrl(id: AppId, entry: string): string; /** * Placement (2026-08-05) — "show this app in that slot". `POST * /apps/:id/place`; one app per slot, so the answer names whatever the * write displaced (`evicted`). */ place(id: AppId, slot: string): Promise<{ evicted?: string; }>; /** `POST /apps/:id/unplace` — clear the slot, if this app still holds it. */ unplace(id: AppId, slot: string): Promise; /** `GET /apps/placements` — what is in the caller's slots. Pass the slots * actually mounted so one request answers the whole page. */ placements(slots?: readonly string[]): Promise; }; automations: { list(): Promise; /** Arm/disarm/preview ONE record — an automation is decided on its own. */ enable(id: AutomationId): Promise; disable(id: AutomationId): Promise; dryRun(id: AutomationId): Promise; }; runs: { list(filter?: { automationId?: AutomationId; owner?: string; agent?: string; status?: RunStatus; cursor?: string; }): Promise<{ runs: RunRecord[]; cursor?: string; }>; get(id: RunId): Promise; stop(id: RunId): Promise; /** POST /runs/:id/rerun — run it again: a FRESH run of the same automation * on the same triggering event. The remedy a failed run leaves behind (07 * §1 `runs.rerun`); answers with the new run's id. */ rerun(id: RunId): Promise; }; activity: { /** GET /activity — self-scoped audit events; cursor = the id of the last seen event. */ list(params?: { cursor?: string; limit?: number; }): Promise; }; /** The slot registry — where the "Add to…" picker's destinations come from. * A slot id lives in the host's markup and nowhere else, so a mounted * `VendoSlot` is the only thing that can say one exists. */ slots: { /** GET /slots — every reported destination, newest first. */ list(): Promise; /** POST /slots — mounted slots saying they exist; batched, idempotent. */ report(slots: readonly { id: string; label: string; description?: string; }[]): Promise; }; status(): Promise; } export { APPROVALS_DECIDED_EVENT, createVendoClient, type ApprovalsDecidedDetail, } from "./client-impl.js";