Skip to content

Defined in: packages/core/src/transport/backend-transport.ts:43

Backend-proxied transport — RECOMMENDED for production.

POSTs { providerId, request } to YOUR endpoint. The backend resolves the API key server-side, calls the vendor, runs the vendor parser server-side, and streams back already-normalized AparteStreamEvents as NDJSON (one JSON object per line). The key never reaches the browser and — unlike a raw proxy — the client stays fully vendor-agnostic (it never runs the vendor’s parseStream, so switching the server-side provider needs no client change). Non-streaming requests get a JSON { text } reply.

This is NOT the Vercel AI SDK Data Stream Protocol; the wire format is a plain NDJSON of AparteStreamEvent (see parseAparteEventStream).

The matching /api/chat handler ships as a real, framework-free function — createAparteChatHandler (Web fetch API, same @aparte/* adapters run server-side, key held server-side). Drop it into a Next.js route handler, Deno, Bun, or a Worker rather than hand-rolling one.

ts
new AparteBackendTransport(options): AparteBackendTransport;

Defined in: packages/core/src/transport/backend-transport.ts:44

ParameterType
optionsBackendTransportOptions

AparteBackendTransport

ts
chat(
   _provider, 
   request, 
   _auth, 
ctx): Promise<AparteChatResponse>;

Defined in: packages/core/src/transport/backend-transport.ts:46

ParameterType
_providerAparteAIProvider
requestAparteChatRequest
_authstring | Record<string, string> | undefined
ctxAparteTransportContext

Promise<AparteChatResponse>

AparteTransport.chat


Defined in: packages/core/src/transport/direct-transport.ts:32

Browser-direct transport (BYOK / local / prototyping) — the DEFAULT.

Calls the vendor endpoint straight from the browser, injecting the resolved key via the adapter’s authHeaders. This is the pre-refactor behaviour and is only safe when the user brings their own key or the model runs locally. Production apps that must keep a key server-side should use AparteBackendTransport.

When a real key is sent straight from the browser and the transport was not flagged { byok: true }, a one-time console.warn names the exposure — the default should be loud, not silent. Local providers (no key) never warn.

Legacy providers (still exposing chat() rather than the adapter surface) are delegated to untouched, so the migration can proceed provider-by-provider.

ts
new AparteDirectTransport(options?): AparteDirectTransport;

Defined in: packages/core/src/transport/direct-transport.ts:36

ParameterType
optionsDirectTransportOptions

AparteDirectTransport

ts
chat(
   provider, 
   request, 
   auth, 
ctx): Promise<AparteChatResponse>;

Defined in: packages/core/src/transport/direct-transport.ts:40

ParameterType
providerAparteAIProvider
requestAparteChatRequest
authstring | Record<string, string> | undefined
ctxAparteTransportContext

Promise<AparteChatResponse>

AparteTransport.chat


ts
function createAparteAgentHandler(options): (req) => Promise<Response>;

Defined in: packages/core/src/transport/agent-handler.ts:123

Build a framework-free handler that runs the agent loop on your server — next to your tools — and streams it to createAgentStreamRunner in the browser.

The browser’s AparteClient keeps doing what it does: rows, Stop, the approval panel and your approval policy. A call that needs a person, or a tool that runs in the browser, SUSPENDS the run: the stream ends on run-suspended with a signed ticket, the page asks, and a second request carries the ticket and the answer. Nothing is stored here — the paused call lives in the conversation the app saves — so the person can leave and answer days later.

The wire is NDJSON: one StreamRunEvent per line, the suspension line with its ticket, and a run-error line (sanitised) when the run fails after the stream began.

ts
// app/api/agent/route.ts (Next.js)
import { createAparteAgentHandler } from '@aparte/core';
export const POST = createAparteAgentHandler({
  providers: { openai: createOpenAICompatProvider(presets.OPENAI) },
  resolveKey: () => process.env.OPENAI_KEY,
  authorize: (req) => isSignedIn(req),
  secret: process.env.AGENT_SECRET!,
  tools: [{ tool: readFile, handler: readFileHandler }],
});
ParameterType
optionsAparteAgentHandlerOptions

(req) => Promise<Response>


ts
function createAparteChatHandler(options): (req) => Promise<Response>;

Defined in: packages/core/src/transport/backend-handler.ts:65

Build a framework-free /api/chat handler for AparteBackendTransport — the server counterpart that keeps the key off the client.

The returned handler speaks the Web fetch API (Request → Response), so it drops into a Next.js route handler, Deno, Bun, or a Cloudflare Worker unchanged. It reads { providerId, request }, runs the matching format adapter server-side (buildRequest → auth → vendor fetch → parseStream), and streams back normalized AparteStreamEvents as NDJSON — the exact wire format AparteBackendTransport expects. The vendor key is injected here and never travels to the browser.

ts
// app/api/chat/route.ts (Next.js)
import { createAparteChatHandler } from '@aparte/core';
import { createOpenAICompatProvider, presets } from '@aparte/provider-openai-compat';
export const POST = createAparteChatHandler({
  providers: { openai: createOpenAICompatProvider(presets.OPENAI) },
  resolveKey: (id) => process.env[`${id.toUpperCase()}_KEY`],
  // Required: this route spends your key. Your own session check goes here.
  authorize: async (req) => Boolean(await getSession(req)),
});
ParameterType
optionsAparteChatHandlerOptions

(req) => Promise<Response>


ts
function isFormatAdapter(p): p is AparteAIProvider & Required<Pick<AparteFormatAdapter, "defaultEndpoint" | "buildRequest" | "parseStream">>;

Defined in: packages/core/src/transport/types.ts:67

True once a provider has been refactored to the format-adapter surface.

ParameterType
pAparteAIProvider

p is AparteAIProvider & Required<Pick<AparteFormatAdapter, “defaultEndpoint” | “buildRequest” | “parseStream”>>


Defined in: packages/core/src/transport/agent-handler.ts:13

A server tool: its definition (what the model sees) and its handler (what runs here).

ts
tool: AparteTool;

Defined in: packages/core/src/transport/agent-handler.ts:14

ts
handler: AparteToolHandler;

Defined in: packages/core/src/transport/agent-handler.ts:15

ts
optional acceptsApprovalPayload?: boolean;

Defined in: packages/core/src/transport/agent-handler.ts:21

Accept the payload an approval may carry — data merged into the call’s input. Off by default: from a browser, a payload rewrites what the model chose to call with, so only a tool whose approval UI edits the arguments on purpose opts in.


Defined in: packages/core/src/transport/agent-handler.ts:24

ts
providers: Record<string, AparteAIProvider>;

Defined in: packages/core/src/transport/agent-handler.ts:30

Your server-side format adapters, keyed by the providerId the client sends. A page that registers no provider sends none, and the handler runs its only one; with several, the page names one.

ts
optional defaultModel?: string;

Defined in: packages/core/src/transport/agent-handler.ts:35

The model a turn runs on when the page selected none, which is the case when the page registers no provider of its own. A model the page selects wins.

ts
authorize: (req) => boolean | Response | Promise<boolean | Response>;

Defined in: packages/core/src/transport/agent-handler.ts:41

Gate run on EVERY request before any work. Required, for the reason createAparteChatHandler gives: this endpoint spends your key and runs your tools. Return false for 401, a Response to answer yourself, true to proceed.

ParameterType
reqRequest

boolean | Response | Promise<boolean | Response>

ts
secret: string | readonly string[];

Defined in: packages/core/src/transport/agent-handler.ts:47

Signs the pending calls of a suspended run, so a resume runs only calls this server emitted. Required, at least 32 characters. An array rotates: the first signs, all verify.

ts
optional tools?: readonly AparteAgentTool[];

Defined in: packages/core/src/transport/agent-handler.ts:49

The tools that run here. tool.needsApproval is the gate.

ts
optional resolveKey?: (providerId) => string | Promise<string | undefined> | undefined;

Defined in: packages/core/src/transport/agent-handler.ts:51

The vendor key for a providerId, from env or a secret store.

ParameterType
providerIdstring

string | Promise<string | undefined> | undefined

ts
optional approvalPolicy?: AparteApprovalPolicy;

Defined in: packages/core/src/transport/agent-handler.ts:53

Server-side rules, per call: allow, ask, deny (refused without asking).

ts
optional clientTools?: boolean | readonly string[];

Defined in: packages/core/src/transport/agent-handler.ts:59

Accept tools the browser declares and runs itself (ask_user). true by default; false refuses them; a list allows those names only. A browser tool named like a server tool is always refused.

ts
optional prepareRequest?: (request, req) => 
  | AparteChatRequest
| Promise<AparteChatRequest>;

Defined in: packages/core/src/transport/agent-handler.ts:64

Rewrite the request before the run — impose your system prompt, drop messages. The resume checks read what it returns: the loop runs on that, not on what the browser sent.

ParameterType
requestAparteChatRequest
reqRequest

| AparteChatRequest | Promise<AparteChatRequest>

ts
optional maxTurns?: number;

Defined in: packages/core/src/transport/agent-handler.ts:66

How many model turns one run may take before it stops.

ts
10
ts
optional toolTimeoutMs?: number;

Defined in: packages/core/src/transport/agent-handler.ts:68

How long one tool call may run, in milliseconds, before it is abandoned.

ts
300000 (5 minutes)
ts
optional ticketMaxAgeMs?: number;

Defined in: packages/core/src/transport/agent-handler.ts:70

How long a suspended run can wait for its answer.

ts
7 days
ts
optional onTicket?: (ticketId) => boolean | Promise<boolean>;

Defined in: packages/core/src/transport/agent-handler.ts:76

Called with a ticket’s id before a resume. Return false to refuse it (409) — the hook for a host that can remember used tickets and make them single-use. A server that stores nothing cannot tell a replay from a first use.

ParameterType
ticketIdstring

boolean | Promise<boolean>

ts
optional transport?: AparteTransport;

Defined in: packages/core/src/transport/agent-handler.ts:78

How the vendor is called.

an AparteDirectTransport (the key is on this server by design).

ts
optional crypto?: SubtleCrypto;

Defined in: packages/core/src/transport/agent-handler.ts:80

Web Crypto.

globalThis.crypto.subtle; on Node 18, pass (await import('node:crypto')).webcrypto.subtle.


Defined in: packages/core/src/transport/agent-handler.ts:84

What createAgentStreamRunner POSTs.

ts
v: 1;

Defined in: packages/core/src/transport/agent-handler.ts:85

ts
optional providerId?: string;

Defined in: packages/core/src/transport/agent-handler.ts:87

Absent when the page registered no provider: the handler runs its only one.

ts
messageId: string;

Defined in: packages/core/src/transport/agent-handler.ts:88

ts
request: AparteChatRequest;

Defined in: packages/core/src/transport/agent-handler.ts:89

ts
optional clientTools?: AparteTool[];

Defined in: packages/core/src/transport/agent-handler.ts:91

Definitions of the tools the browser runs; the server marks them external.

ts
optional resume?: {
  ticket: string;
  outcomes: Record<string, StreamDeferredOutcome>;
};

Defined in: packages/core/src/transport/agent-handler.ts:92

ts
ticket: string;
ts
outcomes: Record<string, StreamDeferredOutcome>;

Defined in: packages/core/src/transport/backend-handler.ts:5

ts
providers: Record<string, AparteAIProvider>;

Defined in: packages/core/src/transport/backend-handler.ts:11

Your server-side format adapters, keyed by the providerId the client sends. The same adapters as the browser (@aparte/provider-*), running here so the vendor key never leaves the server.

ts
authorize: (req) => boolean | Response | Promise<boolean | Response>;

Defined in: packages/core/src/transport/backend-handler.ts:28

Gate run on EVERY request before any work. Required.

This endpoint spends your server-held vendor key, so leaving it open means anyone who finds the URL spends your money. It used to be optional, and both the JSDoc example here and the primary snippet in the docs omitted it — so the copy-paste path was the unauthenticated one. Making it required moves that decision from “did you notice?” to a compile error.

Return false to reject with 401, a Response to reject with your own status/body (e.g. 403 + a message), or true to proceed. Reads cookies/headers via req.

A deliberately open endpoint is still possible — authorize: () => true — but now it is a line someone wrote on purpose.

ParameterType
reqRequest

boolean | Response | Promise<boolean | Response>

ts
optional resolveKey?: (providerId) => string | Promise<string | undefined> | undefined;

Defined in: packages/core/src/transport/backend-handler.ts:33

Resolve the vendor API key for a providerId — from env / a secret store. Runs server-side only. Return undefined for keyless/local providers.

ParameterType
providerIdstring

string | Promise<string | undefined> | undefined

ts
optional fetchImpl?: {
  (input, init?): Promise<Response>;
  (input, init?): Promise<Response>;
};

Defined in: packages/core/src/transport/backend-handler.ts:38

The fetch used to call the vendor. Defaults to the global fetch (Node 18+, Deno, Bun, Workers). Override in tests or to add a proxy.

ts
(input, init?): Promise<Response>;

MDN Reference

ParameterType
inputRequestInfo | URL
init?RequestInit

Promise<Response>

ts
(input, init?): Promise<Response>;

MDN Reference

ParameterType
inputstring | Request | URL
init?RequestInit

Promise<Response>


Defined in: packages/core/src/transport/backend-transport.ts:7

ts
endpoint: string;

Defined in: packages/core/src/transport/backend-transport.ts:9

Your backend chat endpoint, e.g. /api/chat.

ts
optional headers?: Record<string, string>;

Defined in: packages/core/src/transport/backend-transport.ts:16

Extra headers to send with each request. A session cookie rides along automatically only for a SAME-ORIGIN endpoint — the request uses fetch’s default credentials: 'same-origin', so a cross-origin endpoint sends no cookie; authenticate it with a header here instead.

ts
optional buildBody?: (request, providerId) => unknown;

Defined in: packages/core/src/transport/backend-transport.ts:21

Override how the request is serialised to your backend. Default body is { providerId, request }. Return any JSON-serialisable value.

ParameterType
requestAparteChatRequest
providerIdstring

unknown


Defined in: packages/core/src/transport/direct-transport.ts:7

Options for AparteDirectTransport.

ts
optional byok?: boolean;

Defined in: packages/core/src/transport/direct-transport.ts:14

Set when the key is the end-user’s own (BYOK) or the model runs locally — i.e. you intend the credential to live in the browser. This silences the one-time insecure-key warning. Leave it unset for keys that must stay server-side, and use AparteBackendTransport for those instead.


Defined in: packages/core/src/transport/types.ts:21

A vendor HTTP request built from an Aparte request — auth is injected separately.

ts
path: string;

Defined in: packages/core/src/transport/types.ts:23

Appended to the transport’s endpoint, e.g. /chat/completions.

ts
body: unknown;

Defined in: packages/core/src/transport/types.ts:25

Vendor-shaped JSON body.

ts
optional headers?: Record<string, string>;

Defined in: packages/core/src/transport/types.ts:27

Content headers (NOT auth — the transport adds auth).


Defined in: packages/core/src/transport/types.ts:31

The vendor-format concern, with no transport/auth coupling.

ts
id: string;

Defined in: packages/core/src/transport/types.ts:32

ts
defaultEndpoint: string;

Defined in: packages/core/src/transport/types.ts:34

Base URL for browser-direct calls (overridable per request via config).

ts
buildRequest(request): AparteVendorRequest;

Defined in: packages/core/src/transport/types.ts:36

Map an Aparte request to the vendor HTTP request (without auth).

ParameterType
requestAparteChatRequest

AparteVendorRequest

ts
optional authHeaders(key): Record<string, string>;

Defined in: packages/core/src/transport/types.ts:38

Vendor auth headers for a resolved key (browser-direct only). Most vendors.

ParameterType
keystring

Record<string, string>

ts
optional authQuery(key): Record<string, string>;

Defined in: packages/core/src/transport/types.ts:40

Vendor auth as URL query params for a resolved key (e.g. Gemini ?key=).

ParameterType
keystring

Record<string, string>

ts
parseStream(body): ReadableStream<AparteStreamEvent>;

Defined in: packages/core/src/transport/types.ts:42

Parse a streaming vendor response body into unified events.

ParameterType
bodyReadableStream<Uint8Array<ArrayBufferLike>>

ReadableStream<AparteStreamEvent>

ts
optional parseText(json): string;

Defined in: packages/core/src/transport/types.ts:44

Extract text from a non-streaming vendor JSON response.

ParameterType
jsonunknown

string


Defined in: packages/core/src/transport/types.ts:47

ts
providerId: string;

Defined in: packages/core/src/transport/types.ts:48

ts
optional signal?: AbortSignal;

Defined in: packages/core/src/transport/types.ts:49


Defined in: packages/core/src/transport/types.ts:57

Decides where a chat request goes and how auth is handled. Given a provider (adapter or legacy), the request, and the already-resolved auth, it returns unified events (streaming) or a string (non-streaming).

ts
chat(
   provider, 
   request, 
   auth, 
ctx): Promise<AparteChatResponse>;

Defined in: packages/core/src/transport/types.ts:58

ParameterType
providerAparteAIProvider
requestAparteChatRequest
authstring | Record<string, string> | undefined
ctxAparteTransportContext

Promise<AparteChatResponse>