UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

102 lines (101 loc) • 7.67 kB
/** OpenAI request-body keys core owns; `extraBody` cannot override these. */ export declare const OPENAI_RESERVED: ReadonlySet<string>; /** Anthropic request-body keys core owns; `extraBody` cannot override these. (Note the asymmetry vs * OpenAI — e.g. `stop`/`stop_sequences` is NOT reserved here because the Anthropic brain never sets it.) */ export declare const ANTHROPIC_RESERVED: ReadonlySet<string>; /** * Open Responses request-body keys core owns; `extraBody` cannot override these (design/163). * * The last three are NOT keys the adapter sets — they are keys it must be IMPOSSIBLE to set. The * Open Responses lane is stateless by contract (this engine is the transcript authority), and * `extraBody` is the only other path to the request body, so `store` / `previous_response_id` / * `conversation` are reserved purely to close it. A deployment that sets one gets a stripped key on the * wire and the drop announced, never a silently server-linked turn. */ export declare const RESPONSES_RESERVED: ReadonlySet<string>; /** * Merge `extraBody` into a brain's already-built request `body`, dropping any key the brain owns so core * always wins. `body` is a NEW object (passthrough keys first, then core keys) when `extraBody` is set; the * exact `body` reference (no allocation, byte-identical serialization) when it isn't — so an existing * deployment with no `extraBody` is unaffected. * * `dropped` is the keys of `extraBody` that were stripped, in `extraBody`'s insertion order, names only (a * value never leaves this function except inside `body`). It is a property of the same walk that strips, so * what is said and what is dropped cannot disagree; empty when nothing was stripped. */ export declare function applyExtraBody(body: Record<string, unknown>, extraBody: Record<string, unknown> | undefined, reserved: ReadonlySet<string>): { body: Record<string, unknown>; dropped: readonly string[]; }; /** * #343 — the shared USER-HEADER merge layer (`model.headers` → construction `config.headers` → * per-call `options.headers`, later bag wins), CASE-FOLD deduplicated: HTTP header field names are * case-insensitive (RFC 9110), but a plain-object spread keys by exact spelling, so an `X-Tenant` * in one bag and an `x-tenant` in another both reach the wire, where fetch's Headers folds them * into one comma-joined value ("a, b") — neither writer's value, the later layer's documented * override silently defeated. A later bag's entry replaces an earlier case-variant instead; the * WINNER'S spelling and value survive (a single-spelling config is byte-identical on the wire). * * EXEMPT: the credential headers ({@link isCredentialHeaderName}, the engine's one reading of which * header carries a credential: `authorization`, `x-api-key`, `api-key`, `cookie`, …, any case) pass * through with plain spread semantics (same-spelling override only, no case-fold dedup), so that the * credential enforcement (`applyRouteCredentialHeaders`, route-adjudicator.ts) stays the ONE authority * over credential spelling and this layer never becomes a second, subtly different one. * * The exemption is NOT what keeps a header-only `ANTHROPIC_AUTH_TOKEN` alive: dedup keeps the * winner's spelling, a lone `Authorization` has nothing to dedup against, and the credential * enforcement already deletes every spelling present when a per-model credential wins. What the * exemption really costs is measured, not reasoned: * `mergeHeaders({Authorization:A},{authorization:B})` keeps BOTH, and the platform `Headers` fold * sends `authorization: A, B`. * * STATED RESIDUAL (deliberately not fixed here), NARROWED by the credential enforcement: where a * PER-MODEL credential wins (an options- or entry-bag carrier), every carrier is stripped and only * the winning bag's re-asserted, so a cross-layer double spelling now folds to the winner. What * remains is the DEPLOYMENT-CONFIG arm — its bytes ride untouched by design — so a config bag that * itself spells the SAME auth carrier two ways still ships both, comma-folded. Held, not denied, on * severity: no server accepts a comma-joined credential, so that failure is a LOUD 401 attributable * to the misconfiguration, while the non-auth case this function exists for produces a silently * WRONG value (`X-Tenant: a, b`). Tightening it changes which credential reaches the wire, so it * belongs in a window that discloses an auth-face behavior change. */ /** * #343 — assign a STRUCTURAL locked header under its canonical lowercase name, deleting every * case-variant spelling first. The brains hard-lock `content-type` / `anthropic-version` AFTER the * user-bag merge precisely so they "can NEVER be overridden" (council design/40) — and without the * delete a user bag carrying `Content-Type: text/plain` survives BESIDE the lowercase lock, the * platform `Headers` fold turning the pair into `text/plain, application/json` on the wire: a lock * that decides nothing. WHICH credential headers ride is the jurisdiction of `applyRouteCredentialHeaders` * (route-adjudicator.ts); the key header a brain writes after it (`authorization` / `x-api-key`, from the * apiKey knob) is a lock like any other — one value per name, whatever spelling a bag carried. */ export declare function lockHeader(headers: Record<string, string>, lowerName: string, value: string): void; /** * #343 (review r4) — case-fold READ-AND-CLAIM for an AUGMENTABLE structural header (`anthropic-beta`): * returns the current value under whatever spelling the user bag carried and deletes that spelling, * so the caller's canonical lowercase write REPLACES it instead of duplicating beside it (the * read-modify-write used to key the read by the exact lowercase name and miss `Anthropic-Beta`, * losing the user's betas from the merge AND double-sending the header). */ export declare function takeHeaderCasefold(headers: Record<string, string>, lowerName: string): string | undefined; /** A caller's header bag read ONCE, into a plain copy of its own enumerable entries — what a request build hands both * the route law (which credential rides, and its identity) and the merge (what goes on the wire), so a bag whose getters * or keys answer differently on a second read cannot be judged as one bag and sent as another. */ export declare function headerBagOnce(bag: Readonly<Record<string, string>> | undefined): Record<string, string> | undefined; export declare function mergeHeaders(...bags: Array<Record<string, string> | undefined>): Record<string, string>; /** * The output-cap key(s) each lane's wire form uses. A lane's set is exactly the keys THAT lane's * endpoint reads — a stray cap key belonging to another wire form is inert there and must not be * mistaken for a bound this request carries. */ export declare const OUTPUT_CAP_KEYS: Readonly<Record<"openai" | "anthropic" | "responses", readonly string[]>>; /** * The per-request output cap a FINAL (post-`extraBody`) request body actually carries — the SMALLEST * of the lane's cap keys that is present and usable, or `undefined` when none is. * * Read off the merged body rather than off the knob the adapter set, because those are not the same * thing: `extraBody` can contribute a cap under a spelling the adapter did not choose, and a * consumer that treats the adapter's own number as "the cap on the wire" would be wrong about a * request that carries two. Smallest wins so a consumer reasoning about the bound stays conservative. */ export declare function effectiveOutputCap(body: Record<string, unknown>, keys: readonly string[]): number | undefined;