@copilotkit/runtime
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
82 lines (80 loc) • 3.71 kB
JavaScript
import "reflect-metadata";
import { mergeForwardableHeaders, resolveForwardHeadersPolicy } from "./header-utils.mjs";
import { cloneAgentForRequest, parseRunRequest } from "./shared/agent-utils.mjs";
import { createSseEventResponse } from "./shared/sse-response.mjs";
import { finalizeRunEvents } from "@copilotkit/shared";
import { Observable } from "rxjs";
//#region src/v2/runtime/handlers/handle-suggest.ts
/**
* Stateless suggestion run.
*
* Executes the provider agent **directly** and streams its AG-UI events back as
* SSE — the same wire format as `/agent/:id/run`, so the client consumes it with
* the stock `HttpAgent` transport and chips fill in as the provider emits them.
* It deliberately does not go through `runtime.runner`: the runner's
* `run()` writes to a module-level store keyed by threadId (backing the SSE
* runtime's local thread endpoints), which would leak the throwaway suggestion
* thread. This handler runs the agent's event pipeline **without** that
* persistence — no thread, lock, gateway, name-gen, or run telemetry — so
* dynamic suggestions are side-effect-free in every runtime mode.
*
* The only per-request configuration it applies is forwarding the request's
* allowlisted headers (`authorization` + `x-*`) onto the agent clone. It does
* **not** attach any request middleware — no A2UI, no MCPApps, no
* OpenGenerativeUI, no Intelligence enterprise-learning tools. The **client**
* (the core suggestion engine) forces `toolChoice: copilotkitSuggest` in the
* request body; this handler does not set tool choice itself and relies on it
* being present in the incoming `input`. Given that forced tool choice, any
* middleware-injected tools are dead weight, and MCPApps setup can incur a
* `listTools` network round-trip per suggestion under `available: "always"` — a
* side effect this path must never pay.
*
* When the client aborts the HTTP request (via its `AbortController`), the
* server-side run is cancelled best-effort so an aborted suggestion does not
* keep running a provider call to completion.
*
* @param params - The runtime, request, and resolved agent id.
* @returns A `text/event-stream` `Response` of the provider agent's AG-UI events
* on success, or the resolution `Response` (e.g. 404/400) when agent/body
* resolution fails before streaming begins.
*/
async function handleSuggestAgent({ runtime, request, agentId }) {
const agent = await cloneAgentForRequest(runtime, agentId, request);
if (agent instanceof Response) return agent;
agent.agentId = agentId;
const input = await parseRunRequest(request);
if (input instanceof Response) return input;
const headerCarryingAgent = agent;
headerCarryingAgent.headers = mergeForwardableHeaders(headerCarryingAgent.headers, request, runtime.forwardHeadersPolicy ?? resolveForwardHeadersPolicy(void 0));
agent.setMessages(input.messages);
agent.setState(input.state);
agent.threadId = input.threadId;
return createSseEventResponse({
request,
agentId,
captureTelemetry: false,
observableFactory: () => new Observable((subscriber) => {
const collected = [];
let settled = false;
agent.runAgent(input, { onEvent: ({ event }) => {
collected.push(event);
subscriber.next(event);
} }).then(() => {
for (const event of finalizeRunEvents(collected, { stopRequested: false })) subscriber.next(event);
settled = true;
subscriber.complete();
}).catch((error) => {
settled = true;
subscriber.error(error);
});
return () => {
if (!settled && typeof agent.abortRun === "function") try {
agent.abortRun();
} catch {}
};
})
});
}
//#endregion
export { handleSuggestAgent };
//# sourceMappingURL=handle-suggest.mjs.map