@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.
Files changed (122) hide show
  1. package/.eslintrc.js +7 -0
  2. package/CHANGELOG.md +2905 -0
  3. package/LICENSE +21 -0
  4. package/README.md +76 -0
  5. package/__snapshots__/schema/schema.graphql +371 -0
  6. package/dist/index.d.ts +1495 -0
  7. package/dist/index.js +5644 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +5601 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/langgraph.d.ts +284 -0
  12. package/dist/langgraph.js +211 -0
  13. package/dist/langgraph.js.map +1 -0
  14. package/dist/langgraph.mjs +206 -0
  15. package/dist/langgraph.mjs.map +1 -0
  16. package/dist/v2/index.d.ts +2 -0
  17. package/dist/v2/index.js +22 -0
  18. package/dist/v2/index.js.map +1 -0
  19. package/dist/v2/index.mjs +5 -0
  20. package/dist/v2/index.mjs.map +1 -0
  21. package/jest.config.js +10 -0
  22. package/package.json +143 -0
  23. package/scripts/generate-gql-schema.ts +13 -0
  24. package/src/agents/langgraph/event-source.ts +329 -0
  25. package/src/agents/langgraph/events.ts +377 -0
  26. package/src/graphql/inputs/action.input.ts +16 -0
  27. package/src/graphql/inputs/agent-session.input.ts +13 -0
  28. package/src/graphql/inputs/agent-state.input.ts +13 -0
  29. package/src/graphql/inputs/cloud-guardrails.input.ts +16 -0
  30. package/src/graphql/inputs/cloud.input.ts +8 -0
  31. package/src/graphql/inputs/context-property.input.ts +10 -0
  32. package/src/graphql/inputs/copilot-context.input.ts +10 -0
  33. package/src/graphql/inputs/custom-property.input.ts +15 -0
  34. package/src/graphql/inputs/extensions.input.ts +21 -0
  35. package/src/graphql/inputs/forwarded-parameters.input.ts +22 -0
  36. package/src/graphql/inputs/frontend.input.ts +14 -0
  37. package/src/graphql/inputs/generate-copilot-response.input.ts +59 -0
  38. package/src/graphql/inputs/load-agent-state.input.ts +10 -0
  39. package/src/graphql/inputs/message.input.ts +110 -0
  40. package/src/graphql/inputs/meta-event.input.ts +18 -0
  41. package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
  42. package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
  43. package/src/graphql/message-conversion/gql-to-agui.test.ts +1580 -0
  44. package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
  45. package/src/graphql/message-conversion/index.ts +2 -0
  46. package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
  47. package/src/graphql/resolvers/copilot.resolver.ts +708 -0
  48. package/src/graphql/resolvers/state.resolver.ts +27 -0
  49. package/src/graphql/types/agents-response.type.ts +19 -0
  50. package/src/graphql/types/base/index.ts +10 -0
  51. package/src/graphql/types/converted/index.ts +176 -0
  52. package/src/graphql/types/copilot-response.type.ts +138 -0
  53. package/src/graphql/types/enums.ts +38 -0
  54. package/src/graphql/types/extensions-response.type.ts +23 -0
  55. package/src/graphql/types/guardrails-result.type.ts +20 -0
  56. package/src/graphql/types/load-agent-state-response.type.ts +17 -0
  57. package/src/graphql/types/message-status.type.ts +42 -0
  58. package/src/graphql/types/meta-events.type.ts +71 -0
  59. package/src/graphql/types/response-status.type.ts +66 -0
  60. package/src/index.ts +4 -0
  61. package/src/langgraph.ts +1 -0
  62. package/src/lib/cloud/index.ts +4 -0
  63. package/src/lib/error-messages.ts +200 -0
  64. package/src/lib/index.ts +52 -0
  65. package/src/lib/integrations/index.ts +6 -0
  66. package/src/lib/integrations/nest/index.ts +14 -0
  67. package/src/lib/integrations/nextjs/app-router.ts +38 -0
  68. package/src/lib/integrations/nextjs/pages-router.ts +39 -0
  69. package/src/lib/integrations/node-express/index.ts +14 -0
  70. package/src/lib/integrations/node-http/index.ts +143 -0
  71. package/src/lib/integrations/node-http/request-handler.ts +111 -0
  72. package/src/lib/integrations/shared.ts +161 -0
  73. package/src/lib/logger.ts +28 -0
  74. package/src/lib/observability.ts +160 -0
  75. package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
  76. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
  77. package/src/lib/runtime/agent-integrations/langgraph/agent.ts +209 -0
  78. package/src/lib/runtime/agent-integrations/langgraph/consts.ts +34 -0
  79. package/src/lib/runtime/agent-integrations/langgraph/index.ts +2 -0
  80. package/src/lib/runtime/copilot-runtime.ts +710 -0
  81. package/src/lib/runtime/mcp-tools-utils.ts +254 -0
  82. package/src/lib/runtime/retry-utils.ts +96 -0
  83. package/src/lib/runtime/telemetry-agent-runner.ts +139 -0
  84. package/src/lib/runtime/types.ts +49 -0
  85. package/src/lib/runtime/utils.ts +87 -0
  86. package/src/lib/streaming.ts +202 -0
  87. package/src/lib/telemetry-client.ts +64 -0
  88. package/src/service-adapters/anthropic/anthropic-adapter.ts +452 -0
  89. package/src/service-adapters/anthropic/utils.ts +152 -0
  90. package/src/service-adapters/bedrock/bedrock-adapter.ts +73 -0
  91. package/src/service-adapters/conversion.ts +67 -0
  92. package/src/service-adapters/empty/empty-adapter.ts +38 -0
  93. package/src/service-adapters/events.ts +294 -0
  94. package/src/service-adapters/experimental/ollama/ollama-adapter.ts +84 -0
  95. package/src/service-adapters/google/google-genai-adapter.test.ts +104 -0
  96. package/src/service-adapters/google/google-genai-adapter.ts +88 -0
  97. package/src/service-adapters/groq/groq-adapter.ts +203 -0
  98. package/src/service-adapters/index.ts +18 -0
  99. package/src/service-adapters/langchain/langchain-adapter.ts +111 -0
  100. package/src/service-adapters/langchain/langserve.ts +88 -0
  101. package/src/service-adapters/langchain/types.ts +14 -0
  102. package/src/service-adapters/langchain/utils.ts +313 -0
  103. package/src/service-adapters/openai/openai-adapter.ts +283 -0
  104. package/src/service-adapters/openai/openai-assistant-adapter.ts +344 -0
  105. package/src/service-adapters/openai/utils.ts +199 -0
  106. package/src/service-adapters/service-adapter.ts +41 -0
  107. package/src/service-adapters/shared/error-utils.ts +61 -0
  108. package/src/service-adapters/shared/index.ts +1 -0
  109. package/src/service-adapters/unify/unify-adapter.ts +151 -0
  110. package/src/utils/failed-response-status-reasons.ts +70 -0
  111. package/src/utils/index.ts +1 -0
  112. package/src/v2/index.ts +3 -0
  113. package/tests/global.d.ts +13 -0
  114. package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
  115. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +389 -0
  116. package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
  117. package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
  118. package/tests/setup.jest.ts +21 -0
  119. package/tests/tsconfig.json +10 -0
  120. package/tsconfig.json +13 -0
  121. package/tsup.config.ts +20 -0
  122. package/typedoc.json +4 -0
@@ -0,0 +1,67 @@
1
+ import {
2
+ ActionExecutionMessage,
3
+ Message,
4
+ ResultMessage,
5
+ TextMessage,
6
+ AgentStateMessage,
7
+ ImageMessage,
8
+ } from "../graphql/types/converted";
9
+ import { MessageInput } from "../graphql/inputs/message.input";
10
+ import { plainToInstance } from "class-transformer";
11
+ import { tryMap } from "@copilotkit/shared";
12
+
13
+ export function convertGqlInputToMessages(inputMessages: MessageInput[]): Message[] {
14
+ const messages = tryMap(inputMessages, (message) => {
15
+ if (message.textMessage) {
16
+ return plainToInstance(TextMessage, {
17
+ id: message.id,
18
+ createdAt: message.createdAt,
19
+ role: message.textMessage.role,
20
+ content: message.textMessage.content,
21
+ parentMessageId: message.textMessage.parentMessageId,
22
+ });
23
+ } else if (message.imageMessage) {
24
+ return plainToInstance(ImageMessage, {
25
+ id: message.id,
26
+ createdAt: message.createdAt,
27
+ role: message.imageMessage.role,
28
+ bytes: message.imageMessage.bytes,
29
+ format: message.imageMessage.format,
30
+ parentMessageId: message.imageMessage.parentMessageId,
31
+ });
32
+ } else if (message.actionExecutionMessage) {
33
+ return plainToInstance(ActionExecutionMessage, {
34
+ id: message.id,
35
+ createdAt: message.createdAt,
36
+ name: message.actionExecutionMessage.name,
37
+ arguments: JSON.parse(message.actionExecutionMessage.arguments),
38
+ parentMessageId: message.actionExecutionMessage.parentMessageId,
39
+ });
40
+ } else if (message.resultMessage) {
41
+ return plainToInstance(ResultMessage, {
42
+ id: message.id,
43
+ createdAt: message.createdAt,
44
+ actionExecutionId: message.resultMessage.actionExecutionId,
45
+ actionName: message.resultMessage.actionName,
46
+ result: message.resultMessage.result,
47
+ });
48
+ } else if (message.agentStateMessage) {
49
+ return plainToInstance(AgentStateMessage, {
50
+ id: message.id,
51
+ threadId: message.agentStateMessage.threadId,
52
+ createdAt: message.createdAt,
53
+ agentName: message.agentStateMessage.agentName,
54
+ nodeName: message.agentStateMessage.nodeName,
55
+ runId: message.agentStateMessage.runId,
56
+ active: message.agentStateMessage.active,
57
+ role: message.agentStateMessage.role,
58
+ state: JSON.parse(message.agentStateMessage.state),
59
+ running: message.agentStateMessage.running,
60
+ });
61
+ } else {
62
+ return null;
63
+ }
64
+ });
65
+
66
+ return messages.filter((m) => m);
67
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * CopilotKit Empty Adapter
3
+ *
4
+ * This adapter is meant to preserve adherence to runtime requirements, while doing nothing
5
+ * Ideal if you don't want to connect an LLM the to the runtime, and only use your LangGraph agent.
6
+ * Be aware that Copilot Suggestions will not work if you use this adapter
7
+ *
8
+ * ## Example
9
+ *
10
+ * ```ts
11
+ * import { CopilotRuntime, EmptyAdapter } from "@copilotkit/runtime";
12
+ *
13
+ * const copilotKit = new CopilotRuntime();
14
+ *
15
+ * return new EmptyAdapter();
16
+ * ```
17
+ */
18
+ import {
19
+ CopilotServiceAdapter,
20
+ CopilotRuntimeChatCompletionRequest,
21
+ CopilotRuntimeChatCompletionResponse,
22
+ } from "../service-adapter";
23
+ import { randomUUID } from "@copilotkit/shared";
24
+
25
+ export class EmptyAdapter implements CopilotServiceAdapter {
26
+ async process(
27
+ request: CopilotRuntimeChatCompletionRequest,
28
+ ): Promise<CopilotRuntimeChatCompletionResponse> {
29
+ return {
30
+ threadId: request.threadId || randomUUID(),
31
+ };
32
+ }
33
+ public get name() {
34
+ return "EmptyAdapter";
35
+ }
36
+ }
37
+
38
+ export const ExperimentalEmptyAdapter = EmptyAdapter;
@@ -0,0 +1,294 @@
1
+ import {
2
+ Action,
3
+ CopilotKitError,
4
+ CopilotKitErrorCode,
5
+ CopilotKitLowLevelError,
6
+ ensureStructuredError,
7
+ randomId,
8
+ Severity,
9
+ } from "@copilotkit/shared";
10
+ import { plainToInstance } from "class-transformer";
11
+ import {
12
+ catchError,
13
+ concat,
14
+ concatMap,
15
+ EMPTY,
16
+ firstValueFrom,
17
+ from,
18
+ of,
19
+ ReplaySubject,
20
+ scan,
21
+ Subject,
22
+ } from "rxjs";
23
+ import { ActionInput } from "../graphql/inputs/action.input";
24
+ import { ActionExecutionMessage, ResultMessage, TextMessage } from "../graphql/types/converted";
25
+ import { GuardrailsResult } from "../graphql/types/guardrails-result.type";
26
+ import { generateHelpfulErrorMessage } from "../lib/streaming";
27
+ import telemetry from "../lib/telemetry-client";
28
+ import { streamLangChainResponse } from "./langchain/utils";
29
+
30
+ export enum RuntimeEventTypes {
31
+ TextMessageStart = "TextMessageStart",
32
+ TextMessageContent = "TextMessageContent",
33
+ TextMessageEnd = "TextMessageEnd",
34
+ ActionExecutionStart = "ActionExecutionStart",
35
+ ActionExecutionArgs = "ActionExecutionArgs",
36
+ ActionExecutionEnd = "ActionExecutionEnd",
37
+ ActionExecutionResult = "ActionExecutionResult",
38
+ AgentStateMessage = "AgentStateMessage",
39
+ MetaEvent = "MetaEvent",
40
+ RunError = "RunError",
41
+ }
42
+
43
+ export enum RuntimeMetaEventName {
44
+ LangGraphInterruptEvent = "LangGraphInterruptEvent",
45
+ LangGraphInterruptResumeEvent = "LangGraphInterruptResumeEvent",
46
+ CopilotKitLangGraphInterruptEvent = "CopilotKitLangGraphInterruptEvent",
47
+ }
48
+
49
+ export type RunTimeMetaEvent =
50
+ | {
51
+ type: RuntimeEventTypes.MetaEvent;
52
+ name: RuntimeMetaEventName.LangGraphInterruptEvent;
53
+ value: string;
54
+ }
55
+ | {
56
+ type: RuntimeEventTypes.MetaEvent;
57
+ name: RuntimeMetaEventName.CopilotKitLangGraphInterruptEvent;
58
+ data: { value: string; messages: (TextMessage | ActionExecutionMessage | ResultMessage)[] };
59
+ }
60
+ | {
61
+ type: RuntimeEventTypes.MetaEvent;
62
+ name: RuntimeMetaEventName.LangGraphInterruptResumeEvent;
63
+ data: string;
64
+ };
65
+
66
+ export type RuntimeErrorEvent = {
67
+ type: RuntimeEventTypes.RunError;
68
+ message: string;
69
+ code?: string;
70
+ };
71
+
72
+ export type RuntimeEvent =
73
+ | { type: RuntimeEventTypes.TextMessageStart; messageId: string; parentMessageId?: string }
74
+ | {
75
+ type: RuntimeEventTypes.TextMessageContent;
76
+ messageId: string;
77
+ content: string;
78
+ }
79
+ | { type: RuntimeEventTypes.TextMessageEnd; messageId: string }
80
+ | {
81
+ type: RuntimeEventTypes.ActionExecutionStart;
82
+ actionExecutionId: string;
83
+ actionName: string;
84
+ parentMessageId?: string;
85
+ }
86
+ | { type: RuntimeEventTypes.ActionExecutionArgs; actionExecutionId: string; args: string }
87
+ | { type: RuntimeEventTypes.ActionExecutionEnd; actionExecutionId: string }
88
+ | {
89
+ type: RuntimeEventTypes.ActionExecutionResult;
90
+ actionName: string;
91
+ actionExecutionId: string;
92
+ result: string;
93
+ }
94
+ | {
95
+ type: RuntimeEventTypes.AgentStateMessage;
96
+ threadId: string;
97
+ agentName: string;
98
+ nodeName: string;
99
+ runId: string;
100
+ active: boolean;
101
+ role: string;
102
+ state: string;
103
+ running: boolean;
104
+ }
105
+ | RunTimeMetaEvent
106
+ | RuntimeErrorEvent;
107
+
108
+ interface RuntimeEventWithState {
109
+ event: RuntimeEvent | null;
110
+ callActionServerSide: boolean;
111
+ action: Action<any> | null;
112
+ actionExecutionId: string | null;
113
+ args: string;
114
+ actionExecutionParentMessageId: string | null;
115
+ }
116
+
117
+ type EventSourceCallback = (eventStream$: RuntimeEventSubject) => Promise<void>;
118
+
119
+ export class RuntimeEventSubject extends ReplaySubject<RuntimeEvent> {
120
+ constructor() {
121
+ super();
122
+ }
123
+
124
+ sendTextMessageStart({
125
+ messageId,
126
+ parentMessageId,
127
+ }: {
128
+ messageId: string;
129
+ parentMessageId?: string;
130
+ }) {
131
+ this.next({ type: RuntimeEventTypes.TextMessageStart, messageId, parentMessageId });
132
+ }
133
+
134
+ sendTextMessageContent({ messageId, content }: { messageId: string; content: string }) {
135
+ this.next({ type: RuntimeEventTypes.TextMessageContent, content, messageId });
136
+ }
137
+
138
+ sendTextMessageEnd({ messageId }: { messageId: string }) {
139
+ this.next({ type: RuntimeEventTypes.TextMessageEnd, messageId });
140
+ }
141
+
142
+ sendTextMessage(messageId: string, content: string) {
143
+ this.sendTextMessageStart({ messageId });
144
+ this.sendTextMessageContent({ messageId, content });
145
+ this.sendTextMessageEnd({ messageId });
146
+ }
147
+
148
+ sendActionExecutionStart({
149
+ actionExecutionId,
150
+ actionName,
151
+ parentMessageId,
152
+ }: {
153
+ actionExecutionId: string;
154
+ actionName: string;
155
+ parentMessageId?: string;
156
+ }) {
157
+ this.next({
158
+ type: RuntimeEventTypes.ActionExecutionStart,
159
+ actionExecutionId,
160
+ actionName,
161
+ parentMessageId,
162
+ });
163
+ }
164
+
165
+ sendActionExecutionArgs({
166
+ actionExecutionId,
167
+ args,
168
+ }: {
169
+ actionExecutionId: string;
170
+ args: string;
171
+ }) {
172
+ this.next({ type: RuntimeEventTypes.ActionExecutionArgs, args, actionExecutionId });
173
+ }
174
+
175
+ sendActionExecutionEnd({ actionExecutionId }: { actionExecutionId: string }) {
176
+ this.next({ type: RuntimeEventTypes.ActionExecutionEnd, actionExecutionId });
177
+ }
178
+
179
+ sendActionExecution({
180
+ actionExecutionId,
181
+ actionName,
182
+ args,
183
+ parentMessageId,
184
+ }: {
185
+ actionExecutionId: string;
186
+ actionName: string;
187
+ args: string;
188
+ parentMessageId?: string;
189
+ }) {
190
+ this.sendActionExecutionStart({ actionExecutionId, actionName, parentMessageId });
191
+ this.sendActionExecutionArgs({ actionExecutionId, args });
192
+ this.sendActionExecutionEnd({ actionExecutionId });
193
+ }
194
+
195
+ sendActionExecutionResult({
196
+ actionExecutionId,
197
+ actionName,
198
+ result,
199
+ error,
200
+ }: {
201
+ actionExecutionId: string;
202
+ actionName: string;
203
+ result?: string;
204
+ error?: { code: string; message: string };
205
+ }) {
206
+ this.next({
207
+ type: RuntimeEventTypes.ActionExecutionResult,
208
+ actionName,
209
+ actionExecutionId,
210
+ result: ResultMessage.encodeResult(result, error),
211
+ });
212
+ }
213
+
214
+ sendAgentStateMessage({
215
+ threadId,
216
+ agentName,
217
+ nodeName,
218
+ runId,
219
+ active,
220
+ role,
221
+ state,
222
+ running,
223
+ }: {
224
+ threadId: string;
225
+ agentName: string;
226
+ nodeName: string;
227
+ runId: string;
228
+ active: boolean;
229
+ role: string;
230
+ state: string;
231
+ running: boolean;
232
+ }) {
233
+ this.next({
234
+ type: RuntimeEventTypes.AgentStateMessage,
235
+ threadId,
236
+ agentName,
237
+ nodeName,
238
+ runId,
239
+ active,
240
+ role,
241
+ state,
242
+ running,
243
+ });
244
+ }
245
+ }
246
+
247
+ export class RuntimeEventSource {
248
+ private eventStream$ = new RuntimeEventSubject();
249
+ private callback!: EventSourceCallback;
250
+ private errorHandler?: (error: any, context: any) => Promise<void>;
251
+ private errorContext?: any;
252
+
253
+ constructor(params?: {
254
+ errorHandler?: (error: any, context: any) => Promise<void>;
255
+ errorContext?: any;
256
+ }) {
257
+ this.errorHandler = params?.errorHandler;
258
+ this.errorContext = params?.errorContext;
259
+ }
260
+
261
+ async stream(callback: EventSourceCallback): Promise<void> {
262
+ this.callback = callback;
263
+ }
264
+ }
265
+
266
+ function convertStreamingErrorToStructured(error: any): CopilotKitError {
267
+ // Determine a more helpful error message based on context
268
+ let helpfulMessage = generateHelpfulErrorMessage(error, "event streaming connection");
269
+
270
+ // For network-related errors, use CopilotKitLowLevelError to preserve the original error
271
+ if (
272
+ error?.message?.includes("fetch failed") ||
273
+ error?.message?.includes("ECONNREFUSED") ||
274
+ error?.message?.includes("ENOTFOUND") ||
275
+ error?.message?.includes("ETIMEDOUT") ||
276
+ error?.message?.includes("terminated") ||
277
+ error?.cause?.code === "UND_ERR_SOCKET" ||
278
+ error?.message?.includes("other side closed") ||
279
+ error?.code === "UND_ERR_SOCKET"
280
+ ) {
281
+ return new CopilotKitLowLevelError({
282
+ error: error instanceof Error ? error : new Error(String(error)),
283
+ url: "event streaming connection",
284
+ message: helpfulMessage,
285
+ });
286
+ }
287
+
288
+ // For all other errors, preserve the raw error in a basic CopilotKitError
289
+ return new CopilotKitError({
290
+ message: helpfulMessage,
291
+ code: CopilotKitErrorCode.UNKNOWN,
292
+ severity: Severity.CRITICAL,
293
+ });
294
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * CopilotKit Adapter for Ollama
3
+ *
4
+ * <RequestExample>
5
+ * ```jsx CopilotRuntime Example
6
+ * const copilotKit = new CopilotRuntime();
7
+ * return copilotKit.response(req, new OllamaAdapter());
8
+ * ```
9
+ * </RequestExample>
10
+ *
11
+ * You can easily set the model to use by passing it to the constructor.
12
+ * ```jsx
13
+ * const copilotKit = new CopilotRuntime();
14
+ * return copilotKit.response(
15
+ * req,
16
+ * new OllamaAdapter({ model: "llama3-70b-8192" }),
17
+ * );
18
+ * ```
19
+ */
20
+ import { TextMessage } from "../../../graphql/types/converted";
21
+ import {
22
+ CopilotServiceAdapter,
23
+ CopilotRuntimeChatCompletionRequest,
24
+ CopilotRuntimeChatCompletionResponse,
25
+ } from "../../service-adapter";
26
+ import { randomId, randomUUID } from "@copilotkit/shared";
27
+
28
+ const DEFAULT_MODEL = "llama3:latest";
29
+
30
+ interface OllamaAdapterOptions {
31
+ model?: string;
32
+ }
33
+
34
+ export class ExperimentalOllamaAdapter implements CopilotServiceAdapter {
35
+ public model: string;
36
+ public provider = "ollama";
37
+ public get name() {
38
+ return "OllamaAdapter";
39
+ }
40
+
41
+ constructor(options?: OllamaAdapterOptions) {
42
+ if (options?.model) {
43
+ this.model = options.model;
44
+ } else {
45
+ this.model = DEFAULT_MODEL;
46
+ }
47
+ }
48
+
49
+ async process(
50
+ request: CopilotRuntimeChatCompletionRequest,
51
+ ): Promise<CopilotRuntimeChatCompletionResponse> {
52
+ const { messages, actions, eventSource } = request;
53
+ // const messages = this.transformMessages(forwardedProps.messages);
54
+
55
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
56
+ const { Ollama } = require("@langchain/community/llms/ollama");
57
+ const ollama = new Ollama({
58
+ model: this.model,
59
+ });
60
+ const contents = (messages.filter((m) => m.isTextMessage()) as TextMessage[]).map(
61
+ (m) => m.content,
62
+ );
63
+ const _stream = await ollama.stream(contents); // [TODO] role info is dropped...
64
+
65
+ eventSource.stream(async (eventStream$) => {
66
+ const currentMessageId = randomId();
67
+ eventStream$.sendTextMessageStart({ messageId: currentMessageId });
68
+ for await (const chunkText of _stream) {
69
+ eventStream$.sendTextMessageContent({
70
+ messageId: currentMessageId,
71
+ content: chunkText,
72
+ });
73
+ }
74
+ eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
75
+ // we may need to add this later.. [nc]
76
+ // let calls = (await result.response).functionCalls();
77
+
78
+ eventStream$.complete();
79
+ });
80
+ return {
81
+ threadId: request.threadId || randomUUID(),
82
+ };
83
+ }
84
+ }
@@ -0,0 +1,104 @@
1
+ import { GoogleGenerativeAIAdapter } from "./google-genai-adapter";
2
+ import { AIMessage, HumanMessage, SystemMessage } from "@langchain/core/messages";
3
+
4
+ // Mock ChatGoogle to capture what messages are passed to it
5
+ jest.mock("@langchain/google-gauth", () => ({
6
+ ChatGoogle: jest.fn().mockImplementation(() => ({
7
+ bindTools: jest.fn().mockReturnThis(),
8
+ stream: jest.fn().mockImplementation((messages) => {
9
+ // Return the messages so we can verify filtering
10
+ return Promise.resolve({ filteredMessages: messages });
11
+ }),
12
+ })),
13
+ }));
14
+
15
+ describe("GoogleGenerativeAIAdapter", () => {
16
+ it("should filter out empty AIMessages to prevent Gemini validation errors", async () => {
17
+ const adapter = new GoogleGenerativeAIAdapter();
18
+
19
+ // Create a mix of messages including an empty AIMessage (the problematic case)
20
+ const messages = [
21
+ new HumanMessage("Hello"),
22
+ new AIMessage(""), // This should be filtered out
23
+ new HumanMessage("How are you?"),
24
+ new AIMessage("I'm doing well!"), // This should be kept
25
+ new SystemMessage("You are a helpful assistant"), // This should be kept
26
+ new AIMessage(""), // Another empty one to filter out
27
+ ];
28
+
29
+ // Access the internal chainFn to test the filtering logic
30
+ const chainFnResult = await (adapter as any).options.chainFn({
31
+ messages,
32
+ tools: [],
33
+ threadId: "test-thread",
34
+ });
35
+
36
+ // The result should be filtered messages passed to ChatGoogle.stream()
37
+ const { filteredMessages } = chainFnResult;
38
+
39
+ // Should only contain non-empty messages
40
+ expect(filteredMessages).toHaveLength(4);
41
+ expect(filteredMessages[0]).toBeInstanceOf(HumanMessage);
42
+ expect(filteredMessages[0].content).toBe("Hello");
43
+ expect(filteredMessages[1]).toBeInstanceOf(HumanMessage);
44
+ expect(filteredMessages[1].content).toBe("How are you?");
45
+ expect(filteredMessages[2]).toBeInstanceOf(AIMessage);
46
+ expect(filteredMessages[2].content).toBe("I'm doing well!");
47
+ expect(filteredMessages[3]).toBeInstanceOf(SystemMessage);
48
+ expect(filteredMessages[3].content).toBe("You are a helpful assistant");
49
+ });
50
+
51
+ it("should keep AIMessages with tool_calls even if content is empty", async () => {
52
+ const adapter = new GoogleGenerativeAIAdapter();
53
+
54
+ const messagesWithToolCalls = [
55
+ new HumanMessage("Execute a function"),
56
+ new AIMessage({
57
+ content: "", // Empty content but has tool calls
58
+ tool_calls: [
59
+ {
60
+ id: "call_123",
61
+ name: "test_function",
62
+ args: { param: "value" },
63
+ },
64
+ ],
65
+ }),
66
+ ];
67
+
68
+ const chainFnResult = await (adapter as any).options.chainFn({
69
+ messages: messagesWithToolCalls,
70
+ tools: [],
71
+ threadId: "test-thread",
72
+ });
73
+
74
+ const { filteredMessages } = chainFnResult;
75
+
76
+ // Should keep both messages - the AIMessage has tool_calls so it's valid
77
+ expect(filteredMessages).toHaveLength(2);
78
+ expect(filteredMessages[1]).toBeInstanceOf(AIMessage);
79
+ expect(filteredMessages[1].tool_calls).toHaveLength(1);
80
+ });
81
+
82
+ it("should keep all non-AIMessage types regardless of content", async () => {
83
+ const adapter = new GoogleGenerativeAIAdapter();
84
+
85
+ const messages = [
86
+ new HumanMessage(""), // Empty human message should be kept
87
+ new SystemMessage(""), // Empty system message should be kept
88
+ new AIMessage(""), // Empty AI message should be filtered
89
+ ];
90
+
91
+ const chainFnResult = await (adapter as any).options.chainFn({
92
+ messages,
93
+ tools: [],
94
+ threadId: "test-thread",
95
+ });
96
+
97
+ const { filteredMessages } = chainFnResult;
98
+
99
+ // Should keep HumanMessage and SystemMessage, filter out empty AIMessage
100
+ expect(filteredMessages).toHaveLength(2);
101
+ expect(filteredMessages[0]).toBeInstanceOf(HumanMessage);
102
+ expect(filteredMessages[1]).toBeInstanceOf(SystemMessage);
103
+ });
104
+ });
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Copilot Runtime adapter for Google Generative AI (e.g. Gemini).
3
+ *
4
+ * ## Example
5
+ *
6
+ * ```ts
7
+ * import { CopilotRuntime, GoogleGenerativeAIAdapter } from "@copilotkit/runtime";
8
+ * const { GoogleGenerativeAI } = require("@google/generative-ai");
9
+ *
10
+ * const genAI = new GoogleGenerativeAI(process.env["GOOGLE_API_KEY"]);
11
+ *
12
+ * const copilotKit = new CopilotRuntime();
13
+ *
14
+ * return new GoogleGenerativeAIAdapter({ model: "gemini-2.5-flash", apiVersion: "v1" });
15
+ * ```
16
+ */
17
+ import { LangChainAdapter } from "../langchain/langchain-adapter";
18
+
19
+ interface GoogleGenerativeAIAdapterOptions {
20
+ /**
21
+ * A custom Google Generative AI model to use.
22
+ */
23
+ model?: string;
24
+ /**
25
+ * The API version to use (e.g. "v1" or "v1beta"). Defaults to "v1".
26
+ */
27
+ apiVersion?: "v1" | "v1beta";
28
+ /**
29
+ * The API key to use.
30
+ */
31
+ apiKey?: string;
32
+ }
33
+
34
+ const DEFAULT_MODEL = "gemini-2.5-flash";
35
+ const DEFAULT_API_VERSION: GoogleGenerativeAIAdapterOptions["apiVersion"] = "v1";
36
+ let hasWarnedDefaultGoogleModel = false;
37
+
38
+ export class GoogleGenerativeAIAdapter extends LangChainAdapter {
39
+ public provider = "google";
40
+ public model: string = DEFAULT_MODEL;
41
+
42
+ constructor(options?: GoogleGenerativeAIAdapterOptions) {
43
+ if (!hasWarnedDefaultGoogleModel && !options?.model && !options?.apiVersion) {
44
+ console.warn(
45
+ `You are using the GoogleGenerativeAIAdapter without explicitly setting a model or apiVersion. ` +
46
+ `CopilotKit will default to apiVersion="v1" and model="${DEFAULT_MODEL}". ` +
47
+ `To silence this warning, pass model and apiVersion when constructing the adapter.`,
48
+ );
49
+ hasWarnedDefaultGoogleModel = true;
50
+ }
51
+
52
+ super({
53
+ chainFn: async ({ messages, tools, threadId }) => {
54
+ // Lazy require for optional peer dependencies
55
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
56
+ const { ChatGoogle } = require("@langchain/google-gauth");
57
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
58
+ const { AIMessage } = require("@langchain/core/messages");
59
+
60
+ // Filter out empty assistant messages to prevent Gemini validation errors
61
+ // Gemini specifically rejects conversations containing AIMessages with empty content
62
+ const filteredMessages = messages.filter((message) => {
63
+ // Keep all non-AI messages (HumanMessage, SystemMessage, ToolMessage, etc.)
64
+ if (!(message instanceof AIMessage)) {
65
+ return true;
66
+ }
67
+
68
+ // For AIMessages, only keep those with non-empty content
69
+ // Also keep AIMessages with tool_calls even if content is empty
70
+ const aiMsg = message as any;
71
+ return (
72
+ (aiMsg.content && String(aiMsg.content).trim().length > 0) ||
73
+ (aiMsg.tool_calls && aiMsg.tool_calls.length > 0)
74
+ );
75
+ });
76
+
77
+ this.model = options?.model ?? DEFAULT_MODEL;
78
+ const model = new ChatGoogle({
79
+ apiKey: options?.apiKey ?? process.env.GOOGLE_API_KEY,
80
+ modelName: this.model,
81
+ apiVersion: options?.apiVersion ?? DEFAULT_API_VERSION,
82
+ }).bindTools(tools);
83
+
84
+ return model.stream(filteredMessages, { metadata: { conversation_id: threadId } });
85
+ },
86
+ });
87
+ }
88
+ }