@copilotkit/runtime
Version:
<div align="center"> <a href="https://copilotkit.ai" target="_blank"> <img src="https://github.com/copilotkit/copilotkit/raw/main/assets/banner.png" alt="CopilotKit Logo"> </a>
213 lines (190 loc) • 6.14 kB
text/typescript
import { Action, CopilotKitErrorCode } from "@copilotkit/shared";
import { GraphQLContext } from "../integrations/shared";
import { Logger } from "pino";
import { Message } from "../../graphql/types/converted";
import { RuntimeEvent } from "../../service-adapters/events";
import { Observable } from "rxjs";
import { ActionInput } from "../../graphql/inputs/action.input";
import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
import {
constructLGCRemoteAction,
constructRemoteActions,
createHeaders,
} from "./remote-action-constructors";
import {
CopilotKitLowLevelError,
ResolvedCopilotKitError,
CopilotKitError,
} from "@copilotkit/shared";
import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
import { AbstractAgent } from "@ag-ui/client";
import { constructAGUIRemoteAction } from "./agui-action";
export type EndpointDefinition = CopilotKitEndpoint | LangGraphPlatformEndpoint;
export enum EndpointType {
CopilotKit = "copilotKit",
LangGraphPlatform = "langgraph-platform",
}
export interface BaseEndpointDefinition<TActionType extends EndpointType> {
type?: TActionType;
}
export interface CopilotKitEndpoint extends BaseEndpointDefinition<EndpointType.CopilotKit> {
url: string;
onBeforeRequest?: ({ ctx }: { ctx: GraphQLContext }) => {
headers?: Record<string, string> | undefined;
};
}
export interface LangGraphPlatformAgent {
name: string;
description: string;
assistantId?: string;
}
export interface LangGraphPlatformEndpoint
extends BaseEndpointDefinition<EndpointType.LangGraphPlatform> {
deploymentUrl: string;
langsmithApiKey?: string | null;
agents: LangGraphPlatformAgent[];
}
export type RemoteActionInfoResponse = {
actions: any[];
agents: any[];
};
export type RemoteAgentHandlerParams = {
name: string;
actionInputsWithoutAgents: ActionInput[];
threadId?: string;
nodeName?: string;
additionalMessages?: Message[];
metaEvents?: MetaEventInput[];
};
export type RemoteAgentAction = Action<any> & {
remoteAgentHandler: (params: RemoteAgentHandlerParams) => Promise<Observable<RuntimeEvent>>;
};
export function isRemoteAgentAction(action: Action<any>): action is RemoteAgentAction {
if (!action) {
return false;
}
return typeof (action as RemoteAgentAction).remoteAgentHandler === "function";
}
async function fetchRemoteInfo({
url,
onBeforeRequest,
graphqlContext,
logger,
frontendUrl,
}: {
url: string;
onBeforeRequest?: CopilotKitEndpoint["onBeforeRequest"];
graphqlContext: GraphQLContext;
logger: Logger;
frontendUrl?: string;
}): Promise<RemoteActionInfoResponse> {
logger.debug({ url }, "Fetching actions from url");
const headers = createHeaders(onBeforeRequest, graphqlContext);
const fetchUrl = `${url}/info`;
try {
const response = await fetch(fetchUrl, {
method: "POST",
headers,
body: JSON.stringify({ properties: graphqlContext.properties, frontendUrl }),
});
if (!response.ok) {
logger.error(
{ url, status: response.status, body: await response.text() },
"Failed to fetch actions from url",
);
throw new ResolvedCopilotKitError({
status: response.status,
url: fetchUrl,
isRemoteEndpoint: true,
});
}
const json = await response.json();
logger.debug({ json }, "Fetched actions from url");
return json;
} catch (error) {
if (error instanceof CopilotKitError) {
throw error;
}
throw new CopilotKitLowLevelError({ error, url: fetchUrl });
}
}
export async function setupRemoteActions({
remoteEndpointDefinitions,
graphqlContext,
messages,
agentStates,
frontendUrl,
agents,
metaEvents,
}: {
remoteEndpointDefinitions: EndpointDefinition[];
graphqlContext: GraphQLContext;
messages: Message[];
agentStates?: AgentStateInput[];
frontendUrl?: string;
agents: Record<string, AbstractAgent>;
metaEvents?: MetaEventInput[];
}): Promise<Action[]> {
const logger = graphqlContext.logger.child({ component: "remote-actions.fetchRemoteActions" });
logger.debug({ remoteEndpointDefinitions }, "Fetching from remote endpoints");
// Remove duplicates of remoteEndpointDefinitions.url
const filtered = remoteEndpointDefinitions.filter((value, index, self) => {
if (value.type === EndpointType.LangGraphPlatform) {
return value;
}
return index === self.findIndex((t: CopilotKitEndpoint) => t.url === value.url);
});
const result = await Promise.all(
filtered.map(async (endpoint) => {
// Check for properties that can distinguish LG platform from other actions
if (endpoint.type === EndpointType.LangGraphPlatform) {
return constructLGCRemoteAction({
endpoint,
messages,
graphqlContext,
logger: logger.child({
component: "remote-actions.constructLGCRemoteAction",
endpoint,
}),
agentStates,
});
}
const json = await fetchRemoteInfo({
url: endpoint.url,
onBeforeRequest: endpoint.onBeforeRequest,
graphqlContext,
logger: logger.child({ component: "remote-actions.fetchActionsFromUrl", endpoint }),
frontendUrl,
});
return constructRemoteActions({
json,
messages,
url: endpoint.url,
onBeforeRequest: endpoint.onBeforeRequest,
graphqlContext,
logger: logger.child({ component: "remote-actions.constructActions", endpoint }),
agentStates,
});
}),
);
for (const [key, agent] of Object.entries(agents)) {
if (agent.agentId !== undefined && agent.agentId !== key) {
throw new CopilotKitError({
message: `Agent ${key} has agentId ${agent.agentId} which does not match the key ${key}`,
code: CopilotKitErrorCode.UNKNOWN,
});
} else if (agent.agentId === undefined) {
agent.agentId = key;
}
result.push(
constructAGUIRemoteAction({
logger,
messages,
agentStates,
agent: agent,
metaEvents,
}),
);
}
return result.flat();
}