@copilotkit/runtime 1.9.2-next.2 → 1.9.2-next.20
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 +130 -0
- package/dist/{chunk-6TQCQ3WJ.mjs → chunk-6KWGISZM.mjs} +2 -2
- package/dist/chunk-AMUJQ6IR.mjs +50 -0
- package/dist/chunk-AMUJQ6IR.mjs.map +1 -0
- package/dist/{chunk-CD2SZGIZ.mjs → chunk-GJ2JWK4N.mjs} +2 -2
- package/dist/{chunk-DOWRU5U6.mjs → chunk-IKR6ULGF.mjs} +2353 -1854
- package/dist/chunk-IKR6ULGF.mjs.map +1 -0
- package/dist/{chunk-IIXJVVTV.mjs → chunk-QLLV2QVK.mjs} +132 -78
- package/dist/chunk-QLLV2QVK.mjs.map +1 -0
- package/dist/{chunk-D3SPXEVJ.mjs → chunk-S77YPKO6.mjs} +2 -2
- package/dist/{chunk-5BIEM2UU.mjs → chunk-XWBDEXDA.mjs} +4 -3
- package/dist/{chunk-5BIEM2UU.mjs.map → chunk-XWBDEXDA.mjs.map} +1 -1
- package/dist/{chunk-ODF35LFG.mjs → chunk-ZJZTEQVW.mjs} +19 -2
- package/dist/chunk-ZJZTEQVW.mjs.map +1 -0
- package/dist/{groq-adapter-25a2bd35.d.ts → groq-adapter-098f97f6.d.ts} +5 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3728 -3114
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +12 -8
- package/dist/index.mjs.map +1 -1
- package/dist/lib/index.d.ts +5 -4
- package/dist/lib/index.js +2711 -2140
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +9 -8
- package/dist/lib/integrations/index.d.ts +3 -3
- package/dist/lib/integrations/index.js +160 -96
- package/dist/lib/integrations/index.js.map +1 -1
- package/dist/lib/integrations/index.mjs +7 -6
- package/dist/lib/integrations/nest/index.d.ts +2 -2
- package/dist/lib/integrations/nest/index.js +160 -96
- package/dist/lib/integrations/nest/index.js.map +1 -1
- package/dist/lib/integrations/nest/index.mjs +5 -4
- package/dist/lib/integrations/node-express/index.d.ts +2 -2
- package/dist/lib/integrations/node-express/index.js +160 -96
- package/dist/lib/integrations/node-express/index.js.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs +5 -4
- package/dist/lib/integrations/node-http/index.d.ts +2 -2
- package/dist/lib/integrations/node-http/index.js +160 -96
- package/dist/lib/integrations/node-http/index.js.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +4 -3
- package/dist/service-adapters/index.d.ts +6 -4
- package/dist/service-adapters/index.js +202 -107
- package/dist/service-adapters/index.js.map +1 -1
- package/dist/service-adapters/index.mjs +6 -2
- package/dist/service-adapters/shared/index.d.ts +9 -0
- package/dist/service-adapters/shared/index.js +72 -0
- package/dist/service-adapters/shared/index.js.map +1 -0
- package/dist/service-adapters/shared/index.mjs +8 -0
- package/dist/service-adapters/shared/index.mjs.map +1 -0
- package/dist/{shared-e272b15a.d.ts → shared-41d4988d.d.ts} +45 -5
- package/dist/utils/index.d.ts +17 -1
- package/dist/utils/index.js +3 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +1 -1
- package/package.json +2 -2
- package/src/agents/langgraph/event-source.ts +36 -38
- package/src/agents/langgraph/events.ts +19 -1
- package/src/graphql/resolvers/copilot.resolver.ts +107 -45
- package/src/graphql/resolvers/state.resolver.ts +3 -3
- package/src/lib/error-messages.ts +200 -0
- package/src/lib/integrations/shared.ts +43 -0
- package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
- package/src/lib/runtime/agui-action.ts +9 -3
- package/src/lib/runtime/copilot-runtime.ts +384 -83
- package/src/lib/runtime/langgraph/langgraph-agent.ts +12 -0
- package/src/lib/runtime/remote-action-constructors.ts +28 -3
- package/src/lib/runtime/remote-actions.ts +6 -0
- package/src/lib/runtime/remote-lg-action.ts +130 -40
- package/src/lib/streaming.ts +125 -36
- package/src/service-adapters/anthropic/anthropic-adapter.ts +67 -8
- package/src/service-adapters/anthropic/utils.ts +3 -8
- package/src/service-adapters/events.ts +37 -81
- package/src/service-adapters/google/google-genai-adapter.ts +5 -0
- package/src/service-adapters/groq/groq-adapter.ts +66 -56
- package/src/service-adapters/index.ts +1 -0
- package/src/service-adapters/openai/openai-adapter.ts +4 -3
- package/src/service-adapters/shared/error-utils.ts +61 -0
- package/src/service-adapters/shared/index.ts +1 -0
- package/src/utils/failed-response-status-reasons.ts +23 -1
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +172 -387
- package/dist/chunk-DOWRU5U6.mjs.map +0 -1
- package/dist/chunk-IIXJVVTV.mjs.map +0 -1
- package/dist/chunk-ODF35LFG.mjs.map +0 -1
- package/dist/{chunk-6TQCQ3WJ.mjs.map → chunk-6KWGISZM.mjs.map} +0 -0
- package/dist/{chunk-CD2SZGIZ.mjs.map → chunk-GJ2JWK4N.mjs.map} +0 -0
- package/dist/{chunk-D3SPXEVJ.mjs.map → chunk-S77YPKO6.mjs.map} +0 -0
- package/dist/{langserve-4a5c9217.d.ts → langserve-fc5cac89.d.ts} +7 -7
|
@@ -54,7 +54,11 @@ import telemetry from "../../lib/telemetry-client";
|
|
|
54
54
|
import { randomId } from "@copilotkit/shared";
|
|
55
55
|
import { AgentsResponse } from "../types/agents-response.type";
|
|
56
56
|
import { LangGraphEventTypes } from "../../agents/langgraph/events";
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
CopilotKitError,
|
|
59
|
+
CopilotKitLowLevelError,
|
|
60
|
+
isStructuredCopilotKitError,
|
|
61
|
+
} from "@copilotkit/shared";
|
|
58
62
|
|
|
59
63
|
const invokeGuardrails = async ({
|
|
60
64
|
baseUrl,
|
|
@@ -174,15 +178,33 @@ export class CopilotResolver {
|
|
|
174
178
|
let copilotCloudPublicApiKey: string | null = null;
|
|
175
179
|
let copilotCloudBaseUrl: string;
|
|
176
180
|
|
|
181
|
+
// Extract publicApiKey from headers for both cloud and non-cloud requests
|
|
182
|
+
// This enables onTrace functionality regardless of cloud configuration
|
|
183
|
+
const publicApiKeyFromHeaders = ctx.request.headers.get("x-copilotcloud-public-api-key");
|
|
184
|
+
if (publicApiKeyFromHeaders) {
|
|
185
|
+
copilotCloudPublicApiKey = publicApiKeyFromHeaders;
|
|
186
|
+
}
|
|
187
|
+
|
|
177
188
|
if (data.cloud) {
|
|
178
189
|
logger = logger.child({ cloud: true });
|
|
179
190
|
logger.debug("Cloud configuration provided, checking for public API key in headers");
|
|
180
|
-
|
|
181
|
-
if (
|
|
182
|
-
logger.debug("Public API key found in headers");
|
|
183
|
-
copilotCloudPublicApiKey = key;
|
|
184
|
-
} else {
|
|
191
|
+
|
|
192
|
+
if (!copilotCloudPublicApiKey) {
|
|
185
193
|
logger.error("Public API key not found in headers");
|
|
194
|
+
|
|
195
|
+
await copilotRuntime.errorGraphQLError(
|
|
196
|
+
{
|
|
197
|
+
message: "X-CopilotCloud-Public-API-Key header is required",
|
|
198
|
+
code: "MISSING_PUBLIC_API_KEY",
|
|
199
|
+
type: "GraphQLError",
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
operation: "generateCopilotResponse",
|
|
203
|
+
cloudConfigPresent: Boolean(data.cloud),
|
|
204
|
+
guardrailsEnabled: Boolean(data.cloud?.guardrails),
|
|
205
|
+
},
|
|
206
|
+
);
|
|
207
|
+
|
|
186
208
|
throw new GraphQLError("X-CopilotCloud-Public-API-Key header is required");
|
|
187
209
|
}
|
|
188
210
|
|
|
@@ -216,6 +238,40 @@ export class CopilotResolver {
|
|
|
216
238
|
}
|
|
217
239
|
|
|
218
240
|
logger.debug("Processing");
|
|
241
|
+
let runtimeResponse;
|
|
242
|
+
try {
|
|
243
|
+
runtimeResponse = await copilotRuntime.processRuntimeRequest({
|
|
244
|
+
serviceAdapter,
|
|
245
|
+
messages: data.messages,
|
|
246
|
+
actions: data.frontend.actions.filter(
|
|
247
|
+
(action) => action.available !== ActionInputAvailability.disabled,
|
|
248
|
+
),
|
|
249
|
+
threadId: data.threadId,
|
|
250
|
+
runId: data.runId,
|
|
251
|
+
publicApiKey: copilotCloudPublicApiKey,
|
|
252
|
+
outputMessagesPromise,
|
|
253
|
+
graphqlContext: ctx,
|
|
254
|
+
forwardedParameters: data.forwardedParameters,
|
|
255
|
+
agentSession: data.agentSession,
|
|
256
|
+
agentStates: data.agentStates,
|
|
257
|
+
url: data.frontend.url,
|
|
258
|
+
extensions: data.extensions,
|
|
259
|
+
metaEvents: data.metaEvents,
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
// Catch structured CopilotKit errors at the main mutation level and re-throw as GraphQL errors
|
|
263
|
+
if (isStructuredCopilotKitError(error) || (error as any)?.extensions?.visibility) {
|
|
264
|
+
throw new GraphQLError(error.message || "Agent error occurred", {
|
|
265
|
+
extensions: {
|
|
266
|
+
...(error as any).extensions,
|
|
267
|
+
code: (error as any).code || (error as any).extensions?.code || "AGENT_ERROR",
|
|
268
|
+
originalError: error,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
throw error; // Re-throw non-CopilotKit errors as-is
|
|
273
|
+
}
|
|
274
|
+
|
|
219
275
|
const {
|
|
220
276
|
eventSource,
|
|
221
277
|
threadId = randomId(),
|
|
@@ -223,24 +279,7 @@ export class CopilotResolver {
|
|
|
223
279
|
serverSideActions,
|
|
224
280
|
actionInputsWithoutAgents,
|
|
225
281
|
extensions,
|
|
226
|
-
} =
|
|
227
|
-
serviceAdapter,
|
|
228
|
-
messages: data.messages,
|
|
229
|
-
actions: data.frontend.actions.filter(
|
|
230
|
-
(action) => action.available !== ActionInputAvailability.disabled,
|
|
231
|
-
),
|
|
232
|
-
threadId: data.threadId,
|
|
233
|
-
runId: data.runId,
|
|
234
|
-
publicApiKey: copilotCloudPublicApiKey,
|
|
235
|
-
outputMessagesPromise,
|
|
236
|
-
graphqlContext: ctx,
|
|
237
|
-
forwardedParameters: data.forwardedParameters,
|
|
238
|
-
agentSession: data.agentSession,
|
|
239
|
-
agentStates: data.agentStates,
|
|
240
|
-
url: data.frontend.url,
|
|
241
|
-
extensions: data.extensions,
|
|
242
|
-
metaEvents: data.metaEvents,
|
|
243
|
-
});
|
|
282
|
+
} = runtimeResponse;
|
|
244
283
|
|
|
245
284
|
logger.debug("Event source created, creating response");
|
|
246
285
|
// run and process the event stream
|
|
@@ -338,12 +377,21 @@ export class CopilotResolver {
|
|
|
338
377
|
}
|
|
339
378
|
},
|
|
340
379
|
error: (err) => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
380
|
+
// For structured CopilotKit errors, set proper error response status
|
|
381
|
+
if (err?.name?.includes("CopilotKit") || err?.extensions?.visibility) {
|
|
382
|
+
responseStatus$.next(
|
|
383
|
+
new UnknownErrorResponse({
|
|
384
|
+
description: err.message || "Agent error occurred",
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
responseStatus$.next(
|
|
389
|
+
new UnknownErrorResponse({
|
|
390
|
+
description: `An unknown error has occurred in the event stream`,
|
|
391
|
+
}),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
347
395
|
eventStreamSubscription?.unsubscribe();
|
|
348
396
|
stop();
|
|
349
397
|
},
|
|
@@ -425,20 +473,20 @@ export class CopilotResolver {
|
|
|
425
473
|
// create a sub stream that contains the message content
|
|
426
474
|
const textMessageContentStream = eventStream.pipe(
|
|
427
475
|
// skip until this message start event
|
|
428
|
-
skipWhile((e) => e !== event),
|
|
476
|
+
skipWhile((e: RuntimeEvent) => e !== event),
|
|
429
477
|
// take until the message end event
|
|
430
478
|
takeWhile(
|
|
431
|
-
(e) =>
|
|
479
|
+
(e: RuntimeEvent) =>
|
|
432
480
|
!(
|
|
433
481
|
e.type === RuntimeEventTypes.TextMessageEnd &&
|
|
434
|
-
e.messageId == event.messageId
|
|
482
|
+
(e as any).messageId == event.messageId
|
|
435
483
|
),
|
|
436
484
|
),
|
|
437
485
|
// filter out any other message events or message ids
|
|
438
486
|
filter(
|
|
439
|
-
(e) =>
|
|
487
|
+
(e: RuntimeEvent) =>
|
|
440
488
|
e.type == RuntimeEventTypes.TextMessageContent &&
|
|
441
|
-
e.messageId == event.messageId,
|
|
489
|
+
(e as any).messageId == event.messageId,
|
|
442
490
|
),
|
|
443
491
|
);
|
|
444
492
|
|
|
@@ -520,20 +568,20 @@ export class CopilotResolver {
|
|
|
520
568
|
case RuntimeEventTypes.ActionExecutionStart:
|
|
521
569
|
logger.debug("Action execution start event received");
|
|
522
570
|
const actionExecutionArgumentStream = eventStream.pipe(
|
|
523
|
-
skipWhile((e) => e !== event),
|
|
571
|
+
skipWhile((e: RuntimeEvent) => e !== event),
|
|
524
572
|
// take until the action execution end event
|
|
525
573
|
takeWhile(
|
|
526
|
-
(e) =>
|
|
574
|
+
(e: RuntimeEvent) =>
|
|
527
575
|
!(
|
|
528
576
|
e.type === RuntimeEventTypes.ActionExecutionEnd &&
|
|
529
|
-
e.actionExecutionId == event.actionExecutionId
|
|
577
|
+
(e as any).actionExecutionId == event.actionExecutionId
|
|
530
578
|
),
|
|
531
579
|
),
|
|
532
580
|
// filter out any other action execution events or action execution ids
|
|
533
581
|
filter(
|
|
534
|
-
(e) =>
|
|
582
|
+
(e: RuntimeEvent) =>
|
|
535
583
|
e.type == RuntimeEventTypes.ActionExecutionArgs &&
|
|
536
|
-
e.actionExecutionId == event.actionExecutionId,
|
|
584
|
+
(e as any).actionExecutionId == event.actionExecutionId,
|
|
537
585
|
),
|
|
538
586
|
);
|
|
539
587
|
const streamingArgumentsStatus = new Subject<typeof MessageStatusUnion>();
|
|
@@ -646,16 +694,30 @@ export class CopilotResolver {
|
|
|
646
694
|
}
|
|
647
695
|
},
|
|
648
696
|
error: (err) => {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// If it's a structured CopilotKitError, stop the repeater with the error so frontend can handle it
|
|
697
|
+
// For structured CopilotKit errors, set proper error response status
|
|
652
698
|
if (
|
|
653
699
|
err instanceof CopilotKitError ||
|
|
654
|
-
|
|
700
|
+
err instanceof CopilotKitLowLevelError ||
|
|
701
|
+
(err instanceof Error && err.name && err.name.includes("CopilotKit")) ||
|
|
702
|
+
err?.extensions?.visibility
|
|
655
703
|
) {
|
|
704
|
+
responseStatus$.next(
|
|
705
|
+
new UnknownErrorResponse({
|
|
706
|
+
description: err.message || "Agent error occurred",
|
|
707
|
+
// Include original error information for frontend to extract
|
|
708
|
+
originalError: {
|
|
709
|
+
code: err.code || err.extensions?.code,
|
|
710
|
+
statusCode: err.statusCode || err.extensions?.statusCode,
|
|
711
|
+
severity: err.severity || err.extensions?.severity,
|
|
712
|
+
visibility: err.visibility || err.extensions?.visibility,
|
|
713
|
+
originalErrorType: err.originalErrorType || err.extensions?.originalErrorType,
|
|
714
|
+
extensions: err.extensions,
|
|
715
|
+
},
|
|
716
|
+
}),
|
|
717
|
+
);
|
|
656
718
|
eventStreamSubscription?.unsubscribe();
|
|
657
719
|
rejectOutputMessagesPromise(err);
|
|
658
|
-
stopStreamingMessages(
|
|
720
|
+
stopStreamingMessages();
|
|
659
721
|
return;
|
|
660
722
|
}
|
|
661
723
|
|
|
@@ -10,9 +10,9 @@ import { CopilotKitAgentDiscoveryError } from "@copilotkit/shared";
|
|
|
10
10
|
export class StateResolver {
|
|
11
11
|
@Query(() => LoadAgentStateResponse)
|
|
12
12
|
async loadAgentState(@Ctx() ctx: GraphQLContext, @Arg("data") data: LoadAgentStateInput) {
|
|
13
|
-
const agents = await ctx._copilotkit.runtime.
|
|
14
|
-
const
|
|
15
|
-
if (!
|
|
13
|
+
const agents = await ctx._copilotkit.runtime.getAllAgents(ctx);
|
|
14
|
+
const hasAgent = agents.some((agent) => agent.name === data.agentName);
|
|
15
|
+
if (!hasAgent) {
|
|
16
16
|
throw new CopilotKitAgentDiscoveryError({
|
|
17
17
|
agentName: data.agentName,
|
|
18
18
|
availableAgents: agents.map((a) => ({ name: a.name, id: a.name })),
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error message configuration - Single source of truth for all error messages
|
|
3
|
+
*
|
|
4
|
+
* This centralized configuration system provides:
|
|
5
|
+
*
|
|
6
|
+
* 🎯 **Benefits:**
|
|
7
|
+
* - Single source of truth for all error messages
|
|
8
|
+
* - Easy content management without touching code
|
|
9
|
+
* - Consistent error messaging across the application
|
|
10
|
+
* - Ready for internationalization (i18n)
|
|
11
|
+
* - Type-safe configuration with TypeScript
|
|
12
|
+
* - Categorized errors for better handling
|
|
13
|
+
*
|
|
14
|
+
* 📝 **How to use:**
|
|
15
|
+
* 1. Add new error patterns to `errorPatterns` object
|
|
16
|
+
* 2. Use {context} placeholder for dynamic context injection
|
|
17
|
+
* 3. Specify category, severity, and actionable flags
|
|
18
|
+
* 4. Add fallback messages for error categories
|
|
19
|
+
*
|
|
20
|
+
* 🔧 **How to maintain:**
|
|
21
|
+
* - Content teams can update messages here without touching logic
|
|
22
|
+
* - Developers add new error patterns as needed
|
|
23
|
+
* - Use categories to group similar errors
|
|
24
|
+
* - Mark errors as actionable if users can fix them
|
|
25
|
+
*
|
|
26
|
+
* 🌍 **Future i18n support:**
|
|
27
|
+
* - Replace `message` string with `messages: { en: "...", es: "..." }`
|
|
28
|
+
* - Add locale parameter to helper functions
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* // Adding a new error pattern:
|
|
33
|
+
* "CUSTOM_ERROR": {
|
|
34
|
+
* message: "Custom error occurred in {context}. Please try again.",
|
|
35
|
+
* category: "unknown",
|
|
36
|
+
* severity: "error",
|
|
37
|
+
* actionable: true
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export interface ErrorPatternConfig {
|
|
43
|
+
message: string;
|
|
44
|
+
category: "network" | "connection" | "authentication" | "validation" | "unknown";
|
|
45
|
+
severity: "error" | "warning" | "info";
|
|
46
|
+
actionable: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ErrorConfig {
|
|
50
|
+
errorPatterns: Record<string, ErrorPatternConfig>;
|
|
51
|
+
fallbacks: Record<string, string>;
|
|
52
|
+
contextTemplates: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const errorConfig: ErrorConfig = {
|
|
56
|
+
errorPatterns: {
|
|
57
|
+
ECONNREFUSED: {
|
|
58
|
+
message:
|
|
59
|
+
"Connection refused - the agent service is not running or not accessible at the specified address. Please check that your agent is started and listening on the correct port.",
|
|
60
|
+
category: "network",
|
|
61
|
+
severity: "error",
|
|
62
|
+
actionable: true,
|
|
63
|
+
},
|
|
64
|
+
ENOTFOUND: {
|
|
65
|
+
message:
|
|
66
|
+
"Host not found - the agent service URL appears to be incorrect or the service is not accessible. Please verify the agent endpoint URL.",
|
|
67
|
+
category: "network",
|
|
68
|
+
severity: "error",
|
|
69
|
+
actionable: true,
|
|
70
|
+
},
|
|
71
|
+
ETIMEDOUT: {
|
|
72
|
+
message:
|
|
73
|
+
"Connection timeout - the agent service is taking too long to respond. This could indicate network issues or an overloaded agent service.",
|
|
74
|
+
category: "network",
|
|
75
|
+
severity: "warning",
|
|
76
|
+
actionable: true,
|
|
77
|
+
},
|
|
78
|
+
terminated: {
|
|
79
|
+
message:
|
|
80
|
+
"Agent {context} was unexpectedly terminated. This often indicates an error in the agent service (e.g., authentication failures, missing environment variables, or agent crashes). Check the agent logs for the root cause.",
|
|
81
|
+
category: "connection",
|
|
82
|
+
severity: "error",
|
|
83
|
+
actionable: true,
|
|
84
|
+
},
|
|
85
|
+
UND_ERR_SOCKET: {
|
|
86
|
+
message:
|
|
87
|
+
"Socket connection was closed unexpectedly. This typically indicates the agent service encountered an error and shut down the connection. Check the agent logs for the underlying cause.",
|
|
88
|
+
category: "connection",
|
|
89
|
+
severity: "error",
|
|
90
|
+
actionable: true,
|
|
91
|
+
},
|
|
92
|
+
other_side_closed: {
|
|
93
|
+
message:
|
|
94
|
+
"The agent service closed the connection unexpectedly. This usually indicates an error in the agent service. Check the agent logs for more details.",
|
|
95
|
+
category: "connection",
|
|
96
|
+
severity: "error",
|
|
97
|
+
actionable: true,
|
|
98
|
+
},
|
|
99
|
+
fetch_failed: {
|
|
100
|
+
message:
|
|
101
|
+
"Failed to connect to the agent service. Please verify the agent is running and the endpoint URL is correct.",
|
|
102
|
+
category: "network",
|
|
103
|
+
severity: "error",
|
|
104
|
+
actionable: true,
|
|
105
|
+
},
|
|
106
|
+
// Authentication patterns
|
|
107
|
+
"401": {
|
|
108
|
+
message:
|
|
109
|
+
"Authentication failed. Please check your API keys and ensure they are correctly configured.",
|
|
110
|
+
category: "authentication",
|
|
111
|
+
severity: "error",
|
|
112
|
+
actionable: true,
|
|
113
|
+
},
|
|
114
|
+
"api key": {
|
|
115
|
+
message:
|
|
116
|
+
"API key error detected. Please verify your API key is correct and has the necessary permissions.",
|
|
117
|
+
category: "authentication",
|
|
118
|
+
severity: "error",
|
|
119
|
+
actionable: true,
|
|
120
|
+
},
|
|
121
|
+
unauthorized: {
|
|
122
|
+
message: "Unauthorized access. Please check your authentication credentials.",
|
|
123
|
+
category: "authentication",
|
|
124
|
+
severity: "error",
|
|
125
|
+
actionable: true,
|
|
126
|
+
},
|
|
127
|
+
// Python-specific error patterns
|
|
128
|
+
AuthenticationError: {
|
|
129
|
+
message:
|
|
130
|
+
"OpenAI authentication failed. Please check your OPENAI_API_KEY environment variable or API key configuration.",
|
|
131
|
+
category: "authentication",
|
|
132
|
+
severity: "error",
|
|
133
|
+
actionable: true,
|
|
134
|
+
},
|
|
135
|
+
"Incorrect API key provided": {
|
|
136
|
+
message:
|
|
137
|
+
"OpenAI API key is invalid. Please verify your OPENAI_API_KEY is correct and active.",
|
|
138
|
+
category: "authentication",
|
|
139
|
+
severity: "error",
|
|
140
|
+
actionable: true,
|
|
141
|
+
},
|
|
142
|
+
RateLimitError: {
|
|
143
|
+
message:
|
|
144
|
+
"OpenAI rate limit exceeded. Please wait a moment and try again, or check your OpenAI usage limits.",
|
|
145
|
+
category: "network",
|
|
146
|
+
severity: "warning",
|
|
147
|
+
actionable: true,
|
|
148
|
+
},
|
|
149
|
+
InvalidRequestError: {
|
|
150
|
+
message:
|
|
151
|
+
"Invalid request to OpenAI API. Please check your request parameters and model configuration.",
|
|
152
|
+
category: "validation",
|
|
153
|
+
severity: "error",
|
|
154
|
+
actionable: true,
|
|
155
|
+
},
|
|
156
|
+
PermissionDeniedError: {
|
|
157
|
+
message:
|
|
158
|
+
"Permission denied for OpenAI API. Please check your API key permissions and billing status.",
|
|
159
|
+
category: "authentication",
|
|
160
|
+
severity: "error",
|
|
161
|
+
actionable: true,
|
|
162
|
+
},
|
|
163
|
+
NotFoundError: {
|
|
164
|
+
message: "OpenAI resource not found. Please check your model name and availability.",
|
|
165
|
+
category: "validation",
|
|
166
|
+
severity: "error",
|
|
167
|
+
actionable: true,
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
fallbacks: {
|
|
171
|
+
network:
|
|
172
|
+
"A network error occurred while connecting to the agent service. Please check your connection and ensure the agent service is running.",
|
|
173
|
+
connection:
|
|
174
|
+
"The connection to the agent service was lost unexpectedly. This may indicate an issue with the agent service.",
|
|
175
|
+
authentication: "Authentication failed. Please check your API keys and credentials.",
|
|
176
|
+
validation: "Invalid input or configuration. Please check your parameters and try again.",
|
|
177
|
+
unknown: "An unexpected error occurred. Please check the logs for more details.",
|
|
178
|
+
default: "An unexpected error occurred. Please check the logs for more details.",
|
|
179
|
+
},
|
|
180
|
+
contextTemplates: {
|
|
181
|
+
connection: "connection",
|
|
182
|
+
event_streaming_connection: "event streaming connection",
|
|
183
|
+
agent_streaming_connection: "agent streaming connection",
|
|
184
|
+
langgraph_agent_connection: "LangGraph agent connection",
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Helper function to get error pattern configuration by key
|
|
190
|
+
*/
|
|
191
|
+
export function getErrorPattern(key: string): ErrorPatternConfig | undefined {
|
|
192
|
+
return errorConfig.errorPatterns[key];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Helper function to get fallback message by category
|
|
197
|
+
*/
|
|
198
|
+
export function getFallbackMessage(category: string): string {
|
|
199
|
+
return errorConfig.fallbacks[category] || errorConfig.fallbacks.default;
|
|
200
|
+
}
|
|
@@ -10,6 +10,7 @@ import { createYoga } from "graphql-yoga";
|
|
|
10
10
|
import telemetry from "../telemetry-client";
|
|
11
11
|
import { StateResolver } from "../../graphql/resolvers/state.resolver";
|
|
12
12
|
import * as packageJson from "../../../package.json";
|
|
13
|
+
import { CopilotKitError, CopilotKitErrorCode } from "@copilotkit/shared";
|
|
13
14
|
|
|
14
15
|
const logger = createLogger();
|
|
15
16
|
|
|
@@ -79,6 +80,9 @@ export type CommonConfig = {
|
|
|
79
80
|
schema: ReturnType<typeof buildSchema>;
|
|
80
81
|
plugins: Parameters<typeof createYoga>[0]["plugins"];
|
|
81
82
|
context: (ctx: YogaInitialContext) => Promise<Partial<GraphQLContext>>;
|
|
83
|
+
maskedErrors: {
|
|
84
|
+
maskError: (error: any, message: string, isDev?: boolean) => any;
|
|
85
|
+
};
|
|
82
86
|
};
|
|
83
87
|
|
|
84
88
|
export function getCommonConfig(options: CreateCopilotRuntimeServerOptions): CommonConfig {
|
|
@@ -108,11 +112,50 @@ export function getCommonConfig(options: CreateCopilotRuntimeServerOptions): Com
|
|
|
108
112
|
},
|
|
109
113
|
});
|
|
110
114
|
|
|
115
|
+
// User error codes that should not be logged as server errors
|
|
116
|
+
const userErrorCodes = [
|
|
117
|
+
CopilotKitErrorCode.AGENT_NOT_FOUND,
|
|
118
|
+
CopilotKitErrorCode.API_NOT_FOUND,
|
|
119
|
+
CopilotKitErrorCode.REMOTE_ENDPOINT_NOT_FOUND,
|
|
120
|
+
CopilotKitErrorCode.CONFIGURATION_ERROR,
|
|
121
|
+
CopilotKitErrorCode.MISSING_PUBLIC_API_KEY_ERROR,
|
|
122
|
+
];
|
|
123
|
+
|
|
111
124
|
return {
|
|
112
125
|
logging: createLogger({ component: "Yoga GraphQL", level: logLevel }),
|
|
113
126
|
schema: buildSchema(),
|
|
114
127
|
plugins: [useDeferStream(), addCustomHeaderPlugin],
|
|
115
128
|
context: (ctx: YogaInitialContext): Promise<Partial<GraphQLContext>> =>
|
|
116
129
|
createContext(ctx, options, contextLogger, options.properties),
|
|
130
|
+
// Suppress logging for user configuration errors
|
|
131
|
+
maskedErrors: {
|
|
132
|
+
maskError: (error: any, message: string, isDev?: boolean) => {
|
|
133
|
+
// Check if this is a user configuration error (could be wrapped in GraphQLError)
|
|
134
|
+
const originalError = error.originalError || error;
|
|
135
|
+
const extensions = error.extensions;
|
|
136
|
+
const errorCode = extensions?.code;
|
|
137
|
+
|
|
138
|
+
// Suppress logging for user errors based on error code
|
|
139
|
+
if (errorCode && userErrorCodes.includes(errorCode)) {
|
|
140
|
+
// Log user configuration errors at debug level instead
|
|
141
|
+
console.debug("User configuration error:", error.message);
|
|
142
|
+
return error;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Check if the original error is a user error
|
|
146
|
+
if (
|
|
147
|
+
originalError instanceof CopilotKitError &&
|
|
148
|
+
userErrorCodes.includes(originalError.code)
|
|
149
|
+
) {
|
|
150
|
+
// Log user configuration errors at debug level instead
|
|
151
|
+
console.debug("User configuration error:", error.message);
|
|
152
|
+
return error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// For application errors, log normally and mask if needed
|
|
156
|
+
console.error("Application error:", error);
|
|
157
|
+
return error;
|
|
158
|
+
},
|
|
159
|
+
},
|
|
117
160
|
};
|
|
118
161
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { CopilotErrorEvent, CopilotRequestContext, CopilotErrorHandler } from "@copilotkit/shared";
|
|
2
|
+
|
|
3
|
+
describe("CopilotRuntime onError types", () => {
|
|
4
|
+
it("should have correct CopilotTraceEvent type structure", () => {
|
|
5
|
+
const errorEvent: CopilotErrorEvent = {
|
|
6
|
+
type: "error",
|
|
7
|
+
timestamp: Date.now(),
|
|
8
|
+
context: {
|
|
9
|
+
threadId: "test-123",
|
|
10
|
+
source: "runtime",
|
|
11
|
+
request: {
|
|
12
|
+
operation: "test-operation",
|
|
13
|
+
startTime: Date.now(),
|
|
14
|
+
},
|
|
15
|
+
technical: {},
|
|
16
|
+
metadata: {},
|
|
17
|
+
},
|
|
18
|
+
error: new Error("Test error"),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
expect(errorEvent.type).toBe("error");
|
|
22
|
+
expect(errorEvent.timestamp).toBeGreaterThan(0);
|
|
23
|
+
expect(errorEvent.context.threadId).toBe("test-123");
|
|
24
|
+
expect(errorEvent.error).toBeInstanceOf(Error);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("should have correct CopilotRequestContext type structure", () => {
|
|
28
|
+
const context: CopilotRequestContext = {
|
|
29
|
+
threadId: "test-thread-456",
|
|
30
|
+
runId: "test-run-789",
|
|
31
|
+
source: "runtime",
|
|
32
|
+
request: {
|
|
33
|
+
operation: "processRuntimeRequest",
|
|
34
|
+
method: "POST",
|
|
35
|
+
url: "http://localhost:3000/api/copilotkit",
|
|
36
|
+
startTime: Date.now(),
|
|
37
|
+
},
|
|
38
|
+
response: {
|
|
39
|
+
status: 200,
|
|
40
|
+
endTime: Date.now(),
|
|
41
|
+
latency: 1200,
|
|
42
|
+
},
|
|
43
|
+
agent: {
|
|
44
|
+
name: "test-agent",
|
|
45
|
+
nodeName: "test-node",
|
|
46
|
+
state: { step: 1 },
|
|
47
|
+
},
|
|
48
|
+
messages: {
|
|
49
|
+
input: [],
|
|
50
|
+
output: [],
|
|
51
|
+
messageCount: 2,
|
|
52
|
+
},
|
|
53
|
+
technical: {
|
|
54
|
+
userAgent: "Mozilla/5.0...",
|
|
55
|
+
host: "localhost:3000",
|
|
56
|
+
environment: "test",
|
|
57
|
+
version: "1.0.0",
|
|
58
|
+
stackTrace: "Error: Test\n at test.js:1:1",
|
|
59
|
+
},
|
|
60
|
+
performance: {
|
|
61
|
+
requestDuration: 1200,
|
|
62
|
+
streamingDuration: 800,
|
|
63
|
+
actionExecutionTime: 400,
|
|
64
|
+
memoryUsage: 45.2,
|
|
65
|
+
},
|
|
66
|
+
metadata: {
|
|
67
|
+
testFlag: true,
|
|
68
|
+
version: "1.0.0",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(context.threadId).toBe("test-thread-456");
|
|
73
|
+
expect(context.agent?.name).toBe("test-agent");
|
|
74
|
+
expect(context.messages?.messageCount).toBe(2);
|
|
75
|
+
expect(context.technical?.stackTrace).toContain("Error: Test");
|
|
76
|
+
expect(context.metadata?.testFlag).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should support all error event types", () => {
|
|
80
|
+
const eventTypes: CopilotErrorEvent["type"][] = [
|
|
81
|
+
"error",
|
|
82
|
+
"request",
|
|
83
|
+
"response",
|
|
84
|
+
"agent_state",
|
|
85
|
+
"action",
|
|
86
|
+
"message",
|
|
87
|
+
"performance",
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
eventTypes.forEach((type) => {
|
|
91
|
+
const event: CopilotErrorEvent = {
|
|
92
|
+
type,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
context: {
|
|
95
|
+
threadId: `test-${type}`,
|
|
96
|
+
source: "runtime",
|
|
97
|
+
request: {
|
|
98
|
+
operation: "test",
|
|
99
|
+
startTime: Date.now(),
|
|
100
|
+
},
|
|
101
|
+
technical: {},
|
|
102
|
+
metadata: {},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
expect(event.type).toBe(type);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("publicApiKey gating logic", () => {
|
|
111
|
+
type ShouldHandleError = (onError?: CopilotErrorHandler, publicApiKey?: string) => boolean;
|
|
112
|
+
|
|
113
|
+
const shouldHandleError: ShouldHandleError = (onError, publicApiKey) => {
|
|
114
|
+
return Boolean(onError && publicApiKey);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
it("should return true when both onError and publicApiKey are provided", () => {
|
|
118
|
+
const onError = jest.fn();
|
|
119
|
+
const result = shouldHandleError(onError, "valid-api-key");
|
|
120
|
+
expect(result).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should return false when onError is missing", () => {
|
|
124
|
+
const result = shouldHandleError(undefined, "valid-api-key");
|
|
125
|
+
expect(result).toBe(false);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("should return false when publicApiKey is missing", () => {
|
|
129
|
+
const onError = jest.fn();
|
|
130
|
+
const result = shouldHandleError(onError, undefined);
|
|
131
|
+
expect(result).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should return false when publicApiKey is empty string", () => {
|
|
135
|
+
const onError = jest.fn();
|
|
136
|
+
const result = shouldHandleError(onError, "");
|
|
137
|
+
expect(result).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should return false when both are missing", () => {
|
|
141
|
+
const result = shouldHandleError(undefined, undefined);
|
|
142
|
+
expect(result).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should extract publicApiKey from headers for both cloud and non-cloud requests", () => {
|
|
146
|
+
// Test the logic we just fixed in the GraphQL resolver
|
|
147
|
+
const mockHeaders = new Map([["x-copilotcloud-public-api-key", "test-key-123"]]);
|
|
148
|
+
|
|
149
|
+
// Simulate header extraction logic
|
|
150
|
+
const extractPublicApiKey = (headers: Map<string, string>, hasCloudConfig: boolean) => {
|
|
151
|
+
const publicApiKeyFromHeaders = headers.get("x-copilotcloud-public-api-key");
|
|
152
|
+
return publicApiKeyFromHeaders || null;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Should work for cloud requests
|
|
156
|
+
const cloudKey = extractPublicApiKey(mockHeaders, true);
|
|
157
|
+
expect(cloudKey).toBe("test-key-123");
|
|
158
|
+
|
|
159
|
+
// Should also work for non-cloud requests (this was the bug)
|
|
160
|
+
const nonCloudKey = extractPublicApiKey(mockHeaders, false);
|
|
161
|
+
expect(nonCloudKey).toBe("test-key-123");
|
|
162
|
+
|
|
163
|
+
// Both should enable error handling when onError is present
|
|
164
|
+
const onError = jest.fn();
|
|
165
|
+
expect(shouldHandleError(onError, cloudKey)).toBe(true);
|
|
166
|
+
expect(shouldHandleError(onError, nonCloudKey)).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|