@copilotkit/runtime 1.10.7-next.0 → 1.50.0-beta.1

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 (134) hide show
  1. package/CHANGELOG.md +0 -6
  2. package/dist/index.d.ts +1655 -27
  3. package/dist/index.js +2172 -5049
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +5441 -99
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/v2/index.d.ts +1 -0
  8. package/dist/v2/index.js +15 -0
  9. package/dist/v2/index.js.map +1 -0
  10. package/dist/v2/index.mjs +4 -0
  11. package/dist/v2/index.mjs.map +1 -0
  12. package/package.json +17 -5
  13. package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
  14. package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
  15. package/src/graphql/message-conversion/gql-to-agui.test.ts +1578 -0
  16. package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
  17. package/src/graphql/message-conversion/index.ts +2 -0
  18. package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
  19. package/src/graphql/resolvers/copilot.resolver.ts +3 -48
  20. package/src/graphql/resolvers/state.resolver.ts +3 -2
  21. package/src/graphql/types/converted/index.ts +32 -6
  22. package/src/graphql/types/enums.ts +2 -2
  23. package/src/graphql/types/message-status.type.ts +3 -1
  24. package/src/lib/index.ts +1 -1
  25. package/src/lib/integrations/nextjs/app-router.ts +10 -11
  26. package/src/lib/integrations/nextjs/pages-router.ts +4 -11
  27. package/src/lib/integrations/node-http/index.ts +64 -5
  28. package/src/lib/integrations/shared.ts +1 -1
  29. package/src/lib/observability.ts +87 -0
  30. package/src/lib/runtime/{langgraph/langgraph-agent.ts → agent-integrations/langgraph.agent.ts} +5 -0
  31. package/src/lib/runtime/copilot-runtime.ts +346 -1333
  32. package/src/lib/runtime/types.ts +49 -0
  33. package/src/lib/runtime/utils.ts +87 -0
  34. package/src/lib/telemetry-client.ts +6 -5
  35. package/src/service-adapters/anthropic/anthropic-adapter.ts +5 -1
  36. package/src/service-adapters/bedrock/bedrock-adapter.ts +6 -1
  37. package/src/service-adapters/empty/empty-adapter.ts +3 -0
  38. package/src/service-adapters/events.ts +0 -254
  39. package/src/service-adapters/experimental/ollama/ollama-adapter.ts +5 -1
  40. package/src/service-adapters/google/google-genai-adapter.ts +7 -1
  41. package/src/service-adapters/groq/groq-adapter.ts +5 -1
  42. package/src/service-adapters/langchain/langchain-adapter.ts +3 -0
  43. package/src/service-adapters/openai/openai-adapter.ts +5 -1
  44. package/src/service-adapters/openai/openai-assistant-adapter.ts +4 -0
  45. package/src/service-adapters/service-adapter.ts +3 -0
  46. package/src/service-adapters/unify/unify-adapter.ts +6 -1
  47. package/src/v2/index.ts +2 -0
  48. package/tsup.config.ts +2 -1
  49. package/dist/chunk-27JKTS6P.mjs +0 -1704
  50. package/dist/chunk-27JKTS6P.mjs.map +0 -1
  51. package/dist/chunk-2OZAGFV3.mjs +0 -43
  52. package/dist/chunk-2OZAGFV3.mjs.map +0 -1
  53. package/dist/chunk-5BW5IBTZ.mjs +0 -80
  54. package/dist/chunk-5BW5IBTZ.mjs.map +0 -1
  55. package/dist/chunk-AMUJQ6IR.mjs +0 -50
  56. package/dist/chunk-AMUJQ6IR.mjs.map +0 -1
  57. package/dist/chunk-BMIYSM5W.mjs +0 -25
  58. package/dist/chunk-BMIYSM5W.mjs.map +0 -1
  59. package/dist/chunk-FDTCG47E.mjs +0 -25
  60. package/dist/chunk-FDTCG47E.mjs.map +0 -1
  61. package/dist/chunk-FHD4JECV.mjs +0 -33
  62. package/dist/chunk-FHD4JECV.mjs.map +0 -1
  63. package/dist/chunk-LRCKLBMO.mjs +0 -6020
  64. package/dist/chunk-LRCKLBMO.mjs.map +0 -1
  65. package/dist/chunk-R7RMYEPZ.mjs +0 -175
  66. package/dist/chunk-R7RMYEPZ.mjs.map +0 -1
  67. package/dist/chunk-SHBDMA63.mjs +0 -141
  68. package/dist/chunk-SHBDMA63.mjs.map +0 -1
  69. package/dist/chunk-XWBDEXDA.mjs +0 -153
  70. package/dist/chunk-XWBDEXDA.mjs.map +0 -1
  71. package/dist/graphql/types/base/index.d.ts +0 -6
  72. package/dist/graphql/types/base/index.js +0 -63
  73. package/dist/graphql/types/base/index.js.map +0 -1
  74. package/dist/graphql/types/base/index.mjs +0 -8
  75. package/dist/graphql/types/base/index.mjs.map +0 -1
  76. package/dist/graphql/types/converted/index.d.ts +0 -2
  77. package/dist/graphql/types/converted/index.js +0 -200
  78. package/dist/graphql/types/converted/index.js.map +0 -1
  79. package/dist/graphql/types/converted/index.mjs +0 -19
  80. package/dist/graphql/types/converted/index.mjs.map +0 -1
  81. package/dist/groq-adapter-c8aec5c5.d.ts +0 -321
  82. package/dist/index-96b330da.d.ts +0 -119
  83. package/dist/langserve-0c6100e3.d.ts +0 -257
  84. package/dist/lib/cloud/index.d.ts +0 -6
  85. package/dist/lib/cloud/index.js +0 -18
  86. package/dist/lib/cloud/index.js.map +0 -1
  87. package/dist/lib/cloud/index.mjs +0 -1
  88. package/dist/lib/cloud/index.mjs.map +0 -1
  89. package/dist/lib/index.d.ts +0 -212
  90. package/dist/lib/index.js +0 -7843
  91. package/dist/lib/index.js.map +0 -1
  92. package/dist/lib/index.mjs +0 -76
  93. package/dist/lib/index.mjs.map +0 -1
  94. package/dist/lib/integrations/index.d.ts +0 -34
  95. package/dist/lib/integrations/index.js +0 -3052
  96. package/dist/lib/integrations/index.js.map +0 -1
  97. package/dist/lib/integrations/index.mjs +0 -37
  98. package/dist/lib/integrations/index.mjs.map +0 -1
  99. package/dist/lib/integrations/nest/index.d.ts +0 -15
  100. package/dist/lib/integrations/nest/index.js +0 -2959
  101. package/dist/lib/integrations/nest/index.js.map +0 -1
  102. package/dist/lib/integrations/nest/index.mjs +0 -14
  103. package/dist/lib/integrations/nest/index.mjs.map +0 -1
  104. package/dist/lib/integrations/node-express/index.d.ts +0 -15
  105. package/dist/lib/integrations/node-express/index.js +0 -2959
  106. package/dist/lib/integrations/node-express/index.js.map +0 -1
  107. package/dist/lib/integrations/node-express/index.mjs +0 -14
  108. package/dist/lib/integrations/node-express/index.mjs.map +0 -1
  109. package/dist/lib/integrations/node-http/index.d.ts +0 -15
  110. package/dist/lib/integrations/node-http/index.js +0 -2945
  111. package/dist/lib/integrations/node-http/index.js.map +0 -1
  112. package/dist/lib/integrations/node-http/index.mjs +0 -13
  113. package/dist/lib/integrations/node-http/index.mjs.map +0 -1
  114. package/dist/service-adapters/index.d.ts +0 -162
  115. package/dist/service-adapters/index.js +0 -1787
  116. package/dist/service-adapters/index.js.map +0 -1
  117. package/dist/service-adapters/index.mjs +0 -34
  118. package/dist/service-adapters/index.mjs.map +0 -1
  119. package/dist/service-adapters/shared/index.d.ts +0 -9
  120. package/dist/service-adapters/shared/index.js +0 -72
  121. package/dist/service-adapters/shared/index.js.map +0 -1
  122. package/dist/service-adapters/shared/index.mjs +0 -8
  123. package/dist/service-adapters/shared/index.mjs.map +0 -1
  124. package/dist/shared-0a7346ce.d.ts +0 -466
  125. package/dist/utils/index.d.ts +0 -65
  126. package/dist/utils/index.js +0 -175
  127. package/dist/utils/index.js.map +0 -1
  128. package/dist/utils/index.mjs +0 -12
  129. package/dist/utils/index.mjs.map +0 -1
  130. package/src/lib/runtime/__tests__/remote-action-constructors.test.ts +0 -246
  131. package/src/lib/runtime/agui-action.ts +0 -180
  132. package/src/lib/runtime/remote-action-constructors.ts +0 -331
  133. package/src/lib/runtime/remote-actions.ts +0 -217
  134. package/src/lib/runtime/remote-lg-action.ts +0 -1006
@@ -1,1006 +0,0 @@
1
- import {
2
- Client as LangGraphClient,
3
- EventsStreamEvent,
4
- GraphSchema,
5
- Interrupt,
6
- StreamMode,
7
- ThreadState,
8
- } from "@langchain/langgraph-sdk";
9
- import { createHash } from "node:crypto";
10
- import { isValidUUID, randomUUID } from "@copilotkit/shared";
11
- import { parse as parsePartialJson } from "partial-json";
12
- import { Logger } from "pino";
13
- import { ActionInput } from "../../graphql/inputs/action.input";
14
- import { LangGraphPlatformAgent, LangGraphPlatformEndpoint } from "./remote-actions";
15
- import { CopilotRequestContextProperties } from "../integrations";
16
- import { ActionExecutionMessage, Message, MessageType } from "../../graphql/types/converted";
17
- import { MessageRole } from "../../graphql/types/enums";
18
- import { CustomEventNames, LangGraphEventTypes } from "../../agents/langgraph/events";
19
- import telemetry from "../telemetry-client";
20
- import { MetaEventInput } from "../../graphql/inputs/meta-event.input";
21
- import { MetaEventName } from "../../graphql/types/meta-events.type";
22
- import {
23
- parseJson,
24
- CopilotKitMisuseError,
25
- CopilotKitLowLevelError,
26
- CopilotKitError,
27
- } from "@copilotkit/shared";
28
- import { RemoveMessage } from "@langchain/core/messages";
29
- import { RETRY_CONFIG, isRetryableError, sleep, calculateDelay } from "./retry-utils";
30
- import { generateHelpfulErrorMessage } from "../streaming";
31
-
32
- // Utility to determine if an error is a user configuration issue vs system error
33
- export function isUserConfigurationError(error: any): boolean {
34
- return (
35
- (error instanceof CopilotKitError || error instanceof CopilotKitLowLevelError) &&
36
- (error.code === "NETWORK_ERROR" ||
37
- error.code === "AUTHENTICATION_ERROR" ||
38
- error.statusCode === 401 ||
39
- error.statusCode === 403 ||
40
- error.message?.toLowerCase().includes("authentication") ||
41
- error.message?.toLowerCase().includes("api key"))
42
- );
43
- }
44
-
45
- type State = Record<string, any>;
46
-
47
- type ExecutionAction = Pick<ActionInput, "name" | "description"> & { parameters: string };
48
-
49
- interface ExecutionArgs extends Omit<LangGraphPlatformEndpoint, "agents"> {
50
- agent: LangGraphPlatformAgent;
51
- threadId: string;
52
- nodeName: string;
53
- messages: Message[];
54
- state: State;
55
- config?: {
56
- configurable?: Record<string, any>;
57
- [key: string]: any;
58
- };
59
- properties: CopilotRequestContextProperties;
60
- actions: ExecutionAction[];
61
- logger: Logger;
62
- metaEvents?: MetaEventInput[];
63
- }
64
-
65
- // The following types are our own definition to the messages accepted by LangGraph Platform, enhanced with some of our extra data.
66
- interface ToolCall {
67
- id: string;
68
- name: string;
69
- args: Record<string, unknown>;
70
- }
71
-
72
- type BaseLangGraphPlatformMessage = Omit<
73
- Message,
74
- | "isResultMessage"
75
- | "isTextMessage"
76
- | "isImageMessage"
77
- | "isActionExecutionMessage"
78
- | "isAgentStateMessage"
79
- | "type"
80
- | "createdAt"
81
- > & {
82
- content: string;
83
- role: MessageRole;
84
- additional_kwargs?: Record<string, unknown>;
85
- type: MessageType;
86
- };
87
-
88
- interface LangGraphPlatformResultMessage extends BaseLangGraphPlatformMessage {
89
- tool_call_id: string;
90
- name: string;
91
- }
92
-
93
- interface LangGraphPlatformActionExecutionMessage extends BaseLangGraphPlatformMessage {
94
- tool_calls: ToolCall[];
95
- }
96
-
97
- type LangGraphPlatformMessage =
98
- | LangGraphPlatformActionExecutionMessage
99
- | LangGraphPlatformResultMessage
100
- | BaseLangGraphPlatformMessage;
101
-
102
- type SchemaKeys = {
103
- input: string[] | null;
104
- output: string[] | null;
105
- config: string[] | null;
106
- } | null;
107
-
108
- export async function execute(args: ExecutionArgs): Promise<ReadableStream<Uint8Array>> {
109
- return new ReadableStream({
110
- async start(controller) {
111
- let lastError: any;
112
-
113
- // Retry logic for transient connection errors
114
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
115
- try {
116
- await streamEvents(controller, args);
117
- controller.close();
118
- return; // Success - exit retry loop
119
- } catch (err) {
120
- lastError = err;
121
-
122
- // Check if this is a retryable error
123
- if (isRetryableError(err) && attempt < RETRY_CONFIG.maxRetries) {
124
- const delay = calculateDelay(attempt);
125
- console.warn(
126
- `LangGraph connection attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1} failed. ` +
127
- `Retrying in ${delay}ms. Error: ${err?.message || String(err)}`,
128
- );
129
- await sleep(delay);
130
- continue; // Retry
131
- }
132
-
133
- // Not retryable or max retries exceeded - handle error
134
- break;
135
- }
136
- }
137
-
138
- // Handle the final error after retries exhausted
139
- const cause = lastError?.cause;
140
- const errorCode = cause?.code || lastError?.code;
141
-
142
- if (errorCode === "ECONNREFUSED") {
143
- throw new CopilotKitMisuseError({
144
- message: `
145
- The LangGraph client could not connect to the graph after ${RETRY_CONFIG.maxRetries + 1} attempts. Please further check previous logs, which includes further details.
146
-
147
- See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
148
- });
149
- } else {
150
- // Preserve already structured CopilotKit errors with semantic information
151
- if (
152
- lastError instanceof CopilotKitError ||
153
- lastError instanceof CopilotKitLowLevelError ||
154
- (lastError instanceof Error && lastError.name && lastError.name.includes("CopilotKit"))
155
- ) {
156
- throw lastError; // Re-throw to preserve semantic information and visibility settings
157
- }
158
-
159
- throw new CopilotKitMisuseError({
160
- message: `
161
- The LangGraph client threw unhandled error ${lastError}.
162
-
163
- See more: https://docs.copilotkit.ai/troubleshooting/common-issues`,
164
- });
165
- }
166
- },
167
- });
168
- }
169
-
170
- async function streamEvents(controller: ReadableStreamDefaultController, args: ExecutionArgs) {
171
- const {
172
- deploymentUrl,
173
- langsmithApiKey,
174
- threadId: argsInitialThreadId,
175
- agent,
176
- nodeName: initialNodeName,
177
- state: initialState,
178
- config: explicitConfig,
179
- messages,
180
- actions,
181
- logger,
182
- properties,
183
- metaEvents,
184
- } = args;
185
-
186
- let nodeName = initialNodeName;
187
- let state = initialState;
188
- const { name, assistantId: initialAssistantId } = agent;
189
-
190
- const propertyHeaders = properties.authorization
191
- ? { authorization: `Bearer ${properties.authorization}` }
192
- : null;
193
-
194
- const client = new LangGraphClient({
195
- apiUrl: deploymentUrl,
196
- apiKey: langsmithApiKey,
197
- defaultHeaders: { ...propertyHeaders },
198
- });
199
-
200
- let threadId = argsInitialThreadId ?? randomUUID();
201
- if (argsInitialThreadId && argsInitialThreadId.startsWith("ck-")) {
202
- threadId = argsInitialThreadId.substring(3);
203
- }
204
-
205
- if (!isValidUUID(threadId)) {
206
- console.warn(
207
- `Cannot use the threadId ${threadId} with LangGraph Platform. Must be a valid UUID.`,
208
- );
209
- }
210
-
211
- let wasInitiatedWithExistingThread = true;
212
- try {
213
- await client.threads.get(threadId);
214
- } catch (error) {
215
- wasInitiatedWithExistingThread = false;
216
- await client.threads.create({ threadId });
217
- }
218
-
219
- let agentState = { values: {} } as ThreadState;
220
- if (wasInitiatedWithExistingThread) {
221
- agentState = await client.threads.getState(threadId);
222
- }
223
-
224
- const agentStateValues = agentState.values as State;
225
- state.messages = agentStateValues.messages;
226
- const mode =
227
- threadId && nodeName != "__end__" && nodeName != undefined && nodeName != null
228
- ? "continue"
229
- : "start";
230
- let formattedMessages = [];
231
- try {
232
- formattedMessages = copilotkitMessagesToLangChain(messages);
233
- } catch (e) {
234
- logger.error(e, `Error event thrown: ${e.message}`);
235
- }
236
- state = langGraphDefaultMergeState(state, formattedMessages, actions, name);
237
-
238
- const streamInput = mode === "start" ? state : null;
239
-
240
- const payload = {
241
- input: streamInput,
242
- streamMode: ["events", "values", "updates"] satisfies StreamMode[],
243
- command: undefined,
244
- };
245
-
246
- const lgInterruptMetaEvent = metaEvents?.find(
247
- (ev) => ev.name === MetaEventName.LangGraphInterruptEvent,
248
- );
249
-
250
- if (lgInterruptMetaEvent?.response) {
251
- let response = lgInterruptMetaEvent.response;
252
- payload.command = { resume: parseJson(response, response) };
253
- }
254
-
255
- const interrupts = (agentState.tasks?.[0]?.interrupts ?? []) as Interrupt[];
256
- if (mode === "continue" && !interrupts.length) {
257
- await client.threads.updateState(threadId, { values: state, asNode: nodeName });
258
- }
259
-
260
- let streamInfo: {
261
- provider?: string;
262
- langGraphHost?: string;
263
- langGraphVersion?: string;
264
- hashedLgcKey?: string | null;
265
- } = {
266
- hashedLgcKey: langsmithApiKey
267
- ? createHash("sha256").update(langsmithApiKey).digest("hex")
268
- : null,
269
- };
270
-
271
- const assistants = await client.assistants.search();
272
- const retrievedAssistant = assistants.find(
273
- (a) => a.name === name || a.assistant_id === initialAssistantId,
274
- );
275
- if (!retrievedAssistant) {
276
- telemetry.capture("oss.runtime.agent_execution_stream_errored", {
277
- ...streamInfo,
278
- error: `Found no assistants for given information, while ${assistants.length} assistants exists`,
279
- });
280
- console.error(`
281
- No agent found for the agent name specified in CopilotKit provider
282
- Please check your available agents or provide an agent ID in the LangGraph Platform endpoint definition.\n
283
-
284
- These are the available agents: [${assistants.map((a) => `${a.name} (ID: ${a.assistant_id})`).join(", ")}]
285
- `);
286
- throw new Error("No agent id found");
287
- }
288
- const assistantId = retrievedAssistant.assistant_id;
289
-
290
- const graphInfo = await client.assistants.getGraph(assistantId);
291
- const graphSchema = await client.assistants.getSchemas(assistantId);
292
- const schemaKeys = getSchemaKeys(graphSchema);
293
-
294
- if (explicitConfig) {
295
- let filteredConfigurable = retrievedAssistant.config.configurable;
296
- if (explicitConfig.configurable) {
297
- filteredConfigurable = schemaKeys?.config
298
- ? filterObjectBySchemaKeys(explicitConfig?.configurable, schemaKeys?.config)
299
- : explicitConfig?.configurable;
300
- }
301
-
302
- const newConfig = {
303
- ...retrievedAssistant.config,
304
- ...explicitConfig,
305
- configurable: filteredConfigurable,
306
- };
307
-
308
- // LG does not return recursion limit if it's the default, therefore we check: if no recursion limit is currently set, and the user asked for 25, there is no change.
309
- const isRecursionLimitSetToDefault =
310
- retrievedAssistant.config.recursion_limit == null && explicitConfig.recursion_limit === 25;
311
- // Deep compare configs to avoid unnecessary update calls
312
- const configsAreDifferent =
313
- JSON.stringify(newConfig) !== JSON.stringify(retrievedAssistant.config);
314
-
315
- // Check if the only difference is the recursion_limit being set to default
316
- const isOnlyRecursionLimitDifferent =
317
- isRecursionLimitSetToDefault &&
318
- JSON.stringify({ ...newConfig, recursion_limit: null }) ===
319
- JSON.stringify({ ...retrievedAssistant.config, recursion_limit: null });
320
-
321
- // If configs are different, we further check: Is the only diff a request to set the recursion limit to its already default?
322
- if (configsAreDifferent && !isOnlyRecursionLimitDifferent) {
323
- await client.assistants.update(assistantId, {
324
- config: newConfig,
325
- });
326
- }
327
- }
328
-
329
- // Do not input keys that are not part of the input schema
330
- if (payload.input && schemaKeys?.input) {
331
- payload.input = filterObjectBySchemaKeys(payload.input, schemaKeys.input);
332
- }
333
-
334
- let streamingStateExtractor = new StreamingStateExtractor([]);
335
- let prevNodeName = null;
336
- let emitIntermediateStateUntilEnd = null;
337
- let shouldExit = false;
338
- let externalRunId = null;
339
-
340
- const emit = (message: string) => controller.enqueue(new TextEncoder().encode(message));
341
-
342
- // If there are still outstanding unresolved interrupts, we must force resolution of them before moving forward
343
- if (interrupts?.length && !payload.command?.resume) {
344
- // If the interrupt is "by message" we assume the upcoming user message is a resoluton for the interrupt
345
- if (!lgInterruptMetaEvent) {
346
- // state.messages includes only messages that were not processed by the agent, which are the interrupt messages
347
- payload.command = { resume: state.messages };
348
- } else {
349
- interrupts.forEach((interrupt) => {
350
- emitInterrupt(interrupt.value, emit);
351
- });
352
- return Promise.resolve();
353
- }
354
- }
355
-
356
- const streamResponse = client.runs.stream(threadId, assistantId, payload);
357
-
358
- let latestStateValues = {};
359
- let updatedState = state;
360
- // If a manual emittance happens, it is the ultimate source of truth of state, unless a node has exited.
361
- // Therefore, this value should either hold null, or the only edition of state that should be used.
362
- let manuallyEmittedState = null;
363
-
364
- try {
365
- telemetry.capture("oss.runtime.agent_execution_stream_started", {
366
- hashedLgcKey: streamInfo.hashedLgcKey,
367
- });
368
- for await (let streamResponseChunk of streamResponse) {
369
- if (!["events", "values", "error", "updates"].includes(streamResponseChunk.event)) continue;
370
-
371
- if (streamResponseChunk.event === "error") {
372
- const errorData = streamResponseChunk.data;
373
-
374
- // Check if this is a structured error from our Python agent
375
- if (errorData && typeof errorData === "object" && "error_details" in errorData) {
376
- const errorDetails = (errorData as any).error_details;
377
-
378
- // Create a structured error with preserved semantic information
379
- const preservedError = new CopilotKitLowLevelError({
380
- error: new Error(errorDetails.message),
381
- url: "langgraph platform agent",
382
- message: `${errorDetails.type}: ${errorDetails.message}`,
383
- });
384
-
385
- // Add additional error context
386
- if (errorDetails.status_code) {
387
- (preservedError as any).statusCode = errorDetails.status_code;
388
- }
389
- if (errorDetails.response_data) {
390
- (preservedError as any).responseData = errorDetails.response_data;
391
- }
392
- (preservedError as any).agentName = errorDetails.agent_name;
393
- (preservedError as any).originalErrorType = errorDetails.type;
394
-
395
- throw preservedError;
396
- }
397
-
398
- // Fallback for generic error messages
399
- const helpfulMessage = generateHelpfulErrorMessage(
400
- new Error(errorData.message),
401
- "LangGraph Platform agent",
402
- );
403
-
404
- throw new CopilotKitLowLevelError({
405
- error: new Error(errorData.message),
406
- url: "langgraph platform agent",
407
- message: helpfulMessage,
408
- });
409
- }
410
-
411
- // Force event type, as data is not properly defined on the LG side.
412
- type EventsChunkData = {
413
- __interrupt__?: any;
414
- metadata: Record<string, any>;
415
- event: string;
416
- data: any;
417
- [key: string]: unknown;
418
- };
419
- const chunk = streamResponseChunk as EventsStreamEvent & { data: EventsChunkData };
420
-
421
- const interruptEvents = chunk.data.__interrupt__;
422
- if (interruptEvents?.length) {
423
- const interruptValue = interruptEvents?.[0].value;
424
- emitInterrupt(interruptValue, emit);
425
- continue;
426
- }
427
- if (streamResponseChunk.event === "updates") continue;
428
-
429
- if (streamResponseChunk.event === "values") {
430
- latestStateValues = chunk.data;
431
- continue;
432
- }
433
-
434
- const chunkData = chunk.data;
435
- const currentNodeName = chunkData.metadata.langgraph_node;
436
- const eventType = chunkData.event;
437
- const runId = chunkData.metadata.run_id;
438
- externalRunId = runId;
439
- const metadata = chunkData.metadata;
440
- if (chunkData.data?.output?.model != null && chunkData.data?.output?.model != "") {
441
- streamInfo.provider = chunkData.data?.output?.model;
442
- }
443
- if (metadata.langgraph_host != null && metadata.langgraph_host != "") {
444
- streamInfo.langGraphHost = metadata.langgraph_host;
445
- }
446
- if (metadata.langgraph_version != null && metadata.langgraph_version != "") {
447
- streamInfo.langGraphVersion = metadata.langgraph_version;
448
- }
449
-
450
- shouldExit =
451
- shouldExit ||
452
- (eventType === LangGraphEventTypes.OnCustomEvent &&
453
- chunkData.name === CustomEventNames.CopilotKitExit);
454
-
455
- const emitIntermediateState = metadata["copilotkit:emit-intermediate-state"];
456
- const manuallyEmitIntermediateState =
457
- eventType === LangGraphEventTypes.OnCustomEvent &&
458
- chunkData.name === CustomEventNames.CopilotKitManuallyEmitIntermediateState;
459
-
460
- const exitingNode =
461
- nodeName === currentNodeName && eventType === LangGraphEventTypes.OnChainEnd;
462
-
463
- // See manuallyEmittedState for explanation
464
- if (exitingNode) {
465
- manuallyEmittedState = null;
466
- }
467
-
468
- // we only want to update the node name under certain conditions
469
- // since we don't need any internal node names to be sent to the frontend
470
- if (graphInfo["nodes"].some((node) => node.id === currentNodeName)) {
471
- nodeName = currentNodeName;
472
- }
473
-
474
- updatedState = manuallyEmittedState ?? latestStateValues;
475
-
476
- if (!nodeName) {
477
- continue;
478
- }
479
-
480
- if (manuallyEmitIntermediateState) {
481
- // See manuallyEmittedState for explanation
482
- manuallyEmittedState = chunkData.data;
483
- emit(
484
- getStateSyncEvent({
485
- threadId,
486
- runId,
487
- agentName: agent.name,
488
- nodeName,
489
- state: manuallyEmittedState,
490
- running: true,
491
- active: true,
492
- schemaKeys,
493
- }),
494
- );
495
- continue;
496
- }
497
-
498
- if (emitIntermediateState && emitIntermediateStateUntilEnd == null) {
499
- emitIntermediateStateUntilEnd = nodeName;
500
- }
501
-
502
- if (emitIntermediateState && eventType === LangGraphEventTypes.OnChatModelStart) {
503
- // reset the streaming state extractor
504
- streamingStateExtractor = new StreamingStateExtractor(emitIntermediateState);
505
- }
506
-
507
- if (emitIntermediateState && eventType === LangGraphEventTypes.OnChatModelStream) {
508
- streamingStateExtractor.bufferToolCalls(chunkData);
509
- }
510
-
511
- if (emitIntermediateStateUntilEnd !== null) {
512
- updatedState = {
513
- ...updatedState,
514
- ...streamingStateExtractor.extractState(),
515
- };
516
- }
517
-
518
- if (
519
- !emitIntermediateState &&
520
- currentNodeName === emitIntermediateStateUntilEnd &&
521
- eventType === LangGraphEventTypes.OnChainEnd
522
- ) {
523
- // stop emitting function call state
524
- emitIntermediateStateUntilEnd = null;
525
- }
526
-
527
- if (
528
- JSON.stringify(updatedState) !== JSON.stringify(state) ||
529
- prevNodeName != nodeName ||
530
- exitingNode
531
- ) {
532
- state = updatedState;
533
- prevNodeName = nodeName;
534
- emit(
535
- getStateSyncEvent({
536
- threadId,
537
- runId,
538
- agentName: agent.name,
539
- nodeName,
540
- state,
541
- running: true,
542
- active: !exitingNode,
543
- schemaKeys,
544
- }),
545
- );
546
- }
547
-
548
- emit(JSON.stringify(chunkData) + "\n");
549
- }
550
-
551
- state = await client.threads.getState(threadId);
552
- const tasks = state.tasks;
553
- const interrupts = (tasks?.[0]?.interrupts ?? []) as Interrupt[];
554
- const isEndNode = state.next.length === 0;
555
- const writes = state.metadata?.writes ?? {};
556
-
557
- if (!interrupts?.length) {
558
- nodeName = isEndNode ? "__end__" : (state.next[0] ?? Object.keys(writes)[0]);
559
- }
560
-
561
- telemetry.capture("oss.runtime.agent_execution_stream_ended", streamInfo);
562
-
563
- emit(
564
- getStateSyncEvent({
565
- threadId,
566
- runId: externalRunId,
567
- agentName: agent.name,
568
- nodeName: isEndNode ? "__end__" : nodeName,
569
- state: state.values,
570
- running: !shouldExit,
571
- active: false,
572
- includeMessages: true,
573
- schemaKeys,
574
- }),
575
- );
576
-
577
- return Promise.resolve();
578
- } catch (e) {
579
- // Distinguish between user errors and system errors for logging
580
- if (isUserConfigurationError(e)) {
581
- // Log user errors at debug level to reduce noise
582
- logger.debug({ error: e.message, code: e.code }, "User configuration error");
583
- } else {
584
- // Log actual system errors at error level
585
- logger.error(e);
586
- }
587
-
588
- telemetry.capture("oss.runtime.agent_execution_stream_errored", {
589
- ...streamInfo,
590
- error: e.message,
591
- });
592
-
593
- // Re-throw CopilotKit errors so they can be handled properly at higher levels
594
- if (
595
- e instanceof CopilotKitError ||
596
- e instanceof CopilotKitLowLevelError ||
597
- (e instanceof Error && e.name && e.name.includes("CopilotKit"))
598
- ) {
599
- throw e;
600
- }
601
-
602
- return Promise.resolve();
603
- }
604
- }
605
-
606
- function getStateSyncEvent({
607
- threadId,
608
- runId,
609
- agentName,
610
- nodeName,
611
- state,
612
- running,
613
- active,
614
- includeMessages = false,
615
- schemaKeys,
616
- }: {
617
- threadId: string;
618
- runId: string;
619
- agentName: string;
620
- nodeName: string;
621
- state: State;
622
- running: boolean;
623
- active: boolean;
624
- includeMessages?: boolean;
625
- schemaKeys: SchemaKeys;
626
- }): string {
627
- if (!includeMessages) {
628
- state = Object.keys(state).reduce((acc, key) => {
629
- if (key !== "messages") {
630
- acc[key] = state[key];
631
- }
632
- return acc;
633
- }, {} as State);
634
- } else {
635
- state = {
636
- ...state,
637
- messages: langchainMessagesToCopilotKit(state.messages || []),
638
- };
639
- }
640
-
641
- // Do not emit state keys that are not part of the output schema
642
- if (schemaKeys?.output) {
643
- state = filterObjectBySchemaKeys(state, schemaKeys.output);
644
- }
645
-
646
- return (
647
- JSON.stringify({
648
- event: LangGraphEventTypes.OnCopilotKitStateSync,
649
- thread_id: threadId,
650
- run_id: runId,
651
- agent_name: agentName,
652
- node_name: nodeName,
653
- active: active,
654
- state: state,
655
- running: running,
656
- role: "assistant",
657
- }) + "\n"
658
- );
659
- }
660
-
661
- class StreamingStateExtractor {
662
- private emitIntermediateState: { [key: string]: any }[];
663
- private toolCallBuffer: { [key: string]: string };
664
- private currentToolCall: string | null;
665
- private previouslyParsableState: { [key: string]: any };
666
-
667
- constructor(emitIntermediateState: { [key: string]: any }[]) {
668
- this.emitIntermediateState = emitIntermediateState;
669
- this.toolCallBuffer = {};
670
- this.currentToolCall = null;
671
- this.previouslyParsableState = {};
672
- }
673
-
674
- bufferToolCalls(event: {
675
- data: { chunk: { tool_call_chunks: { name: string | null; args: string }[] } };
676
- }) {
677
- if (event.data.chunk.tool_call_chunks.length > 0) {
678
- const chunk = event.data.chunk.tool_call_chunks[0];
679
-
680
- if (chunk.name !== null && chunk.name !== undefined) {
681
- this.currentToolCall = chunk.name;
682
- this.toolCallBuffer[this.currentToolCall] = chunk.args;
683
- } else if (this.currentToolCall !== null && this.currentToolCall !== undefined) {
684
- this.toolCallBuffer[this.currentToolCall] += chunk.args;
685
- }
686
- }
687
- }
688
-
689
- getEmitStateConfig(currentToolName: string): [string | null, string | null] {
690
- for (const config of this.emitIntermediateState) {
691
- const stateKey = config["state_key"];
692
- const tool = config["tool"];
693
- const toolArgument = config["tool_argument"];
694
-
695
- if (currentToolName === tool) {
696
- return [toolArgument, stateKey];
697
- }
698
- }
699
- return [null, null];
700
- }
701
-
702
- extractState(): State {
703
- const state: State = {};
704
-
705
- for (const [key, value] of Object.entries(this.toolCallBuffer)) {
706
- const [argumentName, stateKey] = this.getEmitStateConfig(key);
707
-
708
- if (stateKey === null) {
709
- continue;
710
- }
711
-
712
- let parsedValue;
713
- try {
714
- parsedValue = parsePartialJson(value);
715
- } catch (error) {
716
- if (key in this.previouslyParsableState) {
717
- parsedValue = this.previouslyParsableState[key];
718
- } else {
719
- continue;
720
- }
721
- }
722
-
723
- this.previouslyParsableState[key] = parsedValue;
724
-
725
- if (!argumentName) {
726
- state[stateKey] = parsedValue;
727
- } else {
728
- state[stateKey] = parsedValue[argumentName];
729
- }
730
- }
731
-
732
- return state;
733
- }
734
- }
735
-
736
- // Start of Selection
737
- function langGraphDefaultMergeState(
738
- state: State,
739
- messages: LangGraphPlatformMessage[],
740
- actions: ExecutionAction[],
741
- agentName: string,
742
- ): State {
743
- if (messages.length > 0 && "role" in messages[0] && messages[0].role === "system") {
744
- // remove system message
745
- messages = messages.slice(1);
746
- }
747
-
748
- // merge with existing messages
749
- const existingMessages: LangGraphPlatformMessage[] = state.messages || [];
750
- const existingMessageIds = new Set(existingMessages.map((message) => message.id));
751
- const messageIds = new Set(messages.map((message) => message.id));
752
-
753
- let removedMessages = [];
754
- if (messages.length < existingMessages.length) {
755
- // Messages were removed
756
- removedMessages = existingMessages
757
- .filter((m) => !messageIds.has(m.id))
758
- .map((m) => new RemoveMessage({ id: m.id }));
759
- }
760
-
761
- const newMessages = messages.filter((message) => !existingMessageIds.has(message.id));
762
-
763
- return {
764
- ...state,
765
- messages: [...removedMessages, ...newMessages],
766
- copilotkit: {
767
- actions,
768
- },
769
- };
770
- }
771
-
772
- export function langchainMessagesToCopilotKit(messages: any[]): any[] {
773
- const result: any[] = [];
774
- const tool_call_names: Record<string, string> = {};
775
-
776
- // First pass: gather all tool call names from AI messages
777
- for (const message of messages) {
778
- if (message.type === "ai") {
779
- for (const tool_call of message.tool_calls) {
780
- tool_call_names[tool_call.id] = tool_call.name;
781
- }
782
- }
783
- }
784
-
785
- for (const message of messages) {
786
- let content: any = message.content;
787
- if (content instanceof Array) {
788
- content = content[0];
789
- }
790
- if (content instanceof Object) {
791
- content = content.text;
792
- }
793
-
794
- if (message.type === "human") {
795
- result.push({
796
- role: "user",
797
- content: content,
798
- id: message.id,
799
- });
800
- } else if (message.type === "system") {
801
- result.push({
802
- role: "system",
803
- content: content,
804
- id: message.id,
805
- });
806
- } else if (message.type === "ai") {
807
- if (message.tool_calls && message.tool_calls.length > 0) {
808
- for (const tool_call of message.tool_calls) {
809
- result.push({
810
- id: tool_call.id,
811
- name: tool_call.name,
812
- arguments: tool_call.args,
813
- parentMessageId: message.id,
814
- });
815
- }
816
- } else {
817
- result.push({
818
- role: "assistant",
819
- content: content,
820
- id: message.id,
821
- parentMessageId: message.id,
822
- });
823
- }
824
- } else if (message.type === "tool") {
825
- const actionName = tool_call_names[message.tool_call_id] || message.name || "";
826
- result.push({
827
- actionExecutionId: message.tool_call_id,
828
- actionName: actionName,
829
- result: content,
830
- id: message.id,
831
- });
832
- }
833
- }
834
- const resultsDict: Record<string, any> = {};
835
- for (const msg of result) {
836
- if (msg.actionExecutionId) {
837
- resultsDict[msg.actionExecutionId] = msg;
838
- }
839
- }
840
-
841
- const reorderedResult: Message[] = [];
842
-
843
- for (const msg of result) {
844
- // If it's not a tool result, just append it
845
- if (!("actionExecutionId" in msg)) {
846
- reorderedResult.push(msg);
847
- }
848
-
849
- // If the message has arguments (i.e., is a tool call invocation),
850
- // append the corresponding result right after it
851
- if ("arguments" in msg) {
852
- const msgId = msg.id;
853
- if (msgId in resultsDict) {
854
- reorderedResult.push(resultsDict[msgId]);
855
- }
856
- }
857
- }
858
-
859
- return reorderedResult;
860
- }
861
-
862
- function copilotkitMessagesToLangChain(messages: Message[]): LangGraphPlatformMessage[] {
863
- const result: LangGraphPlatformMessage[] = [];
864
- const processedActionExecutions = new Set<string>();
865
-
866
- for (const message of messages) {
867
- // Handle TextMessage
868
- if (message.isTextMessage()) {
869
- if (message.role === "user") {
870
- // Human message
871
- result.push({
872
- ...message,
873
- role: MessageRole.user,
874
- });
875
- } else if (message.role === "system") {
876
- // System message
877
- result.push({
878
- ...message,
879
- role: MessageRole.system,
880
- });
881
- } else if (message.role === "assistant") {
882
- // Assistant message
883
- result.push({
884
- ...message,
885
- role: MessageRole.assistant,
886
- });
887
- }
888
- continue;
889
- }
890
-
891
- // Handle ImageMessage
892
- if (message.isImageMessage()) {
893
- if (message.role === "user") {
894
- result.push({
895
- ...message,
896
- role: MessageRole.user,
897
- content: "",
898
- });
899
- } else if (message.role === "assistant") {
900
- result.push({
901
- ...message,
902
- role: MessageRole.assistant,
903
- content: "",
904
- });
905
- }
906
- continue;
907
- }
908
-
909
- // Handle ActionExecutionMessage (multiple tool calls per parentMessageId)
910
- if (message.isActionExecutionMessage()) {
911
- const messageId = message.parentMessageId ?? message.id;
912
-
913
- // If we've already processed this action execution group, skip
914
- if (processedActionExecutions.has(messageId)) {
915
- continue;
916
- }
917
-
918
- processedActionExecutions.add(messageId);
919
-
920
- // Gather all tool calls related to this messageId
921
- const relatedActionExecutions = messages.filter(
922
- (m) =>
923
- m.isActionExecutionMessage() &&
924
- ((m.parentMessageId && m.parentMessageId === messageId) || m.id === messageId),
925
- ) as ActionExecutionMessage[];
926
-
927
- const tool_calls: ToolCall[] = relatedActionExecutions.map((m) => ({
928
- name: m.name,
929
- args: m.arguments,
930
- id: m.id,
931
- }));
932
-
933
- result.push({
934
- id: messageId,
935
- type: "ActionExecutionMessage",
936
- content: "",
937
- tool_calls: tool_calls,
938
- role: MessageRole.assistant,
939
- } satisfies LangGraphPlatformActionExecutionMessage);
940
-
941
- continue;
942
- }
943
-
944
- // Handle ResultMessage
945
- if (message.isResultMessage()) {
946
- result.push({
947
- type: message.type,
948
- content: message.result,
949
- id: message.id,
950
- tool_call_id: message.actionExecutionId,
951
- name: message.actionName,
952
- role: MessageRole.tool,
953
- } satisfies LangGraphPlatformResultMessage);
954
- continue;
955
- }
956
-
957
- throw new Error(`Unknown message type ${message.type}`);
958
- }
959
-
960
- return result;
961
- }
962
-
963
- function getSchemaKeys(graphSchema: GraphSchema): SchemaKeys {
964
- const CONSTANT_KEYS = ["messages", "copilotkit"];
965
- let configSchema = null;
966
- if (graphSchema.config_schema.properties) {
967
- configSchema = Object.keys(graphSchema.config_schema.properties);
968
- }
969
- if (!graphSchema.input_schema.properties || !graphSchema.output_schema.properties) {
970
- return configSchema;
971
- }
972
- const inputSchema = Object.keys(graphSchema.input_schema.properties);
973
- const outputSchema = Object.keys(graphSchema.output_schema.properties);
974
-
975
- return {
976
- input: inputSchema && inputSchema.length ? [...inputSchema, ...CONSTANT_KEYS] : null,
977
- output: outputSchema && outputSchema.length ? [...outputSchema, ...CONSTANT_KEYS] : null,
978
- config: configSchema,
979
- };
980
- }
981
-
982
- function filterObjectBySchemaKeys(obj: Record<string, any>, schemaKeys: string[]) {
983
- return Object.fromEntries(Object.entries(obj).filter(([key]) => schemaKeys.includes(key)));
984
- }
985
-
986
- function emitInterrupt(interruptValue: any, emit: (data: string) => void) {
987
- if (typeof interruptValue != "string" && "__copilotkit_interrupt_value__" in interruptValue) {
988
- const evValue = interruptValue.__copilotkit_interrupt_value__;
989
- emit(
990
- JSON.stringify({
991
- event: LangGraphEventTypes.OnCopilotKitInterrupt,
992
- data: {
993
- value: typeof evValue === "string" ? evValue : JSON.stringify(evValue),
994
- messages: langchainMessagesToCopilotKit(interruptValue.__copilotkit_messages__),
995
- },
996
- }) + "\n",
997
- );
998
- } else {
999
- emit(
1000
- JSON.stringify({
1001
- event: LangGraphEventTypes.OnInterrupt,
1002
- value: typeof interruptValue === "string" ? interruptValue : JSON.stringify(interruptValue),
1003
- }) + "\n",
1004
- );
1005
- }
1006
- }