@copilotkit/runtime 1.6.0-next.9 → 1.7.0-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dist/{chunk-5ZBUMQE2.mjs → chunk-2BN7NZNC.mjs} +2 -2
- package/dist/{chunk-L7OTGCAG.mjs → chunk-34Y5DNNJ.mjs} +104 -74
- package/dist/chunk-34Y5DNNJ.mjs.map +1 -0
- package/dist/{chunk-7BCFHZLY.mjs → chunk-PH24IU7T.mjs} +2 -2
- package/dist/{chunk-4HQ5OZXN.mjs → chunk-ZYFN76KV.mjs} +2 -2
- package/dist/index.js +103 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -4
- package/dist/lib/index.js +103 -73
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +4 -4
- package/dist/lib/integrations/index.js +6 -3
- package/dist/lib/integrations/index.js.map +1 -1
- package/dist/lib/integrations/index.mjs +4 -4
- package/dist/lib/integrations/nest/index.js +6 -3
- package/dist/lib/integrations/nest/index.js.map +1 -1
- package/dist/lib/integrations/nest/index.mjs +2 -2
- package/dist/lib/integrations/node-express/index.js +6 -3
- package/dist/lib/integrations/node-express/index.js.map +1 -1
- package/dist/lib/integrations/node-express/index.mjs +2 -2
- package/dist/lib/integrations/node-http/index.js +6 -3
- package/dist/lib/integrations/node-http/index.js.map +1 -1
- package/dist/lib/integrations/node-http/index.mjs +1 -1
- package/package.json +2 -2
- package/src/agents/langgraph/event-source.ts +11 -4
- package/src/graphql/resolvers/copilot.resolver.ts +4 -0
- package/src/lib/runtime/__tests__/remote-action-constructors.test.ts +45 -35
- package/src/lib/runtime/copilot-runtime.ts +23 -17
- package/src/lib/runtime/remote-action-constructors.ts +28 -68
- package/src/lib/runtime/remote-actions.ts +5 -5
- package/src/lib/streaming.ts +59 -0
- package/src/lib/telemetry-client.ts +3 -1
- package/src/service-adapters/events.ts +3 -3
- package/dist/chunk-L7OTGCAG.mjs.map +0 -1
- /package/dist/{chunk-5ZBUMQE2.mjs.map → chunk-2BN7NZNC.mjs.map} +0 -0
- /package/dist/{chunk-7BCFHZLY.mjs.map → chunk-PH24IU7T.mjs.map} +0 -0
- /package/dist/{chunk-4HQ5OZXN.mjs.map → chunk-ZYFN76KV.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
"version": "1.
|
|
12
|
+
"version": "1.7.0-next.0",
|
|
13
13
|
"sideEffects": false,
|
|
14
14
|
"main": "./dist/index.js",
|
|
15
15
|
"module": "./dist/index.mjs",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"rxjs": "^7.8.1",
|
|
60
60
|
"type-graphql": "2.0.0-rc.1",
|
|
61
61
|
"zod": "^3.23.3",
|
|
62
|
-
"@copilotkit/shared": "1.
|
|
62
|
+
"@copilotkit/shared": "1.7.0-next.0"
|
|
63
63
|
},
|
|
64
64
|
"keywords": [
|
|
65
65
|
"copilotkit",
|
|
@@ -20,6 +20,7 @@ interface LangGraphEventWithState {
|
|
|
20
20
|
lastToolCallId: string | null;
|
|
21
21
|
lastToolCallName: string | null;
|
|
22
22
|
currentContent: string | null;
|
|
23
|
+
processedToolCallIds: Set<string>;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export class RemoteLangGraphEventSource {
|
|
@@ -94,12 +95,12 @@ export class RemoteLangGraphEventSource {
|
|
|
94
95
|
acc.isToolCallStart = toolCallChunks.some((chunk: any) => chunk.name && chunk.id);
|
|
95
96
|
acc.isMessageStart = prevMessageId !== acc.lastMessageId && !acc.isToolCallStart;
|
|
96
97
|
|
|
98
|
+
let previousRoundHadToolCall = acc.isToolCall;
|
|
99
|
+
acc.isToolCall = toolCallCheck;
|
|
97
100
|
// Previous "acc.isToolCall" was set but now it won't pass the check, it means the tool call just ended.
|
|
98
|
-
if (
|
|
101
|
+
if (previousRoundHadToolCall && !toolCallCheck) {
|
|
99
102
|
isToolCallEnd = true;
|
|
100
103
|
}
|
|
101
|
-
|
|
102
|
-
acc.isToolCall = toolCallCheck;
|
|
103
104
|
acc.isToolCallEnd = isToolCallEnd;
|
|
104
105
|
acc.isMessageEnd = responseMetadata?.finish_reason === "stop";
|
|
105
106
|
({ name: acc.lastToolCallName, id: acc.lastToolCallId } = toolCallChunks.find(
|
|
@@ -121,6 +122,7 @@ export class RemoteLangGraphEventSource {
|
|
|
121
122
|
lastToolCallId: null,
|
|
122
123
|
lastToolCallName: null,
|
|
123
124
|
currentContent: null,
|
|
125
|
+
processedToolCallIds: new Set<string>(),
|
|
124
126
|
} as LangGraphEventWithState,
|
|
125
127
|
),
|
|
126
128
|
mergeMap((acc): RuntimeEvent[] => {
|
|
@@ -158,8 +160,12 @@ export class RemoteLangGraphEventSource {
|
|
|
158
160
|
// Tool call ended: emit ActionExecutionEnd
|
|
159
161
|
if (
|
|
160
162
|
acc.isToolCallEnd &&
|
|
161
|
-
this.shouldEmitToolCall(shouldEmitToolCalls, acc.lastToolCallName)
|
|
163
|
+
this.shouldEmitToolCall(shouldEmitToolCalls, acc.lastToolCallName) &&
|
|
164
|
+
acc.lastToolCallId &&
|
|
165
|
+
!acc.processedToolCallIds.has(acc.lastToolCallId)
|
|
162
166
|
) {
|
|
167
|
+
acc.processedToolCallIds.add(acc.lastToolCallId);
|
|
168
|
+
|
|
163
169
|
events.push({
|
|
164
170
|
type: RuntimeEventTypes.ActionExecutionEnd,
|
|
165
171
|
actionExecutionId: acc.lastToolCallId,
|
|
@@ -245,6 +251,7 @@ export class RemoteLangGraphEventSource {
|
|
|
245
251
|
}
|
|
246
252
|
// Message started: emit TextMessageStart
|
|
247
253
|
else if (acc.isMessageStart && shouldEmitMessages) {
|
|
254
|
+
acc.processedToolCallIds.clear();
|
|
248
255
|
events.push({
|
|
249
256
|
type: RuntimeEventTypes.TextMessageStart,
|
|
250
257
|
messageId: acc.lastMessageId,
|
|
@@ -196,6 +196,10 @@ export class CopilotResolver {
|
|
|
196
196
|
rejectOutputMessagesPromise = reject;
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
if (copilotCloudPublicApiKey) {
|
|
200
|
+
ctx.properties["copilotCloudPublicApiKey"] = copilotCloudPublicApiKey;
|
|
201
|
+
}
|
|
202
|
+
|
|
199
203
|
logger.debug("Processing");
|
|
200
204
|
const {
|
|
201
205
|
eventSource,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "reflect-metadata";
|
|
1
2
|
import { TextEncoder } from "util";
|
|
2
3
|
import { RemoteLangGraphEventSource } from "../../../agents/langgraph/event-source";
|
|
3
4
|
import telemetry from "../../telemetry-client";
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
createHeaders,
|
|
8
9
|
} from "../remote-action-constructors";
|
|
9
10
|
import { execute } from "../remote-lg-action";
|
|
11
|
+
import { ReplaySubject } from "rxjs";
|
|
10
12
|
|
|
11
13
|
// Mock external dependencies
|
|
12
14
|
jest.mock("../remote-lg-action", () => ({
|
|
@@ -49,7 +51,7 @@ beforeEach(() => {
|
|
|
49
51
|
|
|
50
52
|
describe("remote action constructors", () => {
|
|
51
53
|
describe("constructLGCRemoteAction", () => {
|
|
52
|
-
it("should create an agent with
|
|
54
|
+
it("should create an agent with remoteAgentHandler that processes events", async () => {
|
|
53
55
|
// Arrange: simulate execute returning a dummy ReadableStream
|
|
54
56
|
const dummyEncodedEvent = new TextEncoder().encode(JSON.stringify({ event: "test" }) + "\n");
|
|
55
57
|
const readerMock = {
|
|
@@ -72,7 +74,7 @@ describe("remote action constructors", () => {
|
|
|
72
74
|
processLangGraphEvents: processLangGraphEventsMock,
|
|
73
75
|
}));
|
|
74
76
|
|
|
75
|
-
// Act: build the action and call
|
|
77
|
+
// Act: build the action and call remoteAgentHandler
|
|
76
78
|
const actions = constructLGCRemoteAction({
|
|
77
79
|
endpoint,
|
|
78
80
|
graphqlContext,
|
|
@@ -84,7 +86,7 @@ describe("remote action constructors", () => {
|
|
|
84
86
|
const action = actions[0];
|
|
85
87
|
expect(action.name).toEqual(dummyAgent.name);
|
|
86
88
|
|
|
87
|
-
const result = await action.
|
|
89
|
+
const result = await action.remoteAgentHandler({
|
|
88
90
|
name: dummyAgent.name,
|
|
89
91
|
actionInputsWithoutAgents: [],
|
|
90
92
|
threadId: "thread1",
|
|
@@ -160,26 +162,46 @@ describe("remote action constructors", () => {
|
|
|
160
162
|
});
|
|
161
163
|
|
|
162
164
|
it("should create remote agent handler that processes events", async () => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
165
|
+
const json = {
|
|
166
|
+
agents: [
|
|
167
|
+
{
|
|
168
|
+
name: "agent2",
|
|
169
|
+
description: "agent desc",
|
|
170
|
+
type: "langgraph", // Add type to match RemoteAgentType.LangGraph
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
actions: [
|
|
174
|
+
{
|
|
175
|
+
name: "action1",
|
|
176
|
+
description: "action desc",
|
|
177
|
+
parameters: { param: "value" },
|
|
178
|
+
},
|
|
179
|
+
],
|
|
173
180
|
};
|
|
174
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
175
|
-
ok: true,
|
|
176
|
-
text: jest.fn().mockResolvedValue("ok"),
|
|
177
|
-
body: dummyStreamResponse,
|
|
178
|
-
});
|
|
179
181
|
|
|
180
|
-
const
|
|
182
|
+
const dummyEncodedAgentEvent = new TextEncoder().encode('{"type":"data","content":"test"}\n');
|
|
183
|
+
|
|
184
|
+
const mockResponse = new Response(
|
|
185
|
+
new ReadableStream({
|
|
186
|
+
start(controller) {
|
|
187
|
+
controller.enqueue(dummyEncodedAgentEvent);
|
|
188
|
+
controller.close();
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
{
|
|
192
|
+
status: 200,
|
|
193
|
+
statusText: "OK",
|
|
194
|
+
headers: new Headers({
|
|
195
|
+
"content-type": "application/json",
|
|
196
|
+
}),
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
global.fetch = jest.fn().mockResolvedValue(mockResponse);
|
|
201
|
+
|
|
202
|
+
const processLangGraphEventsMock = jest.fn().mockResolvedValue("agent events processed");
|
|
181
203
|
(RemoteLangGraphEventSource as jest.Mock).mockImplementation(() => ({
|
|
182
|
-
eventStream$:
|
|
204
|
+
eventStream$: new ReplaySubject(),
|
|
183
205
|
processLangGraphEvents: processLangGraphEventsMock,
|
|
184
206
|
}));
|
|
185
207
|
|
|
@@ -192,29 +214,17 @@ describe("remote action constructors", () => {
|
|
|
192
214
|
messages: [],
|
|
193
215
|
agentStates,
|
|
194
216
|
});
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const remoteAgentHandler = (actionsArray[1] as any).langGraphAgentHandler;
|
|
217
|
+
|
|
218
|
+
const remoteAgentHandler = (actionsArray[1] as any).remoteAgentHandler;
|
|
198
219
|
const result = await remoteAgentHandler({
|
|
199
220
|
name: "agent2",
|
|
200
221
|
actionInputsWithoutAgents: [],
|
|
201
222
|
threadId: "thread2",
|
|
202
223
|
nodeName: "node2",
|
|
203
|
-
additionalMessages: [],
|
|
204
|
-
metaEvents: [],
|
|
205
224
|
});
|
|
225
|
+
|
|
206
226
|
expect(processLangGraphEventsMock).toHaveBeenCalled();
|
|
207
227
|
expect(result).toBe("agent events processed");
|
|
208
|
-
|
|
209
|
-
// Check telemetry.capture for agent execution
|
|
210
|
-
expect(telemetry.capture).toHaveBeenCalledWith(
|
|
211
|
-
"oss.runtime.remote_action_executed",
|
|
212
|
-
expect.objectContaining({
|
|
213
|
-
agentExecution: true,
|
|
214
|
-
type: "self-hosted",
|
|
215
|
-
agentsAmount: 1,
|
|
216
|
-
}),
|
|
217
|
-
);
|
|
218
228
|
});
|
|
219
229
|
});
|
|
220
230
|
|
|
@@ -39,8 +39,8 @@ import { Message } from "../../graphql/types/converted";
|
|
|
39
39
|
import { ForwardedParametersInput } from "../../graphql/inputs/forwarded-parameters.input";
|
|
40
40
|
|
|
41
41
|
import {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
isRemoteAgentAction,
|
|
43
|
+
RemoteAgentAction,
|
|
44
44
|
EndpointType,
|
|
45
45
|
setupRemoteActions,
|
|
46
46
|
EndpointDefinition,
|
|
@@ -153,7 +153,7 @@ export interface CopilotRuntimeConstructorParams<T extends Parameter[] | [] = []
|
|
|
153
153
|
middleware?: Middleware;
|
|
154
154
|
|
|
155
155
|
/*
|
|
156
|
-
* A list of server side actions that can be executed.
|
|
156
|
+
* A list of server side actions that can be executed. Will be ignored when remoteActions are set
|
|
157
157
|
*/
|
|
158
158
|
actions?: ActionsConfiguration<T>;
|
|
159
159
|
|
|
@@ -190,7 +190,13 @@ export class CopilotRuntime<const T extends Parameter[] | [] = []> {
|
|
|
190
190
|
private delegateAgentProcessingToServiceAdapter: boolean;
|
|
191
191
|
|
|
192
192
|
constructor(params?: CopilotRuntimeConstructorParams<T>) {
|
|
193
|
-
|
|
193
|
+
// Do not register actions if endpoints are set
|
|
194
|
+
if (params?.actions && params?.remoteEndpoints) {
|
|
195
|
+
console.warn("Actions set in runtime instance will be ignored when remote endpoints are set");
|
|
196
|
+
this.actions = [];
|
|
197
|
+
} else {
|
|
198
|
+
this.actions = params?.actions || [];
|
|
199
|
+
}
|
|
194
200
|
|
|
195
201
|
for (const chain of params?.langserve || []) {
|
|
196
202
|
const remoteChain = new RemoteChain(chain);
|
|
@@ -299,7 +305,7 @@ please use an LLM adapter instead.`,
|
|
|
299
305
|
(action) =>
|
|
300
306
|
// TODO-AGENTS: do not exclude ALL server side actions
|
|
301
307
|
!serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
|
|
302
|
-
// !
|
|
308
|
+
// !isRemoteAgentAction(
|
|
303
309
|
// serverSideActions.find((serverSideAction) => serverSideAction.name == action.name),
|
|
304
310
|
// ),
|
|
305
311
|
),
|
|
@@ -316,7 +322,6 @@ please use an LLM adapter instead.`,
|
|
|
316
322
|
}
|
|
317
323
|
|
|
318
324
|
async discoverAgentsFromEndpoints(graphqlContext: GraphQLContext): Promise<AgentWithEndpoint[]> {
|
|
319
|
-
const headers = createHeaders(null, graphqlContext);
|
|
320
325
|
const agents = this.remoteEndpointDefinitions.reduce(
|
|
321
326
|
async (acc: Promise<Agent[]>, endpoint) => {
|
|
322
327
|
const agents = await acc;
|
|
@@ -349,12 +354,12 @@ please use an LLM adapter instead.`,
|
|
|
349
354
|
description: string;
|
|
350
355
|
}>;
|
|
351
356
|
}
|
|
352
|
-
|
|
353
|
-
const fetchUrl = `${
|
|
357
|
+
const cpkEndpoint = endpoint as CopilotKitEndpoint;
|
|
358
|
+
const fetchUrl = `${endpoint.url}/info`;
|
|
354
359
|
try {
|
|
355
360
|
const response = await fetch(fetchUrl, {
|
|
356
361
|
method: "POST",
|
|
357
|
-
headers,
|
|
362
|
+
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
|
|
358
363
|
body: JSON.stringify({ properties: graphqlContext.properties }),
|
|
359
364
|
});
|
|
360
365
|
if (!response.ok) {
|
|
@@ -438,11 +443,12 @@ please use an LLM adapter instead.`,
|
|
|
438
443
|
agentWithEndpoint.endpoint.type === EndpointType.CopilotKit ||
|
|
439
444
|
!("type" in agentWithEndpoint.endpoint)
|
|
440
445
|
) {
|
|
441
|
-
const
|
|
446
|
+
const cpkEndpoint = agentWithEndpoint.endpoint as CopilotKitEndpoint;
|
|
447
|
+
const fetchUrl = `${cpkEndpoint.url}/agents/state`;
|
|
442
448
|
try {
|
|
443
449
|
const response = await fetch(fetchUrl, {
|
|
444
450
|
method: "POST",
|
|
445
|
-
headers,
|
|
451
|
+
headers: createHeaders(cpkEndpoint.onBeforeRequest, graphqlContext),
|
|
446
452
|
body: JSON.stringify({
|
|
447
453
|
properties: graphqlContext.properties,
|
|
448
454
|
threadId,
|
|
@@ -499,8 +505,8 @@ please use an LLM adapter instead.`,
|
|
|
499
505
|
const messages = convertGqlInputToMessages(rawMessages);
|
|
500
506
|
|
|
501
507
|
const currentAgent = serverSideActions.find(
|
|
502
|
-
(action) => action.name === agentName &&
|
|
503
|
-
) as
|
|
508
|
+
(action) => action.name === agentName && isRemoteAgentAction(action),
|
|
509
|
+
) as RemoteAgentAction;
|
|
504
510
|
|
|
505
511
|
if (!currentAgent) {
|
|
506
512
|
throw new CopilotKitAgentDiscoveryError({ agentName });
|
|
@@ -513,9 +519,9 @@ please use an LLM adapter instead.`,
|
|
|
513
519
|
.filter(
|
|
514
520
|
(action) =>
|
|
515
521
|
// Case 1: Keep all regular (non-agent) actions
|
|
516
|
-
!
|
|
522
|
+
!isRemoteAgentAction(action) ||
|
|
517
523
|
// Case 2: For agent actions, keep all except self (prevent infinite loops)
|
|
518
|
-
(
|
|
524
|
+
(isRemoteAgentAction(action) && action.name !== agentName) /* prevent self-calls */,
|
|
519
525
|
)
|
|
520
526
|
.map((action) => ({
|
|
521
527
|
name: action.name,
|
|
@@ -536,7 +542,7 @@ please use an LLM adapter instead.`,
|
|
|
536
542
|
});
|
|
537
543
|
try {
|
|
538
544
|
const eventSource = new RuntimeEventSource();
|
|
539
|
-
const stream = await currentAgent.
|
|
545
|
+
const stream = await currentAgent.remoteAgentHandler({
|
|
540
546
|
name: agentName,
|
|
541
547
|
threadId,
|
|
542
548
|
nodeName,
|
|
@@ -651,7 +657,7 @@ export function langGraphPlatformEndpoint(
|
|
|
651
657
|
|
|
652
658
|
export function resolveEndpointType(endpoint: EndpointDefinition) {
|
|
653
659
|
if (!endpoint.type) {
|
|
654
|
-
if ("
|
|
660
|
+
if ("deploymentUrl" in endpoint && "agents" in endpoint) {
|
|
655
661
|
return EndpointType.LangGraphPlatform;
|
|
656
662
|
} else {
|
|
657
663
|
return EndpointType.CopilotKit;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
3
|
CopilotKitEndpoint,
|
|
4
|
-
|
|
4
|
+
RemoteAgentHandlerParams,
|
|
5
5
|
RemoteActionInfoResponse,
|
|
6
6
|
LangGraphPlatformEndpoint,
|
|
7
7
|
} from "./remote-actions";
|
|
@@ -10,13 +10,18 @@ import { Logger } from "pino";
|
|
|
10
10
|
import { Message } from "../../graphql/types/converted";
|
|
11
11
|
import { AgentStateInput } from "../../graphql/inputs/agent-state.input";
|
|
12
12
|
import { Observable, ReplaySubject } from "rxjs";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
RuntimeEvent,
|
|
15
|
+
RuntimeEventSource,
|
|
16
|
+
RuntimeEventSubject,
|
|
17
|
+
} from "../../service-adapters/events";
|
|
14
18
|
import telemetry from "../telemetry-client";
|
|
15
19
|
import { RemoteLangGraphEventSource } from "../../agents/langgraph/event-source";
|
|
16
20
|
import { Action } from "@copilotkit/shared";
|
|
17
21
|
import { LangGraphEvent } from "../../agents/langgraph/events";
|
|
18
22
|
import { execute } from "./remote-lg-action";
|
|
19
23
|
import { CopilotKitError, CopilotKitLowLevelError } from "@copilotkit/shared";
|
|
24
|
+
import { writeJsonLineResponseToEventStream } from "../streaming";
|
|
20
25
|
import { CopilotKitApiDiscoveryError, ResolvedCopilotKitError } from "@copilotkit/shared";
|
|
21
26
|
import { parseJson, tryMap } from "@copilotkit/shared";
|
|
22
27
|
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
@@ -39,14 +44,14 @@ export function constructLGCRemoteAction({
|
|
|
39
44
|
description: agent.description,
|
|
40
45
|
parameters: [],
|
|
41
46
|
handler: async (_args: any) => {},
|
|
42
|
-
|
|
47
|
+
remoteAgentHandler: async ({
|
|
43
48
|
name,
|
|
44
49
|
actionInputsWithoutAgents,
|
|
45
50
|
threadId,
|
|
46
51
|
nodeName,
|
|
47
52
|
additionalMessages = [],
|
|
48
53
|
metaEvents,
|
|
49
|
-
}:
|
|
54
|
+
}: RemoteAgentHandlerParams): Promise<Observable<RuntimeEvent>> => {
|
|
50
55
|
logger.debug({ actionName: agent.name }, "Executing LangGraph Platform agent");
|
|
51
56
|
|
|
52
57
|
telemetry.capture("oss.runtime.remote_action_executed", {
|
|
@@ -87,7 +92,7 @@ export function constructLGCRemoteAction({
|
|
|
87
92
|
});
|
|
88
93
|
|
|
89
94
|
const eventSource = new RemoteLangGraphEventSource();
|
|
90
|
-
|
|
95
|
+
writeJsonLineResponseToEventStream(response, eventSource.eventStream$);
|
|
91
96
|
return eventSource.processLangGraphEvents();
|
|
92
97
|
} catch (error) {
|
|
93
98
|
logger.error(
|
|
@@ -102,6 +107,11 @@ export function constructLGCRemoteAction({
|
|
|
102
107
|
return [...agents];
|
|
103
108
|
}
|
|
104
109
|
|
|
110
|
+
export enum RemoteAgentType {
|
|
111
|
+
LangGraph = "langgraph",
|
|
112
|
+
CrewAI = "crewai",
|
|
113
|
+
}
|
|
114
|
+
|
|
105
115
|
export function constructRemoteActions({
|
|
106
116
|
json,
|
|
107
117
|
url,
|
|
@@ -183,14 +193,14 @@ export function constructRemoteActions({
|
|
|
183
193
|
parameters: [],
|
|
184
194
|
handler: async (_args: any) => {},
|
|
185
195
|
|
|
186
|
-
|
|
196
|
+
remoteAgentHandler: async ({
|
|
187
197
|
name,
|
|
188
198
|
actionInputsWithoutAgents,
|
|
189
199
|
threadId,
|
|
190
200
|
nodeName,
|
|
191
201
|
additionalMessages = [],
|
|
192
202
|
metaEvents,
|
|
193
|
-
}:
|
|
203
|
+
}: RemoteAgentHandlerParams): Promise<Observable<RuntimeEvent>> => {
|
|
194
204
|
logger.debug({ actionName: agent.name }, "Executing remote agent");
|
|
195
205
|
|
|
196
206
|
const headers = createHeaders(onBeforeRequest, graphqlContext);
|
|
@@ -247,9 +257,17 @@ export function constructRemoteActions({
|
|
|
247
257
|
});
|
|
248
258
|
}
|
|
249
259
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
if (agent.type === RemoteAgentType.LangGraph) {
|
|
261
|
+
const eventSource = new RemoteLangGraphEventSource();
|
|
262
|
+
writeJsonLineResponseToEventStream(response.body!, eventSource.eventStream$);
|
|
263
|
+
return eventSource.processLangGraphEvents();
|
|
264
|
+
} else if (agent.type === RemoteAgentType.CrewAI) {
|
|
265
|
+
const eventStream$ = new RuntimeEventSubject();
|
|
266
|
+
writeJsonLineResponseToEventStream(response.body!, eventStream$);
|
|
267
|
+
return eventStream$;
|
|
268
|
+
} else {
|
|
269
|
+
throw new Error("Unsupported agent type");
|
|
270
|
+
}
|
|
253
271
|
} catch (error) {
|
|
254
272
|
if (error instanceof CopilotKitError) {
|
|
255
273
|
throw error;
|
|
@@ -263,64 +281,6 @@ export function constructRemoteActions({
|
|
|
263
281
|
return [...actions, ...agents];
|
|
264
282
|
}
|
|
265
283
|
|
|
266
|
-
async function streamResponse(
|
|
267
|
-
response: ReadableStream<Uint8Array>,
|
|
268
|
-
eventStream$: ReplaySubject<LangGraphEvent>,
|
|
269
|
-
) {
|
|
270
|
-
const reader = response.getReader();
|
|
271
|
-
const decoder = new TextDecoder();
|
|
272
|
-
let buffer = [];
|
|
273
|
-
|
|
274
|
-
function flushBuffer() {
|
|
275
|
-
const currentBuffer = buffer.join("");
|
|
276
|
-
if (currentBuffer.trim().length === 0) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
const parts = currentBuffer.split("\n");
|
|
280
|
-
if (parts.length === 0) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const lastPartIsComplete = currentBuffer.endsWith("\n");
|
|
285
|
-
|
|
286
|
-
// truncate buffer
|
|
287
|
-
buffer = [];
|
|
288
|
-
|
|
289
|
-
if (!lastPartIsComplete) {
|
|
290
|
-
// put back the last part
|
|
291
|
-
buffer.push(parts.pop());
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
parts
|
|
295
|
-
.map((part) => part.trim())
|
|
296
|
-
.filter((part) => part != "")
|
|
297
|
-
.forEach((part) => {
|
|
298
|
-
eventStream$.next(JSON.parse(part));
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
while (true) {
|
|
304
|
-
const { done, value } = await reader.read();
|
|
305
|
-
|
|
306
|
-
if (!done) {
|
|
307
|
-
buffer.push(decoder.decode(value, { stream: true }));
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
flushBuffer();
|
|
311
|
-
|
|
312
|
-
if (done) {
|
|
313
|
-
break;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
console.error("Error in stream", error);
|
|
318
|
-
eventStream$.error(error);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
eventStream$.complete();
|
|
322
|
-
}
|
|
323
|
-
|
|
324
284
|
export function createHeaders(
|
|
325
285
|
onBeforeRequest: CopilotKitEndpoint["onBeforeRequest"],
|
|
326
286
|
graphqlContext: GraphQLContext,
|
|
@@ -54,7 +54,7 @@ export type RemoteActionInfoResponse = {
|
|
|
54
54
|
agents: any[];
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
-
export type
|
|
57
|
+
export type RemoteAgentHandlerParams = {
|
|
58
58
|
name: string;
|
|
59
59
|
actionInputsWithoutAgents: ActionInput[];
|
|
60
60
|
threadId?: string;
|
|
@@ -63,15 +63,15 @@ export type LangGraphAgentHandlerParams = {
|
|
|
63
63
|
metaEvents?: MetaEventInput[];
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
export type
|
|
67
|
-
|
|
66
|
+
export type RemoteAgentAction = Action<any> & {
|
|
67
|
+
remoteAgentHandler: (params: RemoteAgentHandlerParams) => Promise<Observable<RuntimeEvent>>;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
export function
|
|
70
|
+
export function isRemoteAgentAction(action: Action<any>): action is RemoteAgentAction {
|
|
71
71
|
if (!action) {
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
74
|
-
return typeof (action as
|
|
74
|
+
return typeof (action as RemoteAgentAction).remoteAgentHandler === "function";
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
async function fetchRemoteInfo({
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ReplaySubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
export async function writeJsonLineResponseToEventStream<T>(
|
|
4
|
+
response: ReadableStream<Uint8Array>,
|
|
5
|
+
eventStream$: ReplaySubject<T>,
|
|
6
|
+
) {
|
|
7
|
+
const reader = response.getReader();
|
|
8
|
+
const decoder = new TextDecoder();
|
|
9
|
+
let buffer = [];
|
|
10
|
+
|
|
11
|
+
function flushBuffer() {
|
|
12
|
+
const currentBuffer = buffer.join("");
|
|
13
|
+
if (currentBuffer.trim().length === 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const parts = currentBuffer.split("\n");
|
|
17
|
+
if (parts.length === 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const lastPartIsComplete = currentBuffer.endsWith("\n");
|
|
22
|
+
|
|
23
|
+
// truncate buffer
|
|
24
|
+
buffer = [];
|
|
25
|
+
|
|
26
|
+
if (!lastPartIsComplete) {
|
|
27
|
+
// put back the last part
|
|
28
|
+
buffer.push(parts.pop());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
parts
|
|
32
|
+
.map((part) => part.trim())
|
|
33
|
+
.filter((part) => part != "")
|
|
34
|
+
.forEach((part) => {
|
|
35
|
+
eventStream$.next(JSON.parse(part));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
while (true) {
|
|
41
|
+
const { done, value } = await reader.read();
|
|
42
|
+
|
|
43
|
+
if (!done) {
|
|
44
|
+
buffer.push(decoder.decode(value, { stream: true }));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
flushBuffer();
|
|
48
|
+
|
|
49
|
+
if (done) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error("Error in stream", error);
|
|
55
|
+
eventStream$.error(error);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
eventStream$.complete();
|
|
59
|
+
}
|
|
@@ -31,7 +31,9 @@ export function getRuntimeInstanceTelemetryInfo(
|
|
|
31
31
|
info = {
|
|
32
32
|
...info,
|
|
33
33
|
agentsAmount: ep.agents.length,
|
|
34
|
-
hashedKey:
|
|
34
|
+
hashedKey: ep.langsmithApiKey
|
|
35
|
+
? createHash("sha256").update(ep.langsmithApiKey).digest("hex")
|
|
36
|
+
: null,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { streamLangChainResponse } from "./langchain/utils";
|
|
15
15
|
import { GuardrailsResult } from "../graphql/types/guardrails-result.type";
|
|
16
16
|
import telemetry from "../lib/telemetry-client";
|
|
17
|
-
import {
|
|
17
|
+
import { isRemoteAgentAction } from "../lib/runtime/remote-actions";
|
|
18
18
|
import { ActionInput } from "../graphql/inputs/action.input";
|
|
19
19
|
import { ActionExecutionMessage, ResultMessage, TextMessage } from "../graphql/types/converted";
|
|
20
20
|
import { plainToInstance } from "class-transformer";
|
|
@@ -372,7 +372,7 @@ async function executeAction(
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
// handle LangGraph agents
|
|
375
|
-
if (
|
|
375
|
+
if (isRemoteAgentAction(action)) {
|
|
376
376
|
const result = `${action.name} agent started`;
|
|
377
377
|
|
|
378
378
|
const agentExecution = plainToInstance(ActionExecutionMessage, {
|
|
@@ -397,7 +397,7 @@ async function executeAction(
|
|
|
397
397
|
result,
|
|
398
398
|
});
|
|
399
399
|
|
|
400
|
-
const stream = await action.
|
|
400
|
+
const stream = await action.remoteAgentHandler({
|
|
401
401
|
name: action.name,
|
|
402
402
|
threadId,
|
|
403
403
|
actionInputsWithoutAgents,
|