@copilotkit/runtime 1.10.7-next.0 → 1.50.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -6
- package/dist/index.d.ts +1655 -27
- package/dist/index.js +2172 -5049
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5441 -99
- package/dist/index.mjs.map +1 -1
- package/dist/v2/index.d.ts +1 -0
- package/dist/v2/index.js +15 -0
- package/dist/v2/index.js.map +1 -0
- package/dist/v2/index.mjs +4 -0
- package/dist/v2/index.mjs.map +1 -0
- package/package.json +33 -21
- package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
- package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
- package/src/graphql/message-conversion/gql-to-agui.test.ts +1578 -0
- package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
- package/src/graphql/message-conversion/index.ts +2 -0
- package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
- package/src/graphql/resolvers/copilot.resolver.ts +3 -48
- package/src/graphql/resolvers/state.resolver.ts +3 -2
- package/src/graphql/types/converted/index.ts +32 -6
- package/src/graphql/types/enums.ts +2 -2
- package/src/graphql/types/message-status.type.ts +3 -1
- package/src/lib/index.ts +1 -1
- package/src/lib/integrations/nextjs/app-router.ts +10 -11
- package/src/lib/integrations/nextjs/pages-router.ts +4 -11
- package/src/lib/integrations/node-http/index.ts +64 -5
- package/src/lib/integrations/shared.ts +1 -1
- package/src/lib/observability.ts +87 -0
- package/src/lib/runtime/{langgraph/langgraph-agent.ts → agent-integrations/langgraph.agent.ts} +5 -0
- package/src/lib/runtime/copilot-runtime.ts +346 -1333
- package/src/lib/runtime/types.ts +49 -0
- package/src/lib/runtime/utils.ts +87 -0
- package/src/lib/telemetry-client.ts +6 -5
- package/src/service-adapters/anthropic/anthropic-adapter.ts +5 -1
- package/src/service-adapters/bedrock/bedrock-adapter.ts +6 -1
- package/src/service-adapters/empty/empty-adapter.ts +3 -0
- package/src/service-adapters/events.ts +0 -254
- package/src/service-adapters/experimental/ollama/ollama-adapter.ts +5 -1
- package/src/service-adapters/google/google-genai-adapter.ts +7 -1
- package/src/service-adapters/groq/groq-adapter.ts +5 -1
- package/src/service-adapters/langchain/langchain-adapter.ts +3 -0
- package/src/service-adapters/openai/openai-adapter.ts +5 -1
- package/src/service-adapters/openai/openai-assistant-adapter.ts +4 -0
- package/src/service-adapters/service-adapter.ts +3 -0
- package/src/service-adapters/unify/unify-adapter.ts +6 -1
- package/src/v2/index.ts +2 -0
- package/tsup.config.ts +2 -1
- package/dist/chunk-27JKTS6P.mjs +0 -1704
- package/dist/chunk-27JKTS6P.mjs.map +0 -1
- package/dist/chunk-2OZAGFV3.mjs +0 -43
- package/dist/chunk-2OZAGFV3.mjs.map +0 -1
- package/dist/chunk-5BW5IBTZ.mjs +0 -80
- package/dist/chunk-5BW5IBTZ.mjs.map +0 -1
- package/dist/chunk-AMUJQ6IR.mjs +0 -50
- package/dist/chunk-AMUJQ6IR.mjs.map +0 -1
- package/dist/chunk-BMIYSM5W.mjs +0 -25
- package/dist/chunk-BMIYSM5W.mjs.map +0 -1
- package/dist/chunk-FDTCG47E.mjs +0 -25
- package/dist/chunk-FDTCG47E.mjs.map +0 -1
- package/dist/chunk-FHD4JECV.mjs +0 -33
- package/dist/chunk-FHD4JECV.mjs.map +0 -1
- package/dist/chunk-LRCKLBMO.mjs +0 -6020
- package/dist/chunk-LRCKLBMO.mjs.map +0 -1
- package/dist/chunk-R7RMYEPZ.mjs +0 -175
- package/dist/chunk-R7RMYEPZ.mjs.map +0 -1
- package/dist/chunk-SHBDMA63.mjs +0 -141
- package/dist/chunk-SHBDMA63.mjs.map +0 -1
- package/dist/chunk-XWBDEXDA.mjs +0 -153
- package/dist/chunk-XWBDEXDA.mjs.map +0 -1
- package/dist/graphql/types/base/index.d.ts +0 -6
- package/dist/graphql/types/base/index.js +0 -63
- package/dist/graphql/types/base/index.js.map +0 -1
- package/dist/graphql/types/base/index.mjs +0 -8
- package/dist/graphql/types/base/index.mjs.map +0 -1
- package/dist/graphql/types/converted/index.d.ts +0 -2
- package/dist/graphql/types/converted/index.js +0 -200
- package/dist/graphql/types/converted/index.js.map +0 -1
- package/dist/graphql/types/converted/index.mjs +0 -19
- package/dist/graphql/types/converted/index.mjs.map +0 -1
- package/dist/groq-adapter-c8aec5c5.d.ts +0 -321
- package/dist/index-96b330da.d.ts +0 -119
- package/dist/langserve-0c6100e3.d.ts +0 -257
- package/dist/lib/cloud/index.d.ts +0 -6
- package/dist/lib/cloud/index.js +0 -18
- package/dist/lib/cloud/index.js.map +0 -1
- package/dist/lib/cloud/index.mjs +0 -1
- package/dist/lib/cloud/index.mjs.map +0 -1
- package/dist/lib/index.d.ts +0 -212
- package/dist/lib/index.js +0 -7843
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/index.mjs +0 -76
- package/dist/lib/index.mjs.map +0 -1
- package/dist/lib/integrations/index.d.ts +0 -34
- package/dist/lib/integrations/index.js +0 -3052
- package/dist/lib/integrations/index.js.map +0 -1
- package/dist/lib/integrations/index.mjs +0 -37
- package/dist/lib/integrations/index.mjs.map +0 -1
- package/dist/lib/integrations/nest/index.d.ts +0 -15
- package/dist/lib/integrations/nest/index.js +0 -2959
- package/dist/lib/integrations/nest/index.js.map +0 -1
- package/dist/lib/integrations/nest/index.mjs +0 -14
- package/dist/lib/integrations/nest/index.mjs.map +0 -1
- package/dist/lib/integrations/node-express/index.d.ts +0 -15
- package/dist/lib/integrations/node-express/index.js +0 -2959
- package/dist/lib/integrations/node-express/index.js.map +0 -1
- package/dist/lib/integrations/node-express/index.mjs +0 -14
- package/dist/lib/integrations/node-express/index.mjs.map +0 -1
- package/dist/lib/integrations/node-http/index.d.ts +0 -15
- package/dist/lib/integrations/node-http/index.js +0 -2945
- package/dist/lib/integrations/node-http/index.js.map +0 -1
- package/dist/lib/integrations/node-http/index.mjs +0 -13
- package/dist/lib/integrations/node-http/index.mjs.map +0 -1
- package/dist/service-adapters/index.d.ts +0 -162
- package/dist/service-adapters/index.js +0 -1787
- package/dist/service-adapters/index.js.map +0 -1
- package/dist/service-adapters/index.mjs +0 -34
- package/dist/service-adapters/index.mjs.map +0 -1
- package/dist/service-adapters/shared/index.d.ts +0 -9
- package/dist/service-adapters/shared/index.js +0 -72
- package/dist/service-adapters/shared/index.js.map +0 -1
- package/dist/service-adapters/shared/index.mjs +0 -8
- package/dist/service-adapters/shared/index.mjs.map +0 -1
- package/dist/shared-0a7346ce.d.ts +0 -466
- package/dist/utils/index.d.ts +0 -65
- package/dist/utils/index.js +0 -175
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/index.mjs +0 -12
- package/dist/utils/index.mjs.map +0 -1
- package/src/lib/runtime/__tests__/remote-action-constructors.test.ts +0 -246
- package/src/lib/runtime/agui-action.ts +0 -180
- package/src/lib/runtime/remote-action-constructors.ts +0 -331
- package/src/lib/runtime/remote-actions.ts +0 -217
- package/src/lib/runtime/remote-lg-action.ts +0 -1006
|
@@ -14,57 +14,44 @@
|
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
Action,
|
|
17
|
-
actionParametersToJsonSchema,
|
|
18
|
-
Parameter,
|
|
19
|
-
ResolvedCopilotKitError,
|
|
20
|
-
CopilotKitApiDiscoveryError,
|
|
21
|
-
randomId,
|
|
22
|
-
CopilotKitError,
|
|
23
|
-
CopilotKitAgentDiscoveryError,
|
|
24
|
-
CopilotKitMisuseError,
|
|
25
|
-
CopilotKitErrorCode,
|
|
26
|
-
CopilotKitLowLevelError,
|
|
27
17
|
CopilotErrorHandler,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
CopilotKitMisuseError,
|
|
19
|
+
MaybePromise,
|
|
20
|
+
NonEmptyRecord,
|
|
21
|
+
Parameter,
|
|
22
|
+
readBody,
|
|
23
|
+
getZodParameters,
|
|
24
|
+
PartialBy,
|
|
31
25
|
} from "@copilotkit/shared";
|
|
26
|
+
import { type RunAgentInput } from "@ag-ui/core";
|
|
27
|
+
import { aguiToGQL } from "../../graphql/message-conversion/agui-to-gql";
|
|
28
|
+
import { CopilotServiceAdapter, RemoteChainParameters } from "../../service-adapters";
|
|
32
29
|
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
} from "
|
|
30
|
+
CopilotRuntime as CopilotRuntimeVNext,
|
|
31
|
+
CopilotRuntimeOptions,
|
|
32
|
+
CopilotRuntimeOptions as CopilotRuntimeOptionsVNext,
|
|
33
|
+
InMemoryAgentRunner as InMemoryAgentRunnerVNext,
|
|
34
|
+
} from "@copilotkitnext/runtime";
|
|
38
35
|
|
|
39
36
|
import { MessageInput } from "../../graphql/inputs/message.input";
|
|
40
37
|
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
41
|
-
import { RuntimeEventSource
|
|
42
|
-
import { convertGqlInputToMessages } from "../../service-adapters/conversion";
|
|
38
|
+
import { RuntimeEventSource } from "../../service-adapters/events";
|
|
43
39
|
import { Message } from "../../graphql/types/converted";
|
|
44
40
|
import { ForwardedParametersInput } from "../../graphql/inputs/forwarded-parameters.input";
|
|
45
41
|
|
|
46
42
|
import {
|
|
47
|
-
isRemoteAgentAction,
|
|
48
43
|
EndpointType,
|
|
49
|
-
setupRemoteActions,
|
|
50
44
|
EndpointDefinition,
|
|
51
45
|
CopilotKitEndpoint,
|
|
52
46
|
LangGraphPlatformEndpoint,
|
|
53
|
-
} from "./
|
|
47
|
+
} from "./types";
|
|
54
48
|
|
|
55
49
|
import { GraphQLContext } from "../integrations/shared";
|
|
56
50
|
import { AgentSessionInput } from "../../graphql/inputs/agent-session.input";
|
|
57
|
-
import { from } from "rxjs";
|
|
58
51
|
import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
|
|
59
|
-
import { ActionInputAvailability } from "../../graphql/types/enums";
|
|
60
|
-
import { createHeaders } from "./remote-action-constructors";
|
|
61
|
-
import { fetchWithRetry } from "./retry-utils";
|
|
62
52
|
import { Agent } from "../../graphql/types/agents-response.type";
|
|
63
53
|
import { ExtensionsInput } from "../../graphql/inputs/extensions.input";
|
|
64
54
|
import { ExtensionsResponse } from "../../graphql/types/extensions-response.type";
|
|
65
|
-
import { LoadAgentStateResponse } from "../../graphql/types/load-agent-state-response.type";
|
|
66
|
-
import { Client as LangGraphClient } from "@langchain/langgraph-sdk";
|
|
67
|
-
import { langchainMessagesToCopilotKit } from "./remote-lg-action";
|
|
68
55
|
import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
|
|
69
56
|
import {
|
|
70
57
|
CopilotObservabilityConfig,
|
|
@@ -73,51 +60,20 @@ import {
|
|
|
73
60
|
LLMErrorData,
|
|
74
61
|
} from "../observability";
|
|
75
62
|
import { AbstractAgent } from "@ag-ui/client";
|
|
76
|
-
import { MessageRole } from "../../graphql/types/enums";
|
|
77
63
|
|
|
78
64
|
// +++ MCP Imports +++
|
|
79
65
|
import {
|
|
80
66
|
MCPClient,
|
|
81
67
|
MCPEndpointConfig,
|
|
82
68
|
MCPTool,
|
|
69
|
+
extractParametersFromSchema,
|
|
83
70
|
convertMCPToolsToActions,
|
|
84
71
|
generateMcpToolInstructions,
|
|
85
72
|
} from "./mcp-tools-utils";
|
|
86
|
-
import { LangGraphAgent } from "./
|
|
73
|
+
import { LangGraphAgent } from "./agent-integrations/langgraph.agent";
|
|
74
|
+
import { BasicAgent, BasicAgentConfiguration } from "@copilotkitnext/agent";
|
|
87
75
|
// Define the function type alias here or import if defined elsewhere
|
|
88
76
|
type CreateMCPClientFunction = (config: MCPEndpointConfig) => Promise<MCPClient>;
|
|
89
|
-
// --- MCP Imports ---
|
|
90
|
-
|
|
91
|
-
import { generateHelpfulErrorMessage } from "../streaming";
|
|
92
|
-
import { CopilotContextInput } from "../../graphql/inputs/copilot-context.input";
|
|
93
|
-
import { RemoteAgentAction } from "./agui-action";
|
|
94
|
-
|
|
95
|
-
export interface CopilotRuntimeRequest {
|
|
96
|
-
serviceAdapter: CopilotServiceAdapter;
|
|
97
|
-
messages: MessageInput[];
|
|
98
|
-
actions: ActionInput[];
|
|
99
|
-
agentSession?: AgentSessionInput;
|
|
100
|
-
agentStates?: AgentStateInput[];
|
|
101
|
-
outputMessagesPromise: Promise<Message[]>;
|
|
102
|
-
threadId?: string;
|
|
103
|
-
runId?: string;
|
|
104
|
-
publicApiKey?: string;
|
|
105
|
-
graphqlContext: GraphQLContext;
|
|
106
|
-
forwardedParameters?: ForwardedParametersInput;
|
|
107
|
-
url?: string;
|
|
108
|
-
extensions?: ExtensionsInput;
|
|
109
|
-
metaEvents?: MetaEventInput[];
|
|
110
|
-
context?: CopilotContextInput[];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
interface CopilotRuntimeResponse {
|
|
114
|
-
threadId: string;
|
|
115
|
-
runId?: string;
|
|
116
|
-
eventSource: RuntimeEventSource;
|
|
117
|
-
serverSideActions: Action<any>[];
|
|
118
|
-
actionInputsWithoutAgents: ActionInput[];
|
|
119
|
-
extensions?: ExtensionsResponse;
|
|
120
|
-
}
|
|
121
77
|
|
|
122
78
|
type ActionsConfiguration<T extends Parameter[] | [] = []> =
|
|
123
79
|
| Action<T>[]
|
|
@@ -157,17 +113,23 @@ interface Middleware {
|
|
|
157
113
|
/**
|
|
158
114
|
* A function that is called before the request is processed.
|
|
159
115
|
*/
|
|
116
|
+
/**
|
|
117
|
+
* @deprecated This middleware hook is deprecated and will be removed in a future version.
|
|
118
|
+
* Use updated middleware integration methods in CopilotRuntimeVNext instead.
|
|
119
|
+
*/
|
|
160
120
|
onBeforeRequest?: OnBeforeRequestHandler;
|
|
161
121
|
|
|
162
122
|
/**
|
|
163
123
|
* A function that is called after the request is processed.
|
|
164
124
|
*/
|
|
125
|
+
/**
|
|
126
|
+
* @deprecated This middleware hook is deprecated and will be removed in a future version.
|
|
127
|
+
* Use updated middleware integration methods in CopilotRuntimeVNext instead.
|
|
128
|
+
*/
|
|
165
129
|
onAfterRequest?: OnAfterRequestHandler;
|
|
166
130
|
}
|
|
167
131
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []> {
|
|
132
|
+
export interface CopilotRuntimeConstructorParams_BASE<T extends Parameter[] | [] = []> {
|
|
171
133
|
/**
|
|
172
134
|
* Middleware to be used by the runtime.
|
|
173
135
|
*
|
|
@@ -190,6 +152,10 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
|
|
|
190
152
|
* }) => void | Promise<void>;
|
|
191
153
|
* ```
|
|
192
154
|
*/
|
|
155
|
+
/**
|
|
156
|
+
* @deprecated This middleware hook is deprecated and will be removed in a future version.
|
|
157
|
+
* Use updated middleware integration methods in CopilotRuntimeVNext instead.
|
|
158
|
+
*/
|
|
193
159
|
middleware?: Middleware;
|
|
194
160
|
|
|
195
161
|
/*
|
|
@@ -312,1342 +278,389 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
|
|
|
312
278
|
onError?: CopilotErrorHandler;
|
|
313
279
|
|
|
314
280
|
onStopGeneration?: OnStopGenerationHandler;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
318
|
-
public actions: ActionsConfiguration<T>;
|
|
319
|
-
public agents: Record<string, AbstractAgent>;
|
|
320
|
-
public remoteEndpointDefinitions: EndpointDefinition[];
|
|
321
|
-
private langserve: Promise<Action<any>>[] = [];
|
|
322
|
-
private onBeforeRequest?: OnBeforeRequestHandler;
|
|
323
|
-
private onAfterRequest?: OnAfterRequestHandler;
|
|
324
|
-
private onStopGeneration?: OnStopGenerationHandler;
|
|
325
|
-
private delegateAgentProcessingToServiceAdapter: boolean;
|
|
326
|
-
private observability?: CopilotObservabilityConfig;
|
|
327
|
-
private availableAgents: Pick<AgentWithEndpoint, "name" | "id">[];
|
|
328
|
-
private onError?: CopilotErrorHandler;
|
|
329
|
-
private hasWarnedAboutError = false;
|
|
330
|
-
|
|
331
|
-
// +++ MCP Properties +++
|
|
332
|
-
private readonly mcpServersConfig?: MCPEndpointConfig[];
|
|
333
|
-
private mcpActionCache = new Map<string, Action<any>[]>();
|
|
334
|
-
// --- MCP Properties ---
|
|
335
|
-
|
|
336
|
-
// +++ MCP Client Factory +++
|
|
337
|
-
private readonly createMCPClientImpl?: CreateMCPClientFunction;
|
|
338
|
-
// --- MCP Client Factory ---
|
|
339
|
-
|
|
340
|
-
constructor(params?: CopilotRuntimeConstructorParams<T>) {
|
|
341
|
-
if (
|
|
342
|
-
params?.remoteEndpoints &&
|
|
343
|
-
params?.remoteEndpoints.some((e) => e.type === EndpointType.LangGraphPlatform)
|
|
344
|
-
) {
|
|
345
|
-
throw new CopilotKitMisuseError({
|
|
346
|
-
message:
|
|
347
|
-
"LangGraph Platform remote endpoints are deprecated in favor of the `agents` property. Refer to https://docs.copilotkit.ai/langgraph for more information.",
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (
|
|
352
|
-
params?.actions &&
|
|
353
|
-
params?.remoteEndpoints &&
|
|
354
|
-
params?.remoteEndpoints.some((e) => e.type === EndpointType.LangGraphPlatform)
|
|
355
|
-
) {
|
|
356
|
-
console.warn("Actions set in runtime instance will not be available for the agent");
|
|
357
|
-
console.warn(
|
|
358
|
-
`LangGraph Platform remote endpoints are deprecated in favor of the "agents" property`,
|
|
359
|
-
);
|
|
360
|
-
}
|
|
361
281
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// console.warn('LangGraph Agent class should be imported from @copilotkit/runtime. ')
|
|
370
|
-
// }
|
|
371
|
-
|
|
372
|
-
this.actions = params?.actions || [];
|
|
373
|
-
this.availableAgents = [];
|
|
374
|
-
|
|
375
|
-
for (const chain of params?.langserve || []) {
|
|
376
|
-
const remoteChain = new RemoteChain(chain);
|
|
377
|
-
this.langserve.push(remoteChain.toAction());
|
|
378
|
-
}
|
|
282
|
+
// /** Optional transcription service for audio processing. */
|
|
283
|
+
// transcriptionService?: CopilotRuntimeOptionsVNext["transcriptionService"];
|
|
284
|
+
// /** Optional *before* middleware – callback function or webhook URL. */
|
|
285
|
+
// beforeRequestMiddleware?: CopilotRuntimeOptionsVNext["beforeRequestMiddleware"];
|
|
286
|
+
// /** Optional *after* middleware – callback function or webhook URL. */
|
|
287
|
+
// afterRequestMiddleware?: CopilotRuntimeOptionsVNext["afterRequestMiddleware"];
|
|
288
|
+
}
|
|
379
289
|
|
|
380
|
-
|
|
290
|
+
type BeforeRequestMiddleware = CopilotRuntimeOptionsVNext["beforeRequestMiddleware"];
|
|
291
|
+
type AfterRequestMiddleware = CopilotRuntimeOptionsVNext["afterRequestMiddleware"];
|
|
292
|
+
type BeforeRequestMiddlewareFn = Exclude<BeforeRequestMiddleware, string>;
|
|
293
|
+
type BeforeRequestMiddlewareFnParameters = Parameters<BeforeRequestMiddlewareFn>;
|
|
294
|
+
type BeforeRequestMiddlewareFnResult = ReturnType<BeforeRequestMiddlewareFn>;
|
|
295
|
+
type AfterRequestMiddlewareFn = Exclude<AfterRequestMiddleware, string>;
|
|
296
|
+
type AfterRequestMiddlewareFnParameters = Parameters<AfterRequestMiddlewareFn>;
|
|
297
|
+
|
|
298
|
+
interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []>
|
|
299
|
+
extends Omit<CopilotRuntimeConstructorParams_BASE<T>, "agents">,
|
|
300
|
+
Omit<CopilotRuntimeOptionsVNext, "agents" | "transcriptionService"> {
|
|
301
|
+
/**
|
|
302
|
+
* TODO: un-omit `transcriptionService` above once it's supported
|
|
303
|
+
*
|
|
304
|
+
* This satisfies...
|
|
305
|
+
* – the optional constraint in `CopilotRuntimeConstructorParams_BASE`
|
|
306
|
+
* – the `MaybePromise<NonEmptyRecord<T>>` constraint in `CopilotRuntimeOptionsVNext`
|
|
307
|
+
* – the `Record<string, AbstractAgent>` constraint in `both
|
|
308
|
+
*/
|
|
309
|
+
agents?: MaybePromise<NonEmptyRecord<Record<string, AbstractAgent>>>;
|
|
310
|
+
}
|
|
381
311
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Central runtime object passed to all request handlers.
|
|
314
|
+
*/
|
|
315
|
+
export class CopilotRuntime {
|
|
316
|
+
params?: CopilotRuntimeConstructorParams;
|
|
317
|
+
private observability?: CopilotObservabilityConfig;
|
|
318
|
+
// Cache MCP tools per endpoint to avoid re-fetching repeatedly
|
|
319
|
+
private mcpToolsCache: Map<string, BasicAgentConfiguration["tools"]> = new Map();
|
|
320
|
+
private runtimeArgs: CopilotRuntimeOptions;
|
|
321
|
+
private _instance: CopilotRuntimeVNext;
|
|
322
|
+
|
|
323
|
+
constructor(
|
|
324
|
+
params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
|
|
325
|
+
) {
|
|
326
|
+
const agents = params?.agents ?? {};
|
|
327
|
+
this.runtimeArgs = {
|
|
328
|
+
agents: { ...this.assignEndpointsToAgents(params?.remoteEndpoints ?? []), ...agents },
|
|
329
|
+
runner: params?.runner ?? new InMemoryAgentRunnerVNext(),
|
|
330
|
+
// TODO: add support for transcriptionService from CopilotRuntimeOptionsVNext once it is ready
|
|
331
|
+
// transcriptionService: params?.transcriptionService,
|
|
332
|
+
|
|
333
|
+
beforeRequestMiddleware: this.createOnBeforeRequestHandler(params).bind(this),
|
|
334
|
+
afterRequestMiddleware: this.createOnAfterRequestHandler(params).bind(this),
|
|
335
|
+
};
|
|
336
|
+
this.params = params;
|
|
387
337
|
this.observability = params?.observability_c;
|
|
388
|
-
this.agents = params?.agents ?? {};
|
|
389
|
-
this.onError = params?.onError;
|
|
390
|
-
// +++ MCP Initialization +++
|
|
391
|
-
this.mcpServersConfig = params?.mcpServers;
|
|
392
|
-
this.createMCPClientImpl = params?.createMCPClient;
|
|
393
|
-
|
|
394
|
-
// Validate: If mcpServers are provided, createMCPClient must also be provided
|
|
395
|
-
if (this.mcpServersConfig && this.mcpServersConfig.length > 0 && !this.createMCPClientImpl) {
|
|
396
|
-
throw new CopilotKitMisuseError({
|
|
397
|
-
message:
|
|
398
|
-
"MCP Integration Error: `mcpServers` were provided, but the `createMCPClient` function was not passed to the CopilotRuntime constructor. " +
|
|
399
|
-
"Please provide an implementation for `createMCPClient`.",
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Warning if actions are defined alongside LangGraph platform (potentially MCP too?)
|
|
404
|
-
if (
|
|
405
|
-
params?.actions &&
|
|
406
|
-
(params?.remoteEndpoints?.some((e) => e.type === EndpointType.LangGraphPlatform) ||
|
|
407
|
-
this.mcpServersConfig?.length)
|
|
408
|
-
) {
|
|
409
|
-
console.warn(
|
|
410
|
-
"Local 'actions' defined in CopilotRuntime might not be available to remote agents (LangGraph, MCP). Consider defining actions closer to the agent implementation if needed.",
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
338
|
}
|
|
414
339
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
currentActions: Action<any>[],
|
|
419
|
-
): MessageInput[] {
|
|
420
|
-
// Filter the *passed-in* actions for MCP tools
|
|
421
|
-
const mcpActionsForRequest = currentActions.filter((action) => (action as any)._isMCPTool);
|
|
422
|
-
|
|
423
|
-
if (!mcpActionsForRequest || mcpActionsForRequest.length === 0) {
|
|
424
|
-
return messages; // No MCP tools for this specific request
|
|
340
|
+
get instance() {
|
|
341
|
+
if (!this._instance) {
|
|
342
|
+
this._instance = new CopilotRuntimeVNext(this.runtimeArgs);
|
|
425
343
|
}
|
|
426
344
|
|
|
427
|
-
|
|
428
|
-
const uniqueMcpTools = new Map<string, Action<any>>();
|
|
429
|
-
|
|
430
|
-
// Add all MCP tools to the map with their names as keys
|
|
431
|
-
mcpActionsForRequest.forEach((action) => {
|
|
432
|
-
uniqueMcpTools.set(action.name, action);
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
// Format instructions from the unique tools map
|
|
436
|
-
// Convert Action objects to MCPTool format for the instruction generator
|
|
437
|
-
const toolsMap: Record<string, MCPTool> = {};
|
|
438
|
-
Array.from(uniqueMcpTools.values()).forEach((action) => {
|
|
439
|
-
toolsMap[action.name] = {
|
|
440
|
-
description: action.description || "",
|
|
441
|
-
schema: action.parameters
|
|
442
|
-
? {
|
|
443
|
-
parameters: {
|
|
444
|
-
properties: action.parameters.reduce(
|
|
445
|
-
(acc, p) => ({
|
|
446
|
-
...acc,
|
|
447
|
-
[p.name]: { type: p.type, description: p.description },
|
|
448
|
-
}),
|
|
449
|
-
{},
|
|
450
|
-
),
|
|
451
|
-
required: action.parameters.filter((p) => p.required).map((p) => p.name),
|
|
452
|
-
},
|
|
453
|
-
}
|
|
454
|
-
: {},
|
|
455
|
-
execute: async () => ({}), // Placeholder, not used for instructions
|
|
456
|
-
};
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
// Generate instructions using the exported helper
|
|
460
|
-
const mcpToolInstructions = generateMcpToolInstructions(toolsMap);
|
|
461
|
-
|
|
462
|
-
if (!mcpToolInstructions) {
|
|
463
|
-
return messages; // No MCP tools to describe
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const instructions =
|
|
467
|
-
mcpToolInstructions + "\nUse them when appropriate to fulfill the user's request.";
|
|
468
|
-
|
|
469
|
-
const systemMessageIndex = messages.findIndex((msg) => msg.textMessage?.role === "system");
|
|
470
|
-
|
|
471
|
-
const newMessages = [...messages]; // Create a mutable copy
|
|
472
|
-
|
|
473
|
-
if (systemMessageIndex !== -1) {
|
|
474
|
-
const existingMsg = newMessages[systemMessageIndex];
|
|
475
|
-
if (existingMsg.textMessage) {
|
|
476
|
-
existingMsg.textMessage.content =
|
|
477
|
-
(existingMsg.textMessage.content ? existingMsg.textMessage.content + "\n\n" : "") +
|
|
478
|
-
instructions;
|
|
479
|
-
}
|
|
480
|
-
} else {
|
|
481
|
-
newMessages.unshift({
|
|
482
|
-
id: randomId(),
|
|
483
|
-
createdAt: new Date(),
|
|
484
|
-
textMessage: {
|
|
485
|
-
role: MessageRole.system,
|
|
486
|
-
content: instructions,
|
|
487
|
-
},
|
|
488
|
-
actionExecutionMessage: undefined,
|
|
489
|
-
resultMessage: undefined,
|
|
490
|
-
agentStateMessage: undefined,
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
return newMessages;
|
|
345
|
+
return this._instance;
|
|
495
346
|
}
|
|
496
347
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
extensions,
|
|
509
|
-
agentSession,
|
|
510
|
-
agentStates,
|
|
511
|
-
publicApiKey,
|
|
512
|
-
context,
|
|
513
|
-
} = request;
|
|
514
|
-
graphqlContext.request.signal.addEventListener(
|
|
515
|
-
"abort",
|
|
516
|
-
() =>
|
|
517
|
-
this.onStopGeneration?.({
|
|
518
|
-
threadId,
|
|
519
|
-
runId,
|
|
520
|
-
url,
|
|
521
|
-
agentName: agentSession?.agentName,
|
|
522
|
-
lastMessage: rawMessages[rawMessages.length - 1],
|
|
523
|
-
}),
|
|
524
|
-
{ once: true }, // optional: fire only once
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
const eventSource = new RuntimeEventSource({
|
|
528
|
-
errorHandler: async (error, context) => {
|
|
529
|
-
await this.error("error", context, error, publicApiKey);
|
|
530
|
-
},
|
|
531
|
-
errorContext: {
|
|
532
|
-
threadId,
|
|
533
|
-
runId,
|
|
534
|
-
source: "runtime",
|
|
535
|
-
request: {
|
|
536
|
-
operation: "processRuntimeRequest",
|
|
537
|
-
method: "POST",
|
|
538
|
-
url: url,
|
|
539
|
-
startTime: Date.now(),
|
|
540
|
-
},
|
|
541
|
-
agent: agentSession ? { name: agentSession.agentName } : undefined,
|
|
542
|
-
technical: {
|
|
543
|
-
environment: process.env.NODE_ENV,
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
});
|
|
547
|
-
// Track request start time for logging
|
|
548
|
-
const requestStartTime = Date.now();
|
|
549
|
-
// For storing streamed chunks if progressive logging is enabled
|
|
550
|
-
const streamedChunks: any[] = [];
|
|
551
|
-
|
|
552
|
-
try {
|
|
553
|
-
if (
|
|
554
|
-
Object.keys(this.agents).length &&
|
|
555
|
-
agentSession?.agentName &&
|
|
556
|
-
!this.delegateAgentProcessingToServiceAdapter
|
|
557
|
-
) {
|
|
558
|
-
this.agents = { [agentSession.agentName]: this.agents[agentSession.agentName] };
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
if (agentSession && !this.delegateAgentProcessingToServiceAdapter) {
|
|
562
|
-
return await this.processAgentRequest(request);
|
|
563
|
-
}
|
|
564
|
-
if (serviceAdapter instanceof EmptyAdapter) {
|
|
565
|
-
throw new CopilotKitMisuseError({
|
|
566
|
-
message: `Invalid adapter configuration: EmptyAdapter is only meant to be used with agent lock mode.
|
|
567
|
-
For non-agent components like useCopilotChatSuggestions, CopilotTextarea, or CopilotTask,
|
|
568
|
-
please use an LLM adapter instead.`,
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// +++ Get Server Side Actions (including dynamic MCP) EARLY +++
|
|
573
|
-
const serverSideActions = await this.getServerSideActions(request);
|
|
574
|
-
// --- Get Server Side Actions (including dynamic MCP) EARLY ---
|
|
575
|
-
|
|
576
|
-
// Filter raw messages *before* injection
|
|
577
|
-
const filteredRawMessages = rawMessages.filter((message) => !message.agentStateMessage);
|
|
578
|
-
|
|
579
|
-
// +++ Inject MCP Instructions based on current actions +++
|
|
580
|
-
const messagesWithInjectedInstructions = this.injectMCPToolInstructions(
|
|
581
|
-
filteredRawMessages,
|
|
582
|
-
serverSideActions,
|
|
583
|
-
);
|
|
584
|
-
const inputMessages = convertGqlInputToMessages(messagesWithInjectedInstructions);
|
|
585
|
-
// --- Inject MCP Instructions based on current actions ---
|
|
586
|
-
|
|
587
|
-
// Log LLM request if logging is enabled
|
|
588
|
-
if (this.observability?.enabled && publicApiKey) {
|
|
589
|
-
try {
|
|
590
|
-
const requestData: LLMRequestData = {
|
|
591
|
-
threadId,
|
|
592
|
-
runId,
|
|
593
|
-
model: forwardedParameters?.model,
|
|
594
|
-
messages: inputMessages,
|
|
595
|
-
actions: clientSideActionsInput,
|
|
596
|
-
forwardedParameters,
|
|
597
|
-
timestamp: requestStartTime,
|
|
598
|
-
provider: this.detectProvider(serviceAdapter),
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
await this.observability.hooks.handleRequest(requestData);
|
|
602
|
-
} catch (error) {
|
|
603
|
-
console.error("Error logging LLM request:", error);
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const serverSideActionsInput: ActionInput[] = serverSideActions.map((action) => ({
|
|
608
|
-
name: action.name,
|
|
609
|
-
description: action.description,
|
|
610
|
-
jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters)),
|
|
611
|
-
additionalConfig: action.additionalConfig,
|
|
612
|
-
}));
|
|
613
|
-
|
|
614
|
-
const actionInputs = flattenToolCallsNoDuplicates([
|
|
615
|
-
...serverSideActionsInput,
|
|
616
|
-
...clientSideActionsInput.filter(
|
|
617
|
-
// Filter remote actions from CopilotKit core loop
|
|
618
|
-
(action) => action.available !== ActionInputAvailability.remote,
|
|
619
|
-
),
|
|
620
|
-
]);
|
|
621
|
-
|
|
622
|
-
await this.onBeforeRequest?.({
|
|
623
|
-
threadId,
|
|
624
|
-
runId,
|
|
625
|
-
inputMessages,
|
|
626
|
-
properties: graphqlContext.properties,
|
|
627
|
-
url,
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
const result = await serviceAdapter.process({
|
|
631
|
-
messages: inputMessages,
|
|
632
|
-
actions: actionInputs,
|
|
633
|
-
threadId,
|
|
634
|
-
runId,
|
|
635
|
-
eventSource,
|
|
636
|
-
forwardedParameters,
|
|
637
|
-
extensions,
|
|
638
|
-
agentSession,
|
|
639
|
-
agentStates,
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
// for backwards compatibility, we deal with the case that no threadId is provided
|
|
643
|
-
// by the frontend, by using the threadId from the response
|
|
644
|
-
const nonEmptyThreadId = threadId ?? result.threadId;
|
|
645
|
-
|
|
646
|
-
outputMessagesPromise
|
|
647
|
-
.then((outputMessages) => {
|
|
648
|
-
this.onAfterRequest?.({
|
|
649
|
-
threadId: nonEmptyThreadId,
|
|
650
|
-
runId: result.runId,
|
|
651
|
-
inputMessages,
|
|
652
|
-
outputMessages,
|
|
653
|
-
properties: graphqlContext.properties,
|
|
654
|
-
url,
|
|
348
|
+
private assignEndpointsToAgents(endpoints: CopilotRuntimeConstructorParams["remoteEndpoints"]) {
|
|
349
|
+
return endpoints.reduce((acc, endpoint) => {
|
|
350
|
+
if (resolveEndpointType(endpoint) == EndpointType.LangGraphPlatform) {
|
|
351
|
+
let lgAgents = {};
|
|
352
|
+
const lgEndpoint = endpoint as LangGraphPlatformEndpoint;
|
|
353
|
+
lgEndpoint.agents.forEach((agent) => {
|
|
354
|
+
const graphId = agent.assistantId ?? agent.name;
|
|
355
|
+
lgAgents[graphId] = new LangGraphAgent({
|
|
356
|
+
deploymentUrl: lgEndpoint.deploymentUrl,
|
|
357
|
+
langsmithApiKey: lgEndpoint.langsmithApiKey,
|
|
358
|
+
graphId,
|
|
655
359
|
});
|
|
656
|
-
})
|
|
657
|
-
.catch((_error) => {});
|
|
658
|
-
|
|
659
|
-
// After getting the response, log it if logging is enabled
|
|
660
|
-
if (this.observability?.enabled && publicApiKey) {
|
|
661
|
-
try {
|
|
662
|
-
outputMessagesPromise
|
|
663
|
-
.then((outputMessages) => {
|
|
664
|
-
const responseData: LLMResponseData = {
|
|
665
|
-
threadId: result.threadId,
|
|
666
|
-
runId: result.runId,
|
|
667
|
-
model: forwardedParameters?.model,
|
|
668
|
-
// Use collected chunks for progressive mode or outputMessages for regular mode
|
|
669
|
-
output: this.observability.progressive ? streamedChunks : outputMessages,
|
|
670
|
-
latency: Date.now() - requestStartTime,
|
|
671
|
-
timestamp: Date.now(),
|
|
672
|
-
provider: this.detectProvider(serviceAdapter),
|
|
673
|
-
// Indicate this is the final response
|
|
674
|
-
isFinalResponse: true,
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
try {
|
|
678
|
-
this.observability.hooks.handleResponse(responseData);
|
|
679
|
-
} catch (logError) {
|
|
680
|
-
console.error("Error logging LLM response:", logError);
|
|
681
|
-
}
|
|
682
|
-
})
|
|
683
|
-
.catch((error) => {
|
|
684
|
-
console.error("Failed to get output messages for logging:", error);
|
|
685
|
-
});
|
|
686
|
-
} catch (error) {
|
|
687
|
-
console.error("Error setting up logging for LLM response:", error);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
360
|
+
});
|
|
690
361
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const originalStream = eventSource.stream.bind(eventSource);
|
|
695
|
-
|
|
696
|
-
// Wrap the stream function to intercept events
|
|
697
|
-
eventSource.stream = async (callback) => {
|
|
698
|
-
await originalStream(async (eventStream$) => {
|
|
699
|
-
// Create subscription to capture streaming events
|
|
700
|
-
eventStream$.subscribe({
|
|
701
|
-
next: (event) => {
|
|
702
|
-
// Only log content chunks
|
|
703
|
-
if (event.type === RuntimeEventTypes.TextMessageContent) {
|
|
704
|
-
// Store the chunk
|
|
705
|
-
streamedChunks.push(event.content);
|
|
706
|
-
|
|
707
|
-
// Log each chunk separately for progressive mode
|
|
708
|
-
try {
|
|
709
|
-
const progressiveData: LLMResponseData = {
|
|
710
|
-
threadId: threadId || "",
|
|
711
|
-
runId,
|
|
712
|
-
model: forwardedParameters?.model,
|
|
713
|
-
output: event.content,
|
|
714
|
-
latency: Date.now() - requestStartTime,
|
|
715
|
-
timestamp: Date.now(),
|
|
716
|
-
provider: this.detectProvider(serviceAdapter),
|
|
717
|
-
isProgressiveChunk: true,
|
|
718
|
-
};
|
|
719
|
-
|
|
720
|
-
// Use Promise to handle async logger without awaiting
|
|
721
|
-
Promise.resolve()
|
|
722
|
-
.then(() => {
|
|
723
|
-
this.observability.hooks.handleResponse(progressiveData);
|
|
724
|
-
})
|
|
725
|
-
.catch((error) => {
|
|
726
|
-
console.error("Error in progressive logging:", error);
|
|
727
|
-
});
|
|
728
|
-
} catch (error) {
|
|
729
|
-
console.error("Error preparing progressive log data:", error);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
// Call the original callback with the event stream
|
|
736
|
-
await callback(eventStream$);
|
|
737
|
-
});
|
|
362
|
+
return {
|
|
363
|
+
...acc,
|
|
364
|
+
...lgAgents,
|
|
738
365
|
};
|
|
739
366
|
}
|
|
740
367
|
|
|
741
|
-
return
|
|
742
|
-
|
|
743
|
-
runId: result.runId,
|
|
744
|
-
eventSource,
|
|
745
|
-
serverSideActions,
|
|
746
|
-
actionInputsWithoutAgents: actionInputs.filter(
|
|
747
|
-
(action) =>
|
|
748
|
-
// TODO-AGENTS: do not exclude ALL server side actions
|
|
749
|
-
!serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
|
|
750
|
-
// !isRemoteAgentAction(
|
|
751
|
-
// serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
|
|
752
|
-
// ),
|
|
753
|
-
),
|
|
754
|
-
extensions: result.extensions,
|
|
755
|
-
};
|
|
756
|
-
} catch (error) {
|
|
757
|
-
// Log error if logging is enabled
|
|
758
|
-
if (this.observability?.enabled && publicApiKey) {
|
|
759
|
-
try {
|
|
760
|
-
const errorData: LLMErrorData = {
|
|
761
|
-
threadId,
|
|
762
|
-
runId,
|
|
763
|
-
model: forwardedParameters?.model,
|
|
764
|
-
error: error instanceof Error ? error : String(error),
|
|
765
|
-
timestamp: Date.now(),
|
|
766
|
-
latency: Date.now() - requestStartTime,
|
|
767
|
-
provider: this.detectProvider(serviceAdapter),
|
|
768
|
-
};
|
|
769
|
-
|
|
770
|
-
await this.observability.hooks.handleError(errorData);
|
|
771
|
-
} catch (logError) {
|
|
772
|
-
console.error("Error logging LLM error:", logError);
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
let structuredError: CopilotKitError;
|
|
777
|
-
|
|
778
|
-
if (error instanceof CopilotKitError) {
|
|
779
|
-
structuredError = error;
|
|
780
|
-
} else {
|
|
781
|
-
// Convert non-CopilotKitErrors to structured errors, but preserve already structured ones
|
|
782
|
-
structuredError = ensureStructuredError(error, (err) =>
|
|
783
|
-
this.convertStreamingErrorToStructured(err),
|
|
784
|
-
);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
// Track the error
|
|
788
|
-
await this.error(
|
|
789
|
-
"error",
|
|
790
|
-
{
|
|
791
|
-
threadId,
|
|
792
|
-
runId,
|
|
793
|
-
source: "runtime",
|
|
794
|
-
request: {
|
|
795
|
-
operation: "processRuntimeRequest",
|
|
796
|
-
method: "POST",
|
|
797
|
-
url: url,
|
|
798
|
-
startTime: requestStartTime,
|
|
799
|
-
},
|
|
800
|
-
response: {
|
|
801
|
-
endTime: Date.now(),
|
|
802
|
-
latency: Date.now() - requestStartTime,
|
|
803
|
-
},
|
|
804
|
-
agent: agentSession ? { name: agentSession.agentName } : undefined,
|
|
805
|
-
technical: {
|
|
806
|
-
environment: process.env.NODE_ENV,
|
|
807
|
-
stackTrace: error instanceof Error ? error.stack : undefined,
|
|
808
|
-
},
|
|
809
|
-
},
|
|
810
|
-
structuredError,
|
|
811
|
-
publicApiKey,
|
|
812
|
-
);
|
|
813
|
-
|
|
814
|
-
throw structuredError;
|
|
815
|
-
}
|
|
368
|
+
return acc;
|
|
369
|
+
}, {});
|
|
816
370
|
}
|
|
817
371
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
372
|
+
handleServiceAdapter(serviceAdapter: CopilotServiceAdapter) {
|
|
373
|
+
this.runtimeArgs.agents = Promise.resolve(this.runtimeArgs.agents ?? {}).then(
|
|
374
|
+
async (agents) => {
|
|
375
|
+
let agentsList = agents;
|
|
376
|
+
const isAgentsListEmpty = !Object.keys(agents).length;
|
|
377
|
+
const hasServiceAdapter = Boolean(serviceAdapter);
|
|
378
|
+
const illegalServiceAdapterNames = ["EmptyAdapter"];
|
|
379
|
+
const serviceAdapterCanBeUsedForAgent = !illegalServiceAdapterNames.includes(
|
|
380
|
+
serviceAdapter.name,
|
|
381
|
+
);
|
|
829
382
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (endpoint.type === EndpointType.LangGraphPlatform) {
|
|
835
|
-
const propertyHeaders = graphqlContext.properties.authorization
|
|
836
|
-
? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
|
|
837
|
-
: null;
|
|
838
|
-
|
|
839
|
-
const client = new LangGraphClient({
|
|
840
|
-
apiUrl: endpoint.deploymentUrl,
|
|
841
|
-
apiKey: endpoint.langsmithApiKey,
|
|
842
|
-
defaultHeaders: { ...propertyHeaders },
|
|
383
|
+
if (isAgentsListEmpty && (!hasServiceAdapter || !serviceAdapterCanBeUsedForAgent)) {
|
|
384
|
+
throw new CopilotKitMisuseError({
|
|
385
|
+
message:
|
|
386
|
+
"No default agent provided. Please provide a default agent in the runtime config.",
|
|
843
387
|
});
|
|
844
|
-
let data: Array<{ assistant_id: string; graph_id: string }> | { detail: string } = [];
|
|
845
|
-
try {
|
|
846
|
-
data = await client.assistants.search();
|
|
847
|
-
|
|
848
|
-
if (data && "detail" in data && (data.detail as string).toLowerCase() === "not found") {
|
|
849
|
-
throw new CopilotKitAgentDiscoveryError({ availableAgents: this.availableAgents });
|
|
850
|
-
}
|
|
851
|
-
} catch (e) {
|
|
852
|
-
throw new CopilotKitMisuseError({
|
|
853
|
-
message: `
|
|
854
|
-
Failed to find or contact remote endpoint at url ${endpoint.deploymentUrl}.
|
|
855
|
-
Make sure the API is running and that it's indeed a LangGraph platform url.
|
|
856
|
-
|
|
857
|
-
See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
const endpointAgents = data.map((entry) => ({
|
|
861
|
-
name: entry.graph_id,
|
|
862
|
-
id: entry.assistant_id,
|
|
863
|
-
description: "",
|
|
864
|
-
endpoint,
|
|
865
|
-
}));
|
|
866
|
-
return [...agents, ...endpointAgents];
|
|
867
388
|
}
|
|
868
389
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
description: string;
|
|
873
|
-
}>;
|
|
874
|
-
}
|
|
875
|
-
const cpkEndpoint = endpoint as CopilotKitEndpoint;
|
|
876
|
-
const fetchUrl = `${endpoint.url}/info`;
|
|
877
|
-
try {
|
|
878
|
-
const response = await fetchWithRetry(fetchUrl, {
|
|
879
|
-
method: "POST",
|
|
880
|
-
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
|
|
881
|
-
body: JSON.stringify({ properties: graphqlContext.properties }),
|
|
390
|
+
if (isAgentsListEmpty) {
|
|
391
|
+
agentsList.default = new BasicAgent({
|
|
392
|
+
model: `${serviceAdapter.provider}/${serviceAdapter.model}`,
|
|
882
393
|
});
|
|
883
|
-
|
|
884
|
-
if (response.status === 404) {
|
|
885
|
-
throw new CopilotKitApiDiscoveryError({ url: fetchUrl });
|
|
886
|
-
}
|
|
887
|
-
throw new ResolvedCopilotKitError({
|
|
888
|
-
status: response.status,
|
|
889
|
-
url: fetchUrl,
|
|
890
|
-
isRemoteEndpoint: true,
|
|
891
|
-
});
|
|
892
|
-
}
|
|
394
|
+
}
|
|
893
395
|
|
|
894
|
-
|
|
895
|
-
const
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}));
|
|
901
|
-
return [...agents, ...endpointAgents];
|
|
902
|
-
} catch (error) {
|
|
903
|
-
if (error instanceof CopilotKitError) {
|
|
904
|
-
throw error;
|
|
905
|
-
}
|
|
906
|
-
throw new CopilotKitLowLevelError({ error: error as Error, url: fetchUrl });
|
|
396
|
+
if (this.params.actions?.length) {
|
|
397
|
+
const mcpTools = await this.getToolsFromMCP();
|
|
398
|
+
agentsList = this.assignToolsToAgents(agents, [
|
|
399
|
+
...this.getToolsFromActions(this.params.actions),
|
|
400
|
+
...mcpTools,
|
|
401
|
+
]);
|
|
907
402
|
}
|
|
403
|
+
|
|
404
|
+
return agentsList;
|
|
908
405
|
},
|
|
909
|
-
Promise.resolve([]),
|
|
910
406
|
);
|
|
911
|
-
|
|
912
|
-
return agents;
|
|
913
407
|
}
|
|
914
408
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
async loadAgentState(
|
|
924
|
-
graphqlContext: GraphQLContext,
|
|
925
|
-
threadId: string,
|
|
926
|
-
agentName: string,
|
|
927
|
-
): Promise<LoadAgentStateResponse> {
|
|
928
|
-
const agents = await this.getAllAgents(graphqlContext);
|
|
929
|
-
|
|
930
|
-
const agent = agents.find((agent) => agent.name === agentName);
|
|
931
|
-
if (!agent) {
|
|
932
|
-
throw new Error("Agent not found");
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (
|
|
936
|
-
"endpoint" in agent &&
|
|
937
|
-
(agent.endpoint.type === EndpointType.CopilotKit || !("type" in agent.endpoint))
|
|
938
|
-
) {
|
|
939
|
-
const cpkEndpoint = agent.endpoint as CopilotKitEndpoint;
|
|
940
|
-
const fetchUrl = `${cpkEndpoint.url}/agents/state`;
|
|
941
|
-
try {
|
|
942
|
-
const response = await fetchWithRetry(fetchUrl, {
|
|
943
|
-
method: "POST",
|
|
944
|
-
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
|
|
945
|
-
body: JSON.stringify({
|
|
946
|
-
properties: graphqlContext.properties,
|
|
947
|
-
threadId,
|
|
948
|
-
name: agentName,
|
|
949
|
-
}),
|
|
950
|
-
});
|
|
951
|
-
if (!response.ok) {
|
|
952
|
-
if (response.status === 404) {
|
|
953
|
-
throw new CopilotKitApiDiscoveryError({ url: fetchUrl });
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// Extract semantic error information from response body
|
|
957
|
-
let errorMessage = `HTTP ${response.status} error`;
|
|
958
|
-
try {
|
|
959
|
-
const errorBody = await response.text();
|
|
960
|
-
const parsedError = JSON.parse(errorBody);
|
|
961
|
-
if (parsedError.error && typeof parsedError.error === "string") {
|
|
962
|
-
errorMessage = parsedError.error;
|
|
963
|
-
}
|
|
964
|
-
} catch {
|
|
965
|
-
// If parsing fails, fall back to generic message
|
|
966
|
-
}
|
|
409
|
+
// Receive this.params.action and turn it into the AbstractAgent tools
|
|
410
|
+
private getToolsFromActions(
|
|
411
|
+
actions: ActionsConfiguration<any>,
|
|
412
|
+
): BasicAgentConfiguration["tools"] {
|
|
413
|
+
// Resolve actions to an array (handle function case)
|
|
414
|
+
const actionsArray =
|
|
415
|
+
typeof actions === "function" ? actions({ properties: {}, url: undefined }) : actions;
|
|
967
416
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
message: errorMessage,
|
|
973
|
-
});
|
|
974
|
-
}
|
|
417
|
+
// Convert each Action to a ToolDefinition
|
|
418
|
+
return actionsArray.map((action) => {
|
|
419
|
+
// Convert JSON schema to Zod schema
|
|
420
|
+
const zodSchema = getZodParameters(action.parameters || []);
|
|
975
421
|
|
|
976
|
-
const data: LoadAgentStateResponse = await response.json();
|
|
977
|
-
|
|
978
|
-
return {
|
|
979
|
-
...data,
|
|
980
|
-
state: JSON.stringify(data.state),
|
|
981
|
-
messages: JSON.stringify(data.messages),
|
|
982
|
-
};
|
|
983
|
-
} catch (error) {
|
|
984
|
-
if (error instanceof CopilotKitError) {
|
|
985
|
-
throw error;
|
|
986
|
-
}
|
|
987
|
-
throw new CopilotKitLowLevelError({ error, url: fetchUrl });
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
const propertyHeaders = graphqlContext.properties.authorization
|
|
992
|
-
? { authorization: `Bearer ${graphqlContext.properties.authorization}` }
|
|
993
|
-
: null;
|
|
994
|
-
|
|
995
|
-
let state: any = {};
|
|
996
|
-
try {
|
|
997
|
-
let client: LangGraphClient | null;
|
|
998
|
-
if ("endpoint" in agent && agent.endpoint.type === EndpointType.LangGraphPlatform) {
|
|
999
|
-
client = new LangGraphClient({
|
|
1000
|
-
apiUrl: agent.endpoint.deploymentUrl,
|
|
1001
|
-
apiKey: agent.endpoint.langsmithApiKey,
|
|
1002
|
-
defaultHeaders: { ...propertyHeaders },
|
|
1003
|
-
});
|
|
1004
|
-
} else {
|
|
1005
|
-
const aguiAgent = graphqlContext._copilotkit.runtime.agents[agent.name] as LangGraphAgent;
|
|
1006
|
-
if (!aguiAgent) {
|
|
1007
|
-
throw new Error(`Agent: ${agent.name} could not be resolved`);
|
|
1008
|
-
}
|
|
1009
|
-
// @ts-expect-error -- both clients are the same
|
|
1010
|
-
client = aguiAgent.client ?? null;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
state = client ? ((await client.threads.getState(threadId)).values as any) : {};
|
|
1014
|
-
} catch (error) {
|
|
1015
|
-
// All errors from agent state loading are user configuration issues
|
|
1016
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1017
|
-
const errorStatus = error?.response?.status || error?.status;
|
|
1018
|
-
|
|
1019
|
-
if (errorStatus === 404) {
|
|
1020
|
-
state = {};
|
|
1021
|
-
} else {
|
|
1022
|
-
// Log user configuration errors at debug level to reduce noise
|
|
1023
|
-
console.debug(`Agent '${agentName}' configuration issue: ${errorMessage}`);
|
|
1024
|
-
|
|
1025
|
-
// Throw a configuration error - all agent state loading failures are user setup issues
|
|
1026
|
-
throw new ResolvedCopilotKitError({
|
|
1027
|
-
status: 400,
|
|
1028
|
-
message: `Agent '${agentName}' failed to execute: ${errorMessage}`,
|
|
1029
|
-
code: CopilotKitErrorCode.CONFIGURATION_ERROR,
|
|
1030
|
-
});
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
if (Object.keys(state).length === 0) {
|
|
1035
422
|
return {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
messages: JSON.stringify([]),
|
|
1040
|
-
};
|
|
1041
|
-
} else {
|
|
1042
|
-
const { messages, ...stateWithoutMessages } = state;
|
|
1043
|
-
const copilotkitMessages = langchainMessagesToCopilotKit(messages);
|
|
1044
|
-
return {
|
|
1045
|
-
threadId: threadId || "",
|
|
1046
|
-
threadExists: true,
|
|
1047
|
-
state: JSON.stringify(stateWithoutMessages),
|
|
1048
|
-
messages: JSON.stringify(copilotkitMessages),
|
|
423
|
+
name: action.name,
|
|
424
|
+
description: action.description || "",
|
|
425
|
+
parameters: zodSchema,
|
|
1049
426
|
};
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
throw new Error(`Agent: ${agent.name} could not be resolved`);
|
|
427
|
+
});
|
|
1053
428
|
}
|
|
1054
429
|
|
|
1055
|
-
private
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
graphqlContext,
|
|
1062
|
-
agentSession,
|
|
1063
|
-
threadId: threadIdFromRequest,
|
|
1064
|
-
metaEvents,
|
|
1065
|
-
publicApiKey,
|
|
1066
|
-
forwardedParameters,
|
|
1067
|
-
context,
|
|
1068
|
-
} = request;
|
|
1069
|
-
const { agentName, nodeName } = agentSession;
|
|
1070
|
-
|
|
1071
|
-
// Track request start time for observability
|
|
1072
|
-
const requestStartTime = Date.now();
|
|
1073
|
-
// For storing streamed chunks if progressive logging is enabled
|
|
1074
|
-
const streamedChunks: any[] = [];
|
|
1075
|
-
|
|
1076
|
-
// for backwards compatibility, deal with the case when no threadId is provided
|
|
1077
|
-
const threadId = threadIdFromRequest ?? agentSession.threadId;
|
|
1078
|
-
|
|
1079
|
-
// Track agent request start
|
|
1080
|
-
await this.error(
|
|
1081
|
-
"agent_state",
|
|
1082
|
-
{
|
|
1083
|
-
threadId,
|
|
1084
|
-
source: "agent",
|
|
1085
|
-
request: {
|
|
1086
|
-
operation: "processAgentRequest",
|
|
1087
|
-
method: "POST",
|
|
1088
|
-
startTime: requestStartTime,
|
|
1089
|
-
},
|
|
1090
|
-
agent: {
|
|
1091
|
-
name: agentName,
|
|
1092
|
-
nodeName: nodeName,
|
|
1093
|
-
},
|
|
1094
|
-
messages: {
|
|
1095
|
-
input: rawMessages,
|
|
1096
|
-
messageCount: rawMessages.length,
|
|
1097
|
-
},
|
|
1098
|
-
technical: {
|
|
1099
|
-
environment: process.env.NODE_ENV,
|
|
1100
|
-
},
|
|
1101
|
-
},
|
|
1102
|
-
undefined,
|
|
1103
|
-
publicApiKey,
|
|
1104
|
-
);
|
|
1105
|
-
|
|
1106
|
-
const serverSideActions = await this.getServerSideActions(request);
|
|
1107
|
-
|
|
1108
|
-
const messages = convertGqlInputToMessages(rawMessages);
|
|
1109
|
-
|
|
1110
|
-
const currentAgent = serverSideActions.find(
|
|
1111
|
-
(action) => action.name === agentName && isRemoteAgentAction(action),
|
|
1112
|
-
) as RemoteAgentAction;
|
|
1113
|
-
|
|
1114
|
-
if (!currentAgent) {
|
|
1115
|
-
throw new CopilotKitAgentDiscoveryError({ agentName, availableAgents: this.availableAgents });
|
|
430
|
+
private assignToolsToAgents(
|
|
431
|
+
agents: Record<string, AbstractAgent>,
|
|
432
|
+
tools: BasicAgentConfiguration["tools"],
|
|
433
|
+
): Record<string, AbstractAgent> {
|
|
434
|
+
if (!tools?.length) {
|
|
435
|
+
return agents;
|
|
1116
436
|
}
|
|
1117
437
|
|
|
1118
|
-
|
|
1119
|
-
// 1. Regular (non-agent) actions
|
|
1120
|
-
// 2. Other agents' actions (but prevent self-calls to avoid infinite loops)
|
|
1121
|
-
const availableActionsForCurrentAgent: ActionInput[] = serverSideActions
|
|
1122
|
-
.filter(
|
|
1123
|
-
(action) =>
|
|
1124
|
-
// Case 1: Keep all regular (non-agent) actions
|
|
1125
|
-
!isRemoteAgentAction(action) ||
|
|
1126
|
-
// Case 2: For agent actions, keep all except self (prevent infinite loops)
|
|
1127
|
-
(isRemoteAgentAction(action) && action.name !== agentName) /* prevent self-calls */,
|
|
1128
|
-
)
|
|
1129
|
-
.map((action) => ({
|
|
1130
|
-
name: action.name,
|
|
1131
|
-
description: action.description,
|
|
1132
|
-
jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters)),
|
|
1133
|
-
}));
|
|
438
|
+
const enrichedAgents: Record<string, AbstractAgent> = { ...agents };
|
|
1134
439
|
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
]);
|
|
440
|
+
for (const [agentId, agent] of Object.entries(enrichedAgents)) {
|
|
441
|
+
const existingConfig = (Reflect.get(agent, "config") ?? {}) as BasicAgentConfiguration;
|
|
442
|
+
const existingTools = existingConfig.tools ?? [];
|
|
1139
443
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
threadId,
|
|
1145
|
-
runId: undefined,
|
|
1146
|
-
model: forwardedParameters?.model,
|
|
1147
|
-
messages,
|
|
1148
|
-
actions: allAvailableActions,
|
|
1149
|
-
forwardedParameters,
|
|
1150
|
-
timestamp: requestStartTime,
|
|
1151
|
-
provider: "agent",
|
|
1152
|
-
agentName, // Add agent-specific context
|
|
1153
|
-
nodeName,
|
|
1154
|
-
};
|
|
444
|
+
const updatedConfig: BasicAgentConfiguration = {
|
|
445
|
+
...existingConfig,
|
|
446
|
+
tools: [...existingTools, ...tools],
|
|
447
|
+
};
|
|
1155
448
|
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
console.error("Error logging agent request:", error);
|
|
1159
|
-
}
|
|
449
|
+
Reflect.set(agent, "config", updatedConfig);
|
|
450
|
+
enrichedAgents[agentId] = agent;
|
|
1160
451
|
}
|
|
1161
452
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
runId: undefined,
|
|
1165
|
-
inputMessages: messages,
|
|
1166
|
-
properties: graphqlContext.properties,
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
try {
|
|
1170
|
-
const eventSource = new RuntimeEventSource({
|
|
1171
|
-
errorHandler: async (error, context) => {
|
|
1172
|
-
await this.error("error", context, error, publicApiKey);
|
|
1173
|
-
},
|
|
1174
|
-
errorContext: {
|
|
1175
|
-
threadId,
|
|
1176
|
-
source: "agent",
|
|
1177
|
-
request: {
|
|
1178
|
-
operation: "processAgentRequest",
|
|
1179
|
-
method: "POST",
|
|
1180
|
-
startTime: requestStartTime,
|
|
1181
|
-
},
|
|
1182
|
-
agent: {
|
|
1183
|
-
name: agentName,
|
|
1184
|
-
nodeName: nodeName,
|
|
1185
|
-
},
|
|
1186
|
-
technical: {
|
|
1187
|
-
environment: process.env.NODE_ENV,
|
|
1188
|
-
},
|
|
1189
|
-
},
|
|
1190
|
-
});
|
|
1191
|
-
const stream = await currentAgent.remoteAgentHandler({
|
|
1192
|
-
name: agentName,
|
|
1193
|
-
threadId,
|
|
1194
|
-
nodeName,
|
|
1195
|
-
metaEvents,
|
|
1196
|
-
actionInputsWithoutAgents: allAvailableActions,
|
|
1197
|
-
});
|
|
1198
|
-
|
|
1199
|
-
// Add progressive observability if enabled
|
|
1200
|
-
if (this.observability?.enabled && this.observability.progressive && publicApiKey) {
|
|
1201
|
-
// Wrap the stream function to intercept events for observability without changing core logic
|
|
1202
|
-
const originalStream = eventSource.stream.bind(eventSource);
|
|
1203
|
-
|
|
1204
|
-
eventSource.stream = async (callback) => {
|
|
1205
|
-
await originalStream(async (eventStream$) => {
|
|
1206
|
-
// Create subscription to capture streaming events
|
|
1207
|
-
eventStream$.subscribe({
|
|
1208
|
-
next: (event) => {
|
|
1209
|
-
// Only log content chunks
|
|
1210
|
-
if (event.type === RuntimeEventTypes.TextMessageContent) {
|
|
1211
|
-
// Store the chunk
|
|
1212
|
-
streamedChunks.push(event.content);
|
|
1213
|
-
|
|
1214
|
-
// Log each chunk separately for progressive mode
|
|
1215
|
-
try {
|
|
1216
|
-
const progressiveData: LLMResponseData = {
|
|
1217
|
-
threadId: threadId || "",
|
|
1218
|
-
runId: undefined,
|
|
1219
|
-
model: forwardedParameters?.model,
|
|
1220
|
-
output: event.content,
|
|
1221
|
-
latency: Date.now() - requestStartTime,
|
|
1222
|
-
timestamp: Date.now(),
|
|
1223
|
-
provider: "agent",
|
|
1224
|
-
isProgressiveChunk: true,
|
|
1225
|
-
agentName,
|
|
1226
|
-
nodeName,
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
// Use Promise to handle async logger without awaiting
|
|
1230
|
-
Promise.resolve()
|
|
1231
|
-
.then(() => {
|
|
1232
|
-
this.observability.hooks.handleResponse(progressiveData);
|
|
1233
|
-
})
|
|
1234
|
-
.catch((error) => {
|
|
1235
|
-
console.error("Error in progressive agent logging:", error);
|
|
1236
|
-
});
|
|
1237
|
-
} catch (error) {
|
|
1238
|
-
console.error("Error preparing progressive agent log data:", error);
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
},
|
|
1242
|
-
});
|
|
1243
|
-
|
|
1244
|
-
// Call the original callback with the event stream
|
|
1245
|
-
await callback(eventStream$);
|
|
1246
|
-
});
|
|
1247
|
-
};
|
|
1248
|
-
}
|
|
453
|
+
return enrichedAgents;
|
|
454
|
+
}
|
|
1249
455
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
console.error("Error logging agent error:", logError);
|
|
1272
|
-
}
|
|
456
|
+
private createOnBeforeRequestHandler(
|
|
457
|
+
params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
|
|
458
|
+
) {
|
|
459
|
+
return async (hookParams: BeforeRequestMiddlewareFnParameters[0]) => {
|
|
460
|
+
// TODO: get public api key and run with expected data
|
|
461
|
+
// if (this.observability?.enabled && this.params.publicApiKey) {
|
|
462
|
+
// this.logObservabilityBeforeRequest()
|
|
463
|
+
// }
|
|
464
|
+
|
|
465
|
+
// TODO: replace hooksParams top argument type with BeforeRequestMiddlewareParameters when exported
|
|
466
|
+
params?.beforeRequestMiddleware?.(hookParams);
|
|
467
|
+
|
|
468
|
+
if (params?.middleware?.onBeforeRequest) {
|
|
469
|
+
const { request, runtime, path } = hookParams;
|
|
470
|
+
const body = (await readBody(request)) as RunAgentInput;
|
|
471
|
+
const gqlMessages = (aguiToGQL(body.messages) as Message[]).reduce(
|
|
472
|
+
(acc, msg) => {
|
|
473
|
+
if ("role" in msg && msg.role === "user") {
|
|
474
|
+
acc.inputMessages.push(msg);
|
|
475
|
+
} else {
|
|
476
|
+
acc.outputMessages.push(msg);
|
|
1273
477
|
}
|
|
1274
|
-
|
|
1275
|
-
// Preserve structured CopilotKit errors, only convert unstructured errors
|
|
1276
|
-
const structuredError = ensureStructuredError(err, (error) =>
|
|
1277
|
-
this.convertStreamingErrorToStructured(error),
|
|
1278
|
-
);
|
|
1279
|
-
|
|
1280
|
-
// Track streaming errors
|
|
1281
|
-
await this.error(
|
|
1282
|
-
"error",
|
|
1283
|
-
{
|
|
1284
|
-
threadId,
|
|
1285
|
-
source: "agent",
|
|
1286
|
-
request: {
|
|
1287
|
-
operation: "processAgentRequest",
|
|
1288
|
-
method: "POST",
|
|
1289
|
-
startTime: requestStartTime,
|
|
1290
|
-
},
|
|
1291
|
-
response: {
|
|
1292
|
-
endTime: Date.now(),
|
|
1293
|
-
latency: Date.now() - requestStartTime,
|
|
1294
|
-
},
|
|
1295
|
-
agent: {
|
|
1296
|
-
name: agentName,
|
|
1297
|
-
nodeName: nodeName,
|
|
1298
|
-
},
|
|
1299
|
-
technical: {
|
|
1300
|
-
environment: process.env.NODE_ENV,
|
|
1301
|
-
stackTrace: err instanceof Error ? err.stack : undefined,
|
|
1302
|
-
},
|
|
1303
|
-
},
|
|
1304
|
-
structuredError,
|
|
1305
|
-
publicApiKey,
|
|
1306
|
-
);
|
|
1307
|
-
|
|
1308
|
-
eventStream$.error(structuredError);
|
|
1309
|
-
eventStream$.complete();
|
|
478
|
+
return acc;
|
|
1310
479
|
},
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
.
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
runId: undefined,
|
|
1322
|
-
model: forwardedParameters?.model,
|
|
1323
|
-
// Use collected chunks for progressive mode or outputMessages for regular mode
|
|
1324
|
-
output: this.observability.progressive ? streamedChunks : outputMessages,
|
|
1325
|
-
latency: Date.now() - requestStartTime,
|
|
1326
|
-
timestamp: Date.now(),
|
|
1327
|
-
provider: "agent",
|
|
1328
|
-
isFinalResponse: true,
|
|
1329
|
-
agentName,
|
|
1330
|
-
nodeName,
|
|
1331
|
-
};
|
|
1332
|
-
|
|
1333
|
-
try {
|
|
1334
|
-
this.observability.hooks.handleResponse(responseData);
|
|
1335
|
-
} catch (logError) {
|
|
1336
|
-
console.error("Error logging agent response:", logError);
|
|
1337
|
-
}
|
|
1338
|
-
})
|
|
1339
|
-
.catch((error) => {
|
|
1340
|
-
console.error("Failed to get output messages for agent logging:", error);
|
|
1341
|
-
});
|
|
480
|
+
{ inputMessages: [] as Message[], outputMessages: [] as Message[] },
|
|
481
|
+
);
|
|
482
|
+
const { inputMessages, outputMessages } = gqlMessages;
|
|
483
|
+
params.middleware.onBeforeRequest({
|
|
484
|
+
threadId: body.threadId,
|
|
485
|
+
runId: body.runId,
|
|
486
|
+
inputMessages,
|
|
487
|
+
properties: body.forwardedProps,
|
|
488
|
+
url: request.url,
|
|
489
|
+
} satisfies OnBeforeRequestOptions);
|
|
1342
490
|
}
|
|
491
|
+
};
|
|
492
|
+
}
|
|
1343
493
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
serverSideActions,
|
|
1361
|
-
actionInputsWithoutAgents: allAvailableActions,
|
|
1362
|
-
};
|
|
1363
|
-
} catch (error) {
|
|
1364
|
-
// Log error with observability if enabled
|
|
1365
|
-
if (this.observability?.enabled && publicApiKey) {
|
|
1366
|
-
try {
|
|
1367
|
-
const errorData: LLMErrorData = {
|
|
1368
|
-
threadId,
|
|
1369
|
-
runId: undefined,
|
|
1370
|
-
model: forwardedParameters?.model,
|
|
1371
|
-
error: error instanceof Error ? error : String(error),
|
|
1372
|
-
timestamp: Date.now(),
|
|
1373
|
-
latency: Date.now() - requestStartTime,
|
|
1374
|
-
provider: "agent",
|
|
1375
|
-
agentName,
|
|
1376
|
-
nodeName,
|
|
1377
|
-
};
|
|
1378
|
-
|
|
1379
|
-
await this.observability.hooks.handleError(errorData);
|
|
1380
|
-
} catch (logError) {
|
|
1381
|
-
console.error("Error logging agent error:", logError);
|
|
1382
|
-
}
|
|
494
|
+
private createOnAfterRequestHandler(
|
|
495
|
+
params?: CopilotRuntimeConstructorParams & PartialBy<CopilotRuntimeOptions, "agents">,
|
|
496
|
+
) {
|
|
497
|
+
return async (hookParams: AfterRequestMiddlewareFnParameters[0]) => {
|
|
498
|
+
// TODO: get public api key and run with expected data
|
|
499
|
+
// if (this.observability?.enabled && publicApiKey) {
|
|
500
|
+
// this.logObservabilityAfterRequest()
|
|
501
|
+
// }
|
|
502
|
+
|
|
503
|
+
// TODO: replace hooksParams top argument type with AfterRequestMiddlewareParameters when exported
|
|
504
|
+
params?.afterRequestMiddleware?.(hookParams);
|
|
505
|
+
|
|
506
|
+
if (params?.middleware?.onAfterRequest) {
|
|
507
|
+
// TODO: provide old expected params here when available
|
|
508
|
+
// @ts-expect-error -- missing arguments.
|
|
509
|
+
params.middleware.onAfterRequest({});
|
|
1383
510
|
}
|
|
1384
|
-
|
|
1385
|
-
// Ensure error is structured
|
|
1386
|
-
const structuredError = ensureStructuredError(error, (err) =>
|
|
1387
|
-
this.convertStreamingErrorToStructured(err),
|
|
1388
|
-
);
|
|
1389
|
-
|
|
1390
|
-
// Track the agent error
|
|
1391
|
-
await this.error(
|
|
1392
|
-
"error",
|
|
1393
|
-
{
|
|
1394
|
-
threadId,
|
|
1395
|
-
source: "agent",
|
|
1396
|
-
request: {
|
|
1397
|
-
operation: "processAgentRequest",
|
|
1398
|
-
method: "POST",
|
|
1399
|
-
startTime: requestStartTime,
|
|
1400
|
-
},
|
|
1401
|
-
response: {
|
|
1402
|
-
endTime: Date.now(),
|
|
1403
|
-
latency: Date.now() - requestStartTime,
|
|
1404
|
-
},
|
|
1405
|
-
agent: {
|
|
1406
|
-
name: agentName,
|
|
1407
|
-
nodeName: nodeName,
|
|
1408
|
-
},
|
|
1409
|
-
technical: {
|
|
1410
|
-
environment: process.env.NODE_ENV,
|
|
1411
|
-
stackTrace: error instanceof Error ? error.stack : undefined,
|
|
1412
|
-
},
|
|
1413
|
-
},
|
|
1414
|
-
structuredError,
|
|
1415
|
-
publicApiKey,
|
|
1416
|
-
);
|
|
1417
|
-
|
|
1418
|
-
console.error("Error getting response:", error);
|
|
1419
|
-
throw structuredError;
|
|
1420
|
-
}
|
|
511
|
+
};
|
|
1421
512
|
}
|
|
1422
513
|
|
|
1423
|
-
|
|
1424
|
-
const { graphqlContext, messages: rawMessages, agentStates, url } = request;
|
|
514
|
+
// Observability Methods
|
|
1425
515
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
console.error("Error loading langserve chain:", error);
|
|
1435
|
-
}
|
|
516
|
+
/**
|
|
517
|
+
* Log LLM request if observability is enabled
|
|
518
|
+
*/
|
|
519
|
+
private async logObservabilityBeforeRequest(requestData: LLMRequestData): Promise<void> {
|
|
520
|
+
try {
|
|
521
|
+
await this.observability.hooks.handleRequest(requestData);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.error("Error logging LLM request:", error);
|
|
1436
524
|
}
|
|
525
|
+
}
|
|
1437
526
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
const effectiveEndpointsMap = new Map<string, MCPEndpointConfig>();
|
|
1472
|
-
|
|
1473
|
-
// First add base endpoints (from runtime configuration)
|
|
1474
|
-
[...baseEndpoints].forEach((ep) => {
|
|
1475
|
-
if (ep && ep.endpoint) {
|
|
1476
|
-
effectiveEndpointsMap.set(ep.endpoint, ep);
|
|
1477
|
-
}
|
|
1478
|
-
});
|
|
1479
|
-
|
|
1480
|
-
// Then add request endpoints (from frontend), which will override duplicates
|
|
1481
|
-
[...requestEndpoints].forEach((ep) => {
|
|
1482
|
-
if (ep && ep.endpoint) {
|
|
1483
|
-
effectiveEndpointsMap.set(ep.endpoint, ep);
|
|
1484
|
-
}
|
|
1485
|
-
});
|
|
1486
|
-
|
|
1487
|
-
const effectiveEndpoints = Array.from(effectiveEndpointsMap.values());
|
|
1488
|
-
|
|
1489
|
-
// 2. Fetch/Cache actions for effective endpoints
|
|
1490
|
-
for (const config of effectiveEndpoints) {
|
|
1491
|
-
const endpointUrl = config.endpoint;
|
|
1492
|
-
let actionsForEndpoint: Action<any>[] | undefined = this.mcpActionCache.get(endpointUrl);
|
|
527
|
+
/**
|
|
528
|
+
* Log final LLM response after request completes
|
|
529
|
+
*/
|
|
530
|
+
private logObservabilityAfterRequest(
|
|
531
|
+
outputMessagesPromise: Promise<Message[]>,
|
|
532
|
+
baseData: {
|
|
533
|
+
threadId: string;
|
|
534
|
+
runId?: string;
|
|
535
|
+
model?: string;
|
|
536
|
+
provider?: string;
|
|
537
|
+
agentName?: string;
|
|
538
|
+
nodeName?: string;
|
|
539
|
+
},
|
|
540
|
+
streamedChunks: any[],
|
|
541
|
+
requestStartTime: number,
|
|
542
|
+
publicApiKey?: string,
|
|
543
|
+
): void {
|
|
544
|
+
try {
|
|
545
|
+
outputMessagesPromise
|
|
546
|
+
.then((outputMessages) => {
|
|
547
|
+
const responseData: LLMResponseData = {
|
|
548
|
+
threadId: baseData.threadId,
|
|
549
|
+
runId: baseData.runId,
|
|
550
|
+
model: baseData.model,
|
|
551
|
+
// Use collected chunks for progressive mode or outputMessages for regular mode
|
|
552
|
+
output: this.observability.progressive ? streamedChunks : outputMessages,
|
|
553
|
+
latency: Date.now() - requestStartTime,
|
|
554
|
+
timestamp: Date.now(),
|
|
555
|
+
provider: baseData.provider,
|
|
556
|
+
isFinalResponse: true,
|
|
557
|
+
agentName: baseData.agentName,
|
|
558
|
+
nodeName: baseData.nodeName,
|
|
559
|
+
};
|
|
1493
560
|
|
|
1494
|
-
if (!actionsForEndpoint) {
|
|
1495
|
-
// Not cached, fetch now
|
|
1496
|
-
let client: MCPClient | null = null;
|
|
1497
561
|
try {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
this.mcpActionCache.set(endpointUrl, actionsForEndpoint); // Store in cache
|
|
1502
|
-
} catch (error) {
|
|
1503
|
-
console.error(
|
|
1504
|
-
`MCP: Failed to fetch tools from endpoint ${endpointUrl}. Skipping. Error:`,
|
|
1505
|
-
error,
|
|
1506
|
-
);
|
|
1507
|
-
actionsForEndpoint = []; // Assign empty array on error to prevent re-fetching constantly
|
|
1508
|
-
this.mcpActionCache.set(endpointUrl, actionsForEndpoint); // Cache the failure (empty array)
|
|
562
|
+
this.observability.hooks.handleResponse(responseData);
|
|
563
|
+
} catch (logError) {
|
|
564
|
+
console.error("Error logging LLM response:", logError);
|
|
1509
565
|
}
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
|
|
566
|
+
})
|
|
567
|
+
.catch((error) => {
|
|
568
|
+
console.error("Failed to get output messages for logging:", error);
|
|
569
|
+
});
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error("Error setting up logging for LLM response:", error);
|
|
1513
572
|
}
|
|
1514
|
-
// --- Dynamic MCP Action Fetching ---
|
|
1515
|
-
|
|
1516
|
-
// Combine all action sources, including the dynamically fetched MCP actions
|
|
1517
|
-
return [
|
|
1518
|
-
...configuredActions,
|
|
1519
|
-
...langserveFunctions,
|
|
1520
|
-
...remoteActions,
|
|
1521
|
-
...requestSpecificMCPActions,
|
|
1522
|
-
];
|
|
1523
573
|
}
|
|
1524
574
|
|
|
1525
|
-
//
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
575
|
+
// Resolve MCP tools to BasicAgent tool definitions
|
|
576
|
+
// Optionally accepts request-scoped properties to merge request-provided mcpServers
|
|
577
|
+
private async getToolsFromMCP(options?: {
|
|
578
|
+
properties?: Record<string, unknown>;
|
|
579
|
+
}): Promise<BasicAgentConfiguration["tools"]> {
|
|
580
|
+
const runtimeMcpServers = (this.params?.mcpServers ?? []) as MCPEndpointConfig[];
|
|
581
|
+
const createMCPClient = this.params?.createMCPClient as CreateMCPClientFunction | undefined;
|
|
582
|
+
|
|
583
|
+
// If no runtime config and no request overrides, nothing to do
|
|
584
|
+
const requestMcpServers = ((
|
|
585
|
+
options?.properties as { mcpServers?: MCPEndpointConfig[] } | undefined
|
|
586
|
+
)?.mcpServers ??
|
|
587
|
+
(options?.properties as { mcpEndpoints?: MCPEndpointConfig[] } | undefined)?.mcpEndpoints ??
|
|
588
|
+
[]) as MCPEndpointConfig[];
|
|
589
|
+
|
|
590
|
+
const hasAnyServers =
|
|
591
|
+
(runtimeMcpServers?.length ?? 0) > 0 || (requestMcpServers?.length ?? 0) > 0;
|
|
592
|
+
if (!hasAnyServers) {
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
1535
595
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
if (
|
|
1542
|
-
error?.message?.includes("fetch failed") ||
|
|
1543
|
-
error?.message?.includes("ECONNREFUSED") ||
|
|
1544
|
-
error?.message?.includes("ENOTFOUND") ||
|
|
1545
|
-
error?.message?.includes("ETIMEDOUT") ||
|
|
1546
|
-
error?.message?.includes("terminated") ||
|
|
1547
|
-
error?.cause?.code === "UND_ERR_SOCKET" ||
|
|
1548
|
-
error?.message?.includes("other side closed") ||
|
|
1549
|
-
error?.code === "UND_ERR_SOCKET"
|
|
1550
|
-
) {
|
|
1551
|
-
return new CopilotKitLowLevelError({
|
|
1552
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
1553
|
-
url: "agent streaming connection",
|
|
1554
|
-
message: helpfulMessage,
|
|
596
|
+
if (!createMCPClient) {
|
|
597
|
+
// Mirror legacy behavior: when servers are provided without a factory, treat as misconfiguration
|
|
598
|
+
throw new CopilotKitMisuseError({
|
|
599
|
+
message:
|
|
600
|
+
"MCP Integration Error: `mcpServers` were provided, but the `createMCPClient` function was not passed to the CopilotRuntime constructor. Please provide an implementation for `createMCPClient`.",
|
|
1555
601
|
});
|
|
1556
602
|
}
|
|
1557
603
|
|
|
1558
|
-
//
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
this.hasWarnedAboutError = true;
|
|
604
|
+
// Merge and dedupe endpoints by URL; request-level overrides take precedence
|
|
605
|
+
const effectiveEndpoints = (() => {
|
|
606
|
+
const byUrl = new Map<string, MCPEndpointConfig>();
|
|
607
|
+
for (const ep of runtimeMcpServers) {
|
|
608
|
+
if (ep?.endpoint) byUrl.set(ep.endpoint, ep);
|
|
609
|
+
}
|
|
610
|
+
for (const ep of requestMcpServers) {
|
|
611
|
+
if (ep?.endpoint) byUrl.set(ep.endpoint, ep);
|
|
612
|
+
}
|
|
613
|
+
return Array.from(byUrl.values());
|
|
614
|
+
})();
|
|
615
|
+
|
|
616
|
+
const allTools: BasicAgentConfiguration["tools"] = [];
|
|
617
|
+
|
|
618
|
+
for (const config of effectiveEndpoints) {
|
|
619
|
+
const endpointUrl = config.endpoint;
|
|
620
|
+
// Return cached tool definitions when available
|
|
621
|
+
const cached = this.mcpToolsCache.get(endpointUrl);
|
|
622
|
+
if (cached) {
|
|
623
|
+
allTools.push(...cached);
|
|
624
|
+
continue;
|
|
1580
625
|
}
|
|
1581
|
-
return;
|
|
1582
|
-
}
|
|
1583
626
|
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
627
|
+
try {
|
|
628
|
+
const client = await createMCPClient(config);
|
|
629
|
+
const toolsMap = await client.tools();
|
|
630
|
+
|
|
631
|
+
const toolDefs: BasicAgentConfiguration["tools"] = Object.entries(toolsMap).map(
|
|
632
|
+
([toolName, tool]: [string, MCPTool]) => {
|
|
633
|
+
const params: Parameter[] = extractParametersFromSchema(tool);
|
|
634
|
+
const zodSchema = getZodParameters(params);
|
|
635
|
+
return {
|
|
636
|
+
name: toolName,
|
|
637
|
+
description: tool.description || `MCP tool: ${toolName} (from ${endpointUrl})`,
|
|
638
|
+
parameters: zodSchema,
|
|
639
|
+
};
|
|
640
|
+
},
|
|
641
|
+
);
|
|
1591
642
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
643
|
+
// Cache per endpoint and add to aggregate
|
|
644
|
+
this.mcpToolsCache.set(endpointUrl, toolDefs);
|
|
645
|
+
allTools.push(...toolDefs);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error(
|
|
648
|
+
`MCP: Failed to fetch tools from endpoint ${endpointUrl}. Skipping. Error:`,
|
|
649
|
+
error,
|
|
650
|
+
);
|
|
651
|
+
// Cache empty to prevent repeated attempts within lifecycle
|
|
652
|
+
this.mcpToolsCache.set(endpointUrl, []);
|
|
653
|
+
}
|
|
1596
654
|
}
|
|
1597
|
-
}
|
|
1598
|
-
|
|
1599
|
-
/**
|
|
1600
|
-
* Public method to handle GraphQL validation errors
|
|
1601
|
-
* This allows the GraphQL resolver to send validation errors through the error system
|
|
1602
|
-
*/
|
|
1603
|
-
public async errorGraphQLError(
|
|
1604
|
-
error: { message: string; code: string; type: string },
|
|
1605
|
-
context: {
|
|
1606
|
-
operation: string;
|
|
1607
|
-
cloudConfigPresent: boolean;
|
|
1608
|
-
guardrailsEnabled: boolean;
|
|
1609
|
-
},
|
|
1610
|
-
): Promise<void> {
|
|
1611
|
-
if (!this.onError) return;
|
|
1612
655
|
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
context: {
|
|
1618
|
-
source: "runtime",
|
|
1619
|
-
request: {
|
|
1620
|
-
operation: context.operation,
|
|
1621
|
-
startTime: Date.now(),
|
|
1622
|
-
},
|
|
1623
|
-
technical: {
|
|
1624
|
-
environment: process.env.NODE_ENV,
|
|
1625
|
-
},
|
|
1626
|
-
metadata: {
|
|
1627
|
-
errorType: "GraphQLValidationError",
|
|
1628
|
-
cloudConfigPresent: context.cloudConfigPresent,
|
|
1629
|
-
guardrailsEnabled: context.guardrailsEnabled,
|
|
1630
|
-
},
|
|
1631
|
-
},
|
|
1632
|
-
error,
|
|
1633
|
-
});
|
|
1634
|
-
} catch (errorHandlerError) {
|
|
1635
|
-
// Don't let error handler errors break the main flow
|
|
1636
|
-
console.error("Error in onError handler:", errorHandlerError);
|
|
656
|
+
// Dedupe tools by name while preserving last-in wins (request overrides)
|
|
657
|
+
const dedupedByName = new Map<string, (typeof allTools)[number]>();
|
|
658
|
+
for (const tool of allTools) {
|
|
659
|
+
dedupedByName.set(tool.name, tool);
|
|
1637
660
|
}
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
661
|
|
|
1641
|
-
|
|
1642
|
-
let allTools: ActionInput[] = [];
|
|
1643
|
-
const allToolNames: string[] = [];
|
|
1644
|
-
for (const tool of toolsByPriority) {
|
|
1645
|
-
if (!allToolNames.includes(tool.name)) {
|
|
1646
|
-
allTools.push(tool);
|
|
1647
|
-
allToolNames.push(tool.name);
|
|
1648
|
-
}
|
|
662
|
+
return Array.from(dedupedByName.values());
|
|
1649
663
|
}
|
|
1650
|
-
return allTools;
|
|
1651
664
|
}
|
|
1652
665
|
|
|
1653
666
|
// The two functions below are "factory functions", meant to create the action objects that adhere to the expected interfaces
|