@copilotkit/runtime 0.0.0-0.0.0-max-changeset-10101010101010-20260109191632
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/.eslintrc.js +7 -0
- package/CHANGELOG.md +2905 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/__snapshots__/schema/schema.graphql +371 -0
- package/dist/index.d.ts +1495 -0
- package/dist/index.js +5644 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5601 -0
- package/dist/index.mjs.map +1 -0
- package/dist/langgraph.d.ts +284 -0
- package/dist/langgraph.js +211 -0
- package/dist/langgraph.js.map +1 -0
- package/dist/langgraph.mjs +206 -0
- package/dist/langgraph.mjs.map +1 -0
- package/dist/v2/index.d.ts +2 -0
- package/dist/v2/index.js +22 -0
- package/dist/v2/index.js.map +1 -0
- package/dist/v2/index.mjs +5 -0
- package/dist/v2/index.mjs.map +1 -0
- package/jest.config.js +10 -0
- package/package.json +143 -0
- package/scripts/generate-gql-schema.ts +13 -0
- package/src/agents/langgraph/event-source.ts +329 -0
- package/src/agents/langgraph/events.ts +377 -0
- package/src/graphql/inputs/action.input.ts +16 -0
- package/src/graphql/inputs/agent-session.input.ts +13 -0
- package/src/graphql/inputs/agent-state.input.ts +13 -0
- package/src/graphql/inputs/cloud-guardrails.input.ts +16 -0
- package/src/graphql/inputs/cloud.input.ts +8 -0
- package/src/graphql/inputs/context-property.input.ts +10 -0
- package/src/graphql/inputs/copilot-context.input.ts +10 -0
- package/src/graphql/inputs/custom-property.input.ts +15 -0
- package/src/graphql/inputs/extensions.input.ts +21 -0
- package/src/graphql/inputs/forwarded-parameters.input.ts +22 -0
- package/src/graphql/inputs/frontend.input.ts +14 -0
- package/src/graphql/inputs/generate-copilot-response.input.ts +59 -0
- package/src/graphql/inputs/load-agent-state.input.ts +10 -0
- package/src/graphql/inputs/message.input.ts +110 -0
- package/src/graphql/inputs/meta-event.input.ts +18 -0
- 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 +1580 -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 +708 -0
- package/src/graphql/resolvers/state.resolver.ts +27 -0
- package/src/graphql/types/agents-response.type.ts +19 -0
- package/src/graphql/types/base/index.ts +10 -0
- package/src/graphql/types/converted/index.ts +176 -0
- package/src/graphql/types/copilot-response.type.ts +138 -0
- package/src/graphql/types/enums.ts +38 -0
- package/src/graphql/types/extensions-response.type.ts +23 -0
- package/src/graphql/types/guardrails-result.type.ts +20 -0
- package/src/graphql/types/load-agent-state-response.type.ts +17 -0
- package/src/graphql/types/message-status.type.ts +42 -0
- package/src/graphql/types/meta-events.type.ts +71 -0
- package/src/graphql/types/response-status.type.ts +66 -0
- package/src/index.ts +4 -0
- package/src/langgraph.ts +1 -0
- package/src/lib/cloud/index.ts +4 -0
- package/src/lib/error-messages.ts +200 -0
- package/src/lib/index.ts +52 -0
- package/src/lib/integrations/index.ts +6 -0
- package/src/lib/integrations/nest/index.ts +14 -0
- package/src/lib/integrations/nextjs/app-router.ts +38 -0
- package/src/lib/integrations/nextjs/pages-router.ts +39 -0
- package/src/lib/integrations/node-express/index.ts +14 -0
- package/src/lib/integrations/node-http/index.ts +143 -0
- package/src/lib/integrations/node-http/request-handler.ts +111 -0
- package/src/lib/integrations/shared.ts +161 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/observability.ts +160 -0
- package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +209 -0
- package/src/lib/runtime/agent-integrations/langgraph/consts.ts +34 -0
- package/src/lib/runtime/agent-integrations/langgraph/index.ts +2 -0
- package/src/lib/runtime/copilot-runtime.ts +710 -0
- package/src/lib/runtime/mcp-tools-utils.ts +254 -0
- package/src/lib/runtime/retry-utils.ts +96 -0
- package/src/lib/runtime/telemetry-agent-runner.ts +139 -0
- package/src/lib/runtime/types.ts +49 -0
- package/src/lib/runtime/utils.ts +87 -0
- package/src/lib/streaming.ts +202 -0
- package/src/lib/telemetry-client.ts +64 -0
- package/src/service-adapters/anthropic/anthropic-adapter.ts +452 -0
- package/src/service-adapters/anthropic/utils.ts +152 -0
- package/src/service-adapters/bedrock/bedrock-adapter.ts +73 -0
- package/src/service-adapters/conversion.ts +67 -0
- package/src/service-adapters/empty/empty-adapter.ts +38 -0
- package/src/service-adapters/events.ts +294 -0
- package/src/service-adapters/experimental/ollama/ollama-adapter.ts +84 -0
- package/src/service-adapters/google/google-genai-adapter.test.ts +104 -0
- package/src/service-adapters/google/google-genai-adapter.ts +88 -0
- package/src/service-adapters/groq/groq-adapter.ts +203 -0
- package/src/service-adapters/index.ts +18 -0
- package/src/service-adapters/langchain/langchain-adapter.ts +111 -0
- package/src/service-adapters/langchain/langserve.ts +88 -0
- package/src/service-adapters/langchain/types.ts +14 -0
- package/src/service-adapters/langchain/utils.ts +313 -0
- package/src/service-adapters/openai/openai-adapter.ts +283 -0
- package/src/service-adapters/openai/openai-assistant-adapter.ts +344 -0
- package/src/service-adapters/openai/utils.ts +199 -0
- package/src/service-adapters/service-adapter.ts +41 -0
- package/src/service-adapters/shared/error-utils.ts +61 -0
- package/src/service-adapters/shared/index.ts +1 -0
- package/src/service-adapters/unify/unify-adapter.ts +151 -0
- package/src/utils/failed-response-status-reasons.ts +70 -0
- package/src/utils/index.ts +1 -0
- package/src/v2/index.ts +3 -0
- package/tests/global.d.ts +13 -0
- package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +389 -0
- package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
- package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
- package/tests/setup.jest.ts +21 -0
- package/tests/tsconfig.json +10 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +4 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { YogaInitialContext } from "graphql-yoga";
|
|
2
|
+
import { buildSchemaSync } from "type-graphql";
|
|
3
|
+
import { CopilotResolver } from "../../graphql/resolvers/copilot.resolver";
|
|
4
|
+
import { useDeferStream } from "@graphql-yoga/plugin-defer-stream";
|
|
5
|
+
import { CopilotRuntime } from "../runtime/copilot-runtime";
|
|
6
|
+
import { CopilotServiceAdapter } from "../../service-adapters";
|
|
7
|
+
import { CopilotCloudOptions } from "../cloud";
|
|
8
|
+
import { LogLevel, createLogger } from "../../lib/logger";
|
|
9
|
+
import { createYoga } from "graphql-yoga";
|
|
10
|
+
import telemetry from "../telemetry-client";
|
|
11
|
+
import { StateResolver } from "../../graphql/resolvers/state.resolver";
|
|
12
|
+
import * as packageJson from "../../../package.json";
|
|
13
|
+
import { CopilotKitError, CopilotKitErrorCode } from "@copilotkit/shared";
|
|
14
|
+
|
|
15
|
+
const logger = createLogger();
|
|
16
|
+
|
|
17
|
+
export const addCustomHeaderPlugin = {
|
|
18
|
+
onResponse({ response }) {
|
|
19
|
+
// Set your custom header; adjust the header name and value as needed
|
|
20
|
+
response.headers.set("X-CopilotKit-Runtime-Version", packageJson.version);
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type AnyPrimitive = string | boolean | number | null;
|
|
25
|
+
export type CopilotRequestContextProperties = Record<
|
|
26
|
+
string,
|
|
27
|
+
AnyPrimitive | Record<string, AnyPrimitive>
|
|
28
|
+
>;
|
|
29
|
+
|
|
30
|
+
export type GraphQLContext = YogaInitialContext & {
|
|
31
|
+
_copilotkit: CreateCopilotRuntimeServerOptions;
|
|
32
|
+
properties: CopilotRequestContextProperties;
|
|
33
|
+
logger: typeof logger;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export interface CreateCopilotRuntimeServerOptions {
|
|
37
|
+
runtime: CopilotRuntime<any>;
|
|
38
|
+
serviceAdapter?: CopilotServiceAdapter;
|
|
39
|
+
endpoint: string;
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
cloud?: CopilotCloudOptions;
|
|
42
|
+
properties?: CopilotRequestContextProperties;
|
|
43
|
+
logLevel?: LogLevel;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function createContext(
|
|
47
|
+
initialContext: YogaInitialContext,
|
|
48
|
+
copilotKitContext: CreateCopilotRuntimeServerOptions,
|
|
49
|
+
contextLogger: typeof logger,
|
|
50
|
+
properties: CopilotRequestContextProperties = {},
|
|
51
|
+
): Promise<Partial<GraphQLContext>> {
|
|
52
|
+
logger.debug({ copilotKitContext }, "Creating GraphQL context");
|
|
53
|
+
const ctx: GraphQLContext = {
|
|
54
|
+
...initialContext,
|
|
55
|
+
_copilotkit: {
|
|
56
|
+
...copilotKitContext,
|
|
57
|
+
},
|
|
58
|
+
properties: { ...properties },
|
|
59
|
+
logger: contextLogger,
|
|
60
|
+
};
|
|
61
|
+
return ctx;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function buildSchema(
|
|
65
|
+
options: {
|
|
66
|
+
emitSchemaFile?: string;
|
|
67
|
+
} = {},
|
|
68
|
+
) {
|
|
69
|
+
logger.debug("Building GraphQL schema...");
|
|
70
|
+
const schema = buildSchemaSync({
|
|
71
|
+
resolvers: [CopilotResolver, StateResolver],
|
|
72
|
+
emitSchemaFile: options.emitSchemaFile,
|
|
73
|
+
});
|
|
74
|
+
logger.debug("GraphQL schema built successfully");
|
|
75
|
+
return schema;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type CommonConfig = {
|
|
79
|
+
logging: typeof logger;
|
|
80
|
+
schema: ReturnType<typeof buildSchema>;
|
|
81
|
+
plugins: Parameters<typeof createYoga>[0]["plugins"];
|
|
82
|
+
context: (ctx: YogaInitialContext) => Promise<Partial<GraphQLContext>>;
|
|
83
|
+
maskedErrors: {
|
|
84
|
+
maskError: (error: any, message: string, isDev?: boolean) => any;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export function getCommonConfig(options: CreateCopilotRuntimeServerOptions): CommonConfig {
|
|
89
|
+
const logLevel = (process.env.LOG_LEVEL as LogLevel) || (options.logLevel as LogLevel) || "error";
|
|
90
|
+
const logger = createLogger({ level: logLevel, component: "getCommonConfig" });
|
|
91
|
+
|
|
92
|
+
const contextLogger = createLogger({ level: logLevel });
|
|
93
|
+
|
|
94
|
+
if (options.cloud) {
|
|
95
|
+
telemetry.setCloudConfiguration({
|
|
96
|
+
publicApiKey: options.cloud.publicApiKey,
|
|
97
|
+
baseUrl: options.cloud.baseUrl,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (options.properties?._copilotkit) {
|
|
102
|
+
telemetry.setGlobalProperties({
|
|
103
|
+
_copilotkit: {
|
|
104
|
+
...(options.properties._copilotkit as Record<string, any>),
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
telemetry.setGlobalProperties({
|
|
110
|
+
runtime: {
|
|
111
|
+
serviceAdapter: options.serviceAdapter.constructor.name,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
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
|
+
|
|
124
|
+
return {
|
|
125
|
+
logging: createLogger({ component: "Yoga GraphQL", level: logLevel }),
|
|
126
|
+
schema: buildSchema(),
|
|
127
|
+
plugins: [useDeferStream(), addCustomHeaderPlugin],
|
|
128
|
+
context: (ctx: YogaInitialContext): Promise<Partial<GraphQLContext>> =>
|
|
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
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import createPinoLogger from "pino";
|
|
2
|
+
import pretty from "pino-pretty";
|
|
3
|
+
|
|
4
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
5
|
+
|
|
6
|
+
export type CopilotRuntimeLogger = ReturnType<typeof createLogger>;
|
|
7
|
+
|
|
8
|
+
export function createLogger(options?: { level?: LogLevel; component?: string }) {
|
|
9
|
+
const { level, component } = options || {};
|
|
10
|
+
const stream = pretty({ colorize: true });
|
|
11
|
+
|
|
12
|
+
const logger = createPinoLogger(
|
|
13
|
+
{
|
|
14
|
+
level: process.env.LOG_LEVEL || level || "error",
|
|
15
|
+
redact: {
|
|
16
|
+
paths: ["pid", "hostname"],
|
|
17
|
+
remove: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
stream,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (component) {
|
|
24
|
+
return logger.child({ component });
|
|
25
|
+
} else {
|
|
26
|
+
return logger;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { RuntimeEventSource, RuntimeEventTypes } from "../service-adapters/events";
|
|
2
|
+
|
|
3
|
+
export interface LLMRequestData {
|
|
4
|
+
threadId?: string;
|
|
5
|
+
runId?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
messages: any[];
|
|
8
|
+
actions?: any[];
|
|
9
|
+
forwardedParameters?: any;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
provider?: string;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface LLMResponseData {
|
|
16
|
+
threadId: string;
|
|
17
|
+
runId?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
output: any;
|
|
20
|
+
latency: number;
|
|
21
|
+
timestamp: number;
|
|
22
|
+
provider?: string;
|
|
23
|
+
isProgressiveChunk?: boolean;
|
|
24
|
+
isFinalResponse?: boolean;
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface LLMErrorData {
|
|
29
|
+
threadId?: string;
|
|
30
|
+
runId?: string;
|
|
31
|
+
model?: string;
|
|
32
|
+
error: Error | string;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
provider?: string;
|
|
35
|
+
[key: string]: any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CopilotObservabilityHooks {
|
|
39
|
+
handleRequest: (data: LLMRequestData) => void | Promise<void>;
|
|
40
|
+
handleResponse: (data: LLMResponseData) => void | Promise<void>;
|
|
41
|
+
handleError: (data: LLMErrorData) => void | Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration for CopilotKit logging functionality.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* Custom logging handlers require a valid CopilotKit public API key.
|
|
49
|
+
* Sign up at https://docs.copilotkit.ai/quickstart#get-a-copilot-cloud-public-api-key to get your key.
|
|
50
|
+
*/
|
|
51
|
+
export interface CopilotObservabilityConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Enable or disable logging functionality.
|
|
54
|
+
*
|
|
55
|
+
* @default false
|
|
56
|
+
*/
|
|
57
|
+
enabled: boolean;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Controls whether logs are streamed progressively or buffered.
|
|
61
|
+
* - When true: Each token and update is logged as it's generated (real-time)
|
|
62
|
+
* - When false: Complete responses are logged after completion (batched)
|
|
63
|
+
*
|
|
64
|
+
* @default true
|
|
65
|
+
*/
|
|
66
|
+
progressive: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Custom observability hooks for request, response, and error events.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Using custom observability hooks requires a valid CopilotKit public API key.
|
|
73
|
+
*/
|
|
74
|
+
hooks: CopilotObservabilityHooks;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Setup progressive logging by wrapping the event stream
|
|
79
|
+
*/
|
|
80
|
+
function setupProgressiveLogging(
|
|
81
|
+
eventSource: RuntimeEventSource,
|
|
82
|
+
streamedChunks: any[],
|
|
83
|
+
requestStartTime: number,
|
|
84
|
+
context: {
|
|
85
|
+
threadId?: string;
|
|
86
|
+
runId?: string;
|
|
87
|
+
model?: string;
|
|
88
|
+
provider?: string;
|
|
89
|
+
agentName?: string;
|
|
90
|
+
nodeName?: string;
|
|
91
|
+
},
|
|
92
|
+
publicApiKey?: string,
|
|
93
|
+
): void {
|
|
94
|
+
if (this.observability?.enabled && this.observability.progressive && publicApiKey) {
|
|
95
|
+
// Keep reference to original stream function
|
|
96
|
+
const originalStream = eventSource.stream.bind(eventSource);
|
|
97
|
+
|
|
98
|
+
// Wrap the stream function to intercept events
|
|
99
|
+
eventSource.stream = async (callback) => {
|
|
100
|
+
await originalStream(async (eventStream$) => {
|
|
101
|
+
// Create subscription to capture streaming events
|
|
102
|
+
eventStream$.subscribe({
|
|
103
|
+
next: (event) => {
|
|
104
|
+
// Only log content chunks
|
|
105
|
+
if (event.type === RuntimeEventTypes.TextMessageContent) {
|
|
106
|
+
// Store the chunk
|
|
107
|
+
streamedChunks.push(event.content);
|
|
108
|
+
|
|
109
|
+
// Log each chunk separately for progressive mode
|
|
110
|
+
try {
|
|
111
|
+
const progressiveData: LLMResponseData = {
|
|
112
|
+
threadId: context.threadId || "",
|
|
113
|
+
runId: context.runId,
|
|
114
|
+
model: context.model,
|
|
115
|
+
output: event.content,
|
|
116
|
+
latency: Date.now() - requestStartTime,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
provider: context.provider,
|
|
119
|
+
isProgressiveChunk: true,
|
|
120
|
+
agentName: context.agentName,
|
|
121
|
+
nodeName: context.nodeName,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Use Promise to handle async logger without awaiting
|
|
125
|
+
Promise.resolve()
|
|
126
|
+
.then(() => {
|
|
127
|
+
this.observability.hooks.handleResponse(progressiveData);
|
|
128
|
+
})
|
|
129
|
+
.catch((error) => {
|
|
130
|
+
console.error("Error in progressive logging:", error);
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error("Error preparing progressive log data:", error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Call the original callback with the event stream
|
|
140
|
+
await callback(eventStream$);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Log error if observability is enabled
|
|
148
|
+
*/
|
|
149
|
+
async function logObservabilityError(
|
|
150
|
+
errorData: LLMErrorData,
|
|
151
|
+
publicApiKey?: string,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
if (this.observability?.enabled && publicApiKey) {
|
|
154
|
+
try {
|
|
155
|
+
await this.observability.hooks.handleError(errorData);
|
|
156
|
+
} catch (logError) {
|
|
157
|
+
console.error("Error logging LLM error:", logError);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -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
|
+
});
|