@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,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
|
+
}
|