agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
104 lines (103 loc) • 5.47 kB
TypeScript
/**
* adapters/hosting/foundryResponses — an agent behind Microsoft Foundry's
* hosted-agent contract.
*
* ── The split, and why it is here ────────────────────────────────────────────
* Two different things are needed to serve a Foundry hosted agent, and only one
* of them is Microsoft's:
*
* • The **Responses protocol** — `input` items, a `response` object, the
* named streaming lifecycle. That is a protocol several runtimes speak, so
* it lives in `responsesWire.ts` with no vendor in it.
* • The **hosting contract** — port 8088, `POST /responses`, a `HEAD` probe
* on the same path, `GET /readiness` answering `{"status":"healthy"}`, and
* `agent_session_id` as a session alias. That IS this runtime's contract,
* the same way `/invocations` and `X-Amzn-…` are another's, and it is what
* this file supplies.
*
* So this file is a configuration, not an implementation. Everything hard —
* draining on close, aborting when the caller hangs up, failing a handler that
* throws or answers nothing — is `httpHost`'s, shared with every other adapter
* rather than reimplemented here where the two would drift.
*
* ── What it does NOT do ──────────────────────────────────────────────────────
* **Workflow Visualizer topology is not provided.** The Inspector can invoke
* this host and render its answer; it does not learn that the answer came from
* four composed agents, because nothing in the Responses protocol carries that
* and inventing a channel for it would be claiming a compatibility this has
* never demonstrated. An agent's internal structure is readable from
* agentfootprint's own recorders.
*
* Everything the protocol does not carry — tool calls, image and file input,
* function-call output, structured output — is refused by name. See
* `responsesWire.ts`.
*
* This is an **inbound hosting adapter**: it is the door callers arrive at. It
* is not a model provider, and it has nothing to do with which model the agent
* calls — for Foundry models, that is `openai()` with the endpoint's base URL.
*
* @example An agent behind the Inspector
* import { foundryResponsesHost, memorySessions, standingAgent } from 'agentfootprint/hosting';
*
* const handle = await standingAgent({
* agent,
* sessions: memorySessions(),
* host: foundryResponsesHost(),
* });
* process.on('SIGTERM', () => void handle.close());
*/
/// <reference types="node" />
import type { IncomingMessage, Server, ServerResponse } from 'node:http';
import { type CrossSiteOptions } from '../../hosting/doorGuard.js';
import { type HttpHost } from '../../hosting/httpHost.js';
/** The port a Foundry hosted agent is expected on. */
export declare const DEFAULT_FOUNDRY_PORT = 8088;
/** The path that takes a turn. */
export declare const FOUNDRY_INVOKE_PATH = "/responses";
/** The path a Foundry hosted agent answers a readiness probe on. */
export declare const FOUNDRY_READINESS_PATH = "/readiness";
/**
* Session aliases this contract accepts, in precedence order.
*
* `conversation` is the protocol's own; `agent_session_id` is this runtime's;
* `session_id` is the spelling several clients send anyway. First one present
* wins, so a caller using any of the three reaches the same session.
*/
export declare const FOUNDRY_SESSION_FIELDS: readonly string[];
/**
* Options for {@link foundryResponsesHost}.
*
* The three cross-site fields are the door guard every `httpHost` keeps, with
* this adapter's defaults INVERTED — off unless set: the platform's front door
* is the only way to this port and demands a credential no web page holds, so
* the browser rules would protect nothing here, and a local inspector talking
* to the container is not something this library should start refusing (see
* `platformFrontedDoor` in `src/hosting/doorGuard.ts`). Set any of them to
* enforce it. On a LOOPBACK `hostname` every plain-host default applies
* instead; with the rules off the host says so once at boot. The session-id
* bound applies regardless.
*/
export interface FoundryResponsesHostOptions extends CrossSiteOptions {
/** Port to bind. Default {@link DEFAULT_FOUNDRY_PORT}. Pass `0` for an ephemeral test port. */
readonly port?: number;
/** Interface to bind. Default `'0.0.0.0'` — a container's door has to be reachable from outside it. */
readonly hostname?: string;
/** Model label echoed in `response` objects when the request names none. */
readonly model?: string;
/** Ceiling on a request body. Default one mebibyte. */
readonly maxBodyBytes?: number;
/**
* A `node:http` server you own, to ATTACH these routes to rather than binding
* one. Refused together with `port`/`hostname`, which would name a socket
* this host does not bind.
*/
readonly server?: Server;
/** Answer a path this host does not own. Private-server mode only. */
readonly onUnhandled?: (req: IncomingMessage, res: ServerResponse) => void;
}
/**
* An {@link HttpHost} speaking Foundry's hosted-agent contract.
*
* @param options - Port, interface, model label and body ceiling.
*/
export declare function foundryResponsesHost(options?: FoundryResponsesHostOptions): HttpHost;