UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

115 lines • 5.78 kB
"use strict"; /** * 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()); */ Object.defineProperty(exports, "__esModule", { value: true }); exports.foundryResponsesHost = exports.FOUNDRY_SESSION_FIELDS = exports.FOUNDRY_READINESS_PATH = exports.FOUNDRY_INVOKE_PATH = exports.DEFAULT_FOUNDRY_PORT = void 0; const doorGuard_js_1 = require("../../hosting/doorGuard.js"); const httpHost_js_1 = require("../../hosting/httpHost.js"); const responsesWire_js_1 = require("./responsesWire.js"); /** The port a Foundry hosted agent is expected on. */ exports.DEFAULT_FOUNDRY_PORT = 8088; /** The path that takes a turn. */ exports.FOUNDRY_INVOKE_PATH = '/responses'; /** The path a Foundry hosted agent answers a readiness probe on. */ exports.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. */ exports.FOUNDRY_SESSION_FIELDS = [ 'conversation', 'agent_session_id', 'session_id', ]; /** * One mebibyte: large enough for any resume or job description this door was * built to carry, small enough that a hostile caller cannot spend the heap. */ const DEFAULT_MAX_BODY_BYTES = 1_048_576; /** * An {@link HttpHost} speaking Foundry's hosted-agent contract. * * @param options - Port, interface, model label and body ceiling. */ function foundryResponsesHost(options = {}) { const { server, onUnhandled, model, hostname } = options; // `port` and `hostname` name a socket a caller-owned server already bound, so // they are passed only when this host is the one binding. Defaulting the port // unconditionally would turn "attach to my server" into a refusal. const socket = server ? { server } : { port: options.port ?? exports.DEFAULT_FOUNDRY_PORT, ...(hostname === undefined ? {} : { hostname }), ...(onUnhandled === undefined ? {} : { onUnhandled }), }; return (0, httpHost_js_1.httpHost)({ name: 'foundryResponsesHost', wire: (0, responsesWire_js_1.responsesWire)({ ...(model === undefined ? {} : { defaultModel: model }), sessionFields: exports.FOUNDRY_SESSION_FIELDS, health: { status: 'healthy' }, }), invokePath: exports.FOUNDRY_INVOKE_PATH, healthPath: exports.FOUNDRY_READINESS_PATH, // The Inspector asks whether the door is there before it uses it. invokeHeadProbe: true, maxBodyBytes: options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES, // The platform fronts this port: browser rules off unless the deployment // sets them (see the options' doc) — except on a LOOPBACK bind, which no // platform fronts, where every plain-host default applies. ...(0, doorGuard_js_1.platformFrontedDoor)(options, server === undefined ? hostname : undefined), ...socket, }); } exports.foundryResponsesHost = foundryResponsesHost; //# sourceMappingURL=foundryResponses.js.map