@copilotkit/runtime 0.0.0-0.0.0-max-changeset-10101010101010-20260109191632

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/.eslintrc.js +7 -0
  2. package/CHANGELOG.md +2905 -0
  3. package/LICENSE +21 -0
  4. package/README.md +76 -0
  5. package/__snapshots__/schema/schema.graphql +371 -0
  6. package/dist/index.d.ts +1495 -0
  7. package/dist/index.js +5644 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/index.mjs +5601 -0
  10. package/dist/index.mjs.map +1 -0
  11. package/dist/langgraph.d.ts +284 -0
  12. package/dist/langgraph.js +211 -0
  13. package/dist/langgraph.js.map +1 -0
  14. package/dist/langgraph.mjs +206 -0
  15. package/dist/langgraph.mjs.map +1 -0
  16. package/dist/v2/index.d.ts +2 -0
  17. package/dist/v2/index.js +22 -0
  18. package/dist/v2/index.js.map +1 -0
  19. package/dist/v2/index.mjs +5 -0
  20. package/dist/v2/index.mjs.map +1 -0
  21. package/jest.config.js +10 -0
  22. package/package.json +143 -0
  23. package/scripts/generate-gql-schema.ts +13 -0
  24. package/src/agents/langgraph/event-source.ts +329 -0
  25. package/src/agents/langgraph/events.ts +377 -0
  26. package/src/graphql/inputs/action.input.ts +16 -0
  27. package/src/graphql/inputs/agent-session.input.ts +13 -0
  28. package/src/graphql/inputs/agent-state.input.ts +13 -0
  29. package/src/graphql/inputs/cloud-guardrails.input.ts +16 -0
  30. package/src/graphql/inputs/cloud.input.ts +8 -0
  31. package/src/graphql/inputs/context-property.input.ts +10 -0
  32. package/src/graphql/inputs/copilot-context.input.ts +10 -0
  33. package/src/graphql/inputs/custom-property.input.ts +15 -0
  34. package/src/graphql/inputs/extensions.input.ts +21 -0
  35. package/src/graphql/inputs/forwarded-parameters.input.ts +22 -0
  36. package/src/graphql/inputs/frontend.input.ts +14 -0
  37. package/src/graphql/inputs/generate-copilot-response.input.ts +59 -0
  38. package/src/graphql/inputs/load-agent-state.input.ts +10 -0
  39. package/src/graphql/inputs/message.input.ts +110 -0
  40. package/src/graphql/inputs/meta-event.input.ts +18 -0
  41. package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
  42. package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
  43. package/src/graphql/message-conversion/gql-to-agui.test.ts +1580 -0
  44. package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
  45. package/src/graphql/message-conversion/index.ts +2 -0
  46. package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
  47. package/src/graphql/resolvers/copilot.resolver.ts +708 -0
  48. package/src/graphql/resolvers/state.resolver.ts +27 -0
  49. package/src/graphql/types/agents-response.type.ts +19 -0
  50. package/src/graphql/types/base/index.ts +10 -0
  51. package/src/graphql/types/converted/index.ts +176 -0
  52. package/src/graphql/types/copilot-response.type.ts +138 -0
  53. package/src/graphql/types/enums.ts +38 -0
  54. package/src/graphql/types/extensions-response.type.ts +23 -0
  55. package/src/graphql/types/guardrails-result.type.ts +20 -0
  56. package/src/graphql/types/load-agent-state-response.type.ts +17 -0
  57. package/src/graphql/types/message-status.type.ts +42 -0
  58. package/src/graphql/types/meta-events.type.ts +71 -0
  59. package/src/graphql/types/response-status.type.ts +66 -0
  60. package/src/index.ts +4 -0
  61. package/src/langgraph.ts +1 -0
  62. package/src/lib/cloud/index.ts +4 -0
  63. package/src/lib/error-messages.ts +200 -0
  64. package/src/lib/index.ts +52 -0
  65. package/src/lib/integrations/index.ts +6 -0
  66. package/src/lib/integrations/nest/index.ts +14 -0
  67. package/src/lib/integrations/nextjs/app-router.ts +38 -0
  68. package/src/lib/integrations/nextjs/pages-router.ts +39 -0
  69. package/src/lib/integrations/node-express/index.ts +14 -0
  70. package/src/lib/integrations/node-http/index.ts +143 -0
  71. package/src/lib/integrations/node-http/request-handler.ts +111 -0
  72. package/src/lib/integrations/shared.ts +161 -0
  73. package/src/lib/logger.ts +28 -0
  74. package/src/lib/observability.ts +160 -0
  75. package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
  76. package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
  77. package/src/lib/runtime/agent-integrations/langgraph/agent.ts +209 -0
  78. package/src/lib/runtime/agent-integrations/langgraph/consts.ts +34 -0
  79. package/src/lib/runtime/agent-integrations/langgraph/index.ts +2 -0
  80. package/src/lib/runtime/copilot-runtime.ts +710 -0
  81. package/src/lib/runtime/mcp-tools-utils.ts +254 -0
  82. package/src/lib/runtime/retry-utils.ts +96 -0
  83. package/src/lib/runtime/telemetry-agent-runner.ts +139 -0
  84. package/src/lib/runtime/types.ts +49 -0
  85. package/src/lib/runtime/utils.ts +87 -0
  86. package/src/lib/streaming.ts +202 -0
  87. package/src/lib/telemetry-client.ts +64 -0
  88. package/src/service-adapters/anthropic/anthropic-adapter.ts +452 -0
  89. package/src/service-adapters/anthropic/utils.ts +152 -0
  90. package/src/service-adapters/bedrock/bedrock-adapter.ts +73 -0
  91. package/src/service-adapters/conversion.ts +67 -0
  92. package/src/service-adapters/empty/empty-adapter.ts +38 -0
  93. package/src/service-adapters/events.ts +294 -0
  94. package/src/service-adapters/experimental/ollama/ollama-adapter.ts +84 -0
  95. package/src/service-adapters/google/google-genai-adapter.test.ts +104 -0
  96. package/src/service-adapters/google/google-genai-adapter.ts +88 -0
  97. package/src/service-adapters/groq/groq-adapter.ts +203 -0
  98. package/src/service-adapters/index.ts +18 -0
  99. package/src/service-adapters/langchain/langchain-adapter.ts +111 -0
  100. package/src/service-adapters/langchain/langserve.ts +88 -0
  101. package/src/service-adapters/langchain/types.ts +14 -0
  102. package/src/service-adapters/langchain/utils.ts +313 -0
  103. package/src/service-adapters/openai/openai-adapter.ts +283 -0
  104. package/src/service-adapters/openai/openai-assistant-adapter.ts +344 -0
  105. package/src/service-adapters/openai/utils.ts +199 -0
  106. package/src/service-adapters/service-adapter.ts +41 -0
  107. package/src/service-adapters/shared/error-utils.ts +61 -0
  108. package/src/service-adapters/shared/index.ts +1 -0
  109. package/src/service-adapters/unify/unify-adapter.ts +151 -0
  110. package/src/utils/failed-response-status-reasons.ts +70 -0
  111. package/src/utils/index.ts +1 -0
  112. package/src/v2/index.ts +3 -0
  113. package/tests/global.d.ts +13 -0
  114. package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
  115. package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +389 -0
  116. package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
  117. package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
  118. package/tests/setup.jest.ts +21 -0
  119. package/tests/tsconfig.json +10 -0
  120. package/tsconfig.json +13 -0
  121. package/tsup.config.ts +20 -0
  122. package/typedoc.json +4 -0
@@ -0,0 +1,313 @@
1
+ import {
2
+ ActionExecutionMessage,
3
+ Message,
4
+ ResultMessage,
5
+ TextMessage,
6
+ } from "../../graphql/types/converted";
7
+ import {
8
+ AIMessage,
9
+ AIMessageChunk,
10
+ BaseMessage,
11
+ BaseMessageChunk,
12
+ HumanMessage,
13
+ SystemMessage,
14
+ ToolMessage,
15
+ } from "@langchain/core/messages";
16
+ import { DynamicStructuredTool } from "@langchain/core/tools";
17
+ import { z } from "zod";
18
+ import { ActionInput } from "../../graphql/inputs/action.input";
19
+ import { LangChainReturnType } from "./types";
20
+ import { RuntimeEventSubject } from "../events";
21
+ import { randomId, convertJsonSchemaToZodSchema } from "@copilotkit/shared";
22
+
23
+ export function convertMessageToLangChainMessage(message: Message): BaseMessage {
24
+ if (message.isTextMessage()) {
25
+ if (message.role == "user") {
26
+ return new HumanMessage(message.content);
27
+ } else if (message.role == "assistant") {
28
+ return new AIMessage(message.content);
29
+ } else if (message.role === "system") {
30
+ return new SystemMessage(message.content);
31
+ }
32
+ } else if (message.isActionExecutionMessage()) {
33
+ return new AIMessage({
34
+ content: "",
35
+ tool_calls: [
36
+ {
37
+ id: message.id,
38
+ args: message.arguments,
39
+ name: message.name,
40
+ },
41
+ ],
42
+ });
43
+ } else if (message.isResultMessage()) {
44
+ return new ToolMessage({
45
+ content: message.result,
46
+ tool_call_id: message.actionExecutionId,
47
+ });
48
+ }
49
+ }
50
+
51
+ export function convertActionInputToLangChainTool(actionInput: ActionInput): any {
52
+ return new DynamicStructuredTool({
53
+ ...actionInput,
54
+ name: actionInput.name,
55
+ description: actionInput.description,
56
+ schema: convertJsonSchemaToZodSchema(
57
+ JSON.parse(actionInput.jsonSchema),
58
+ true,
59
+ ) as z.ZodObject<any>,
60
+ func: async () => {
61
+ return "";
62
+ },
63
+ });
64
+ }
65
+
66
+ interface StreamLangChainResponseParams {
67
+ result: LangChainReturnType;
68
+ eventStream$: RuntimeEventSubject;
69
+ actionExecution?: {
70
+ id: string;
71
+ name: string;
72
+ returnDirect?: boolean;
73
+ };
74
+ }
75
+
76
+ function getConstructorName(object: any): string {
77
+ if (object && typeof object === "object" && object.constructor && object.constructor.name) {
78
+ return object.constructor.name;
79
+ }
80
+ return "";
81
+ }
82
+
83
+ function isAIMessage(message: any): message is AIMessage {
84
+ return Object.prototype.toString.call(message) === "[object AIMessage]";
85
+ }
86
+
87
+ function isAIMessageChunk(message: any): message is AIMessageChunk {
88
+ return Object.prototype.toString.call(message) === "[object AIMessageChunk]";
89
+ }
90
+
91
+ function isBaseMessageChunk(message: any): message is BaseMessageChunk {
92
+ return Object.prototype.toString.call(message) === "[object BaseMessageChunk]";
93
+ }
94
+
95
+ function maybeSendActionExecutionResultIsMessage(
96
+ eventStream$: RuntimeEventSubject,
97
+ actionExecution?: { id: string; name: string },
98
+ ) {
99
+ // language models need a result after the function call
100
+ // we simply let them know that we are sending a message
101
+ if (actionExecution) {
102
+ eventStream$.sendActionExecutionResult({
103
+ actionExecutionId: actionExecution.id,
104
+ actionName: actionExecution.name,
105
+ result: "Sending a message",
106
+ });
107
+ }
108
+ }
109
+
110
+ export async function streamLangChainResponse({
111
+ result,
112
+ eventStream$,
113
+ actionExecution,
114
+ }: StreamLangChainResponseParams) {
115
+ // We support several types of return values from LangChain functions:
116
+
117
+ // 1. string
118
+
119
+ if (typeof result === "string") {
120
+ if (!actionExecution || actionExecution?.returnDirect) {
121
+ // Just send one chunk with the string as the content.
122
+ eventStream$.sendActionExecutionResult({
123
+ actionExecutionId: actionExecution.id,
124
+ actionName: actionExecution.name,
125
+ result: result,
126
+ });
127
+ eventStream$.sendTextMessage(randomId(), result);
128
+ } else {
129
+ // Send as a result
130
+ eventStream$.sendActionExecutionResult({
131
+ actionExecutionId: actionExecution.id,
132
+ actionName: actionExecution.name,
133
+ result: result,
134
+ });
135
+ }
136
+ }
137
+
138
+ // 2. AIMessage
139
+ // Send the content and function call of the AIMessage as the content of the chunk.
140
+ else if (isAIMessage(result)) {
141
+ maybeSendActionExecutionResultIsMessage(eventStream$, actionExecution);
142
+
143
+ if (result.content) {
144
+ eventStream$.sendTextMessage(randomId(), result.content as string);
145
+ }
146
+ for (const toolCall of result.tool_calls) {
147
+ eventStream$.sendActionExecution({
148
+ actionExecutionId: toolCall.id || randomId(),
149
+ actionName: toolCall.name,
150
+ args: JSON.stringify(toolCall.args),
151
+ });
152
+ }
153
+ }
154
+
155
+ // 3. BaseMessageChunk
156
+ // Send the content and function call of the AIMessage as the content of the chunk.
157
+ else if (isBaseMessageChunk(result)) {
158
+ maybeSendActionExecutionResultIsMessage(eventStream$, actionExecution);
159
+
160
+ if (result.lc_kwargs?.content) {
161
+ eventStream$.sendTextMessage(randomId(), result.content as string);
162
+ }
163
+ if (result.lc_kwargs?.tool_calls) {
164
+ for (const toolCall of result.lc_kwargs?.tool_calls) {
165
+ eventStream$.sendActionExecution({
166
+ actionExecutionId: toolCall.id || randomId(),
167
+ actionName: toolCall.name,
168
+ args: JSON.stringify(toolCall.args),
169
+ });
170
+ }
171
+ }
172
+ }
173
+
174
+ // 4. IterableReadableStream
175
+ // Stream the result of the LangChain function.
176
+ else if (result && "getReader" in result) {
177
+ maybeSendActionExecutionResultIsMessage(eventStream$, actionExecution);
178
+
179
+ let reader = result.getReader();
180
+
181
+ let mode: "function" | "message" | null = null;
182
+ let currentMessageId: string;
183
+
184
+ const toolCallDetails = {
185
+ name: null,
186
+ id: null,
187
+ index: null,
188
+ prevIndex: null,
189
+ };
190
+
191
+ while (true) {
192
+ try {
193
+ const { done, value } = await reader.read();
194
+
195
+ let toolCallName: string | undefined = undefined;
196
+ let toolCallId: string | undefined = undefined;
197
+ let toolCallArgs: string | undefined = undefined;
198
+ let hasToolCall: boolean = false;
199
+ let content = "";
200
+ if (value && value.content) {
201
+ content = Array.isArray(value.content)
202
+ ? (((value.content[0] as any)?.text ?? "") as string)
203
+ : value.content;
204
+ }
205
+
206
+ if (isAIMessageChunk(value)) {
207
+ let chunk = value.tool_call_chunks?.[0];
208
+ toolCallArgs = chunk?.args;
209
+ hasToolCall = chunk != undefined;
210
+ if (chunk?.name) toolCallDetails.name = chunk.name;
211
+ // track different index on the same tool cool
212
+ if (chunk?.index != null) {
213
+ toolCallDetails.index = chunk.index; // 1
214
+ if (toolCallDetails.prevIndex == null) toolCallDetails.prevIndex = chunk.index;
215
+ }
216
+ // Differentiate when calling the same tool but with different index
217
+ if (chunk?.id)
218
+ toolCallDetails.id = chunk.index != null ? `${chunk.id}-idx-${chunk.index}` : chunk.id;
219
+
220
+ // Assign to internal variables that the entire script here knows how to work with
221
+ toolCallName = toolCallDetails.name;
222
+ toolCallId = toolCallDetails.id;
223
+ } else if (isBaseMessageChunk(value)) {
224
+ let chunk = value.additional_kwargs?.tool_calls?.[0];
225
+ toolCallName = chunk?.function?.name;
226
+ toolCallId = chunk?.id;
227
+ toolCallArgs = chunk?.function?.arguments;
228
+ hasToolCall = chunk?.function != undefined;
229
+ }
230
+
231
+ // When switching from message to function or vice versa,
232
+ // send the respective end event.
233
+ // If toolCallName is defined, it means a new tool call starts.
234
+ if (mode === "message" && (toolCallId || done)) {
235
+ mode = null;
236
+ eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
237
+ } else if (mode === "function" && (!hasToolCall || done)) {
238
+ mode = null;
239
+ eventStream$.sendActionExecutionEnd({ actionExecutionId: toolCallId });
240
+ }
241
+
242
+ if (done) {
243
+ break;
244
+ }
245
+
246
+ // If we send a new message type, send the appropriate start event.
247
+ if (mode === null) {
248
+ if (hasToolCall && toolCallId && toolCallName) {
249
+ mode = "function";
250
+ eventStream$.sendActionExecutionStart({
251
+ actionExecutionId: toolCallId,
252
+ actionName: toolCallName,
253
+ parentMessageId: value.lc_kwargs?.id,
254
+ });
255
+ } else if (content) {
256
+ mode = "message";
257
+ currentMessageId = value.lc_kwargs?.id || randomId();
258
+ eventStream$.sendTextMessageStart({ messageId: currentMessageId });
259
+ }
260
+ }
261
+
262
+ // send the content events
263
+ if (mode === "message" && content) {
264
+ eventStream$.sendTextMessageContent({
265
+ messageId: currentMessageId,
266
+ content,
267
+ });
268
+ } else if (mode === "function" && toolCallArgs) {
269
+ // For calls of the same tool with different index, we seal last tool call and register a new one
270
+ if (toolCallDetails.index !== toolCallDetails.prevIndex) {
271
+ eventStream$.sendActionExecutionEnd({ actionExecutionId: toolCallId });
272
+ eventStream$.sendActionExecutionStart({
273
+ actionExecutionId: toolCallId,
274
+ actionName: toolCallName,
275
+ parentMessageId: value.lc_kwargs?.id,
276
+ });
277
+ toolCallDetails.prevIndex = toolCallDetails.index;
278
+ }
279
+ eventStream$.sendActionExecutionArgs({
280
+ actionExecutionId: toolCallId,
281
+ args: toolCallArgs,
282
+ });
283
+ }
284
+ } catch (error) {
285
+ console.error("Error reading from stream", error);
286
+ break;
287
+ }
288
+ }
289
+ } else if (actionExecution) {
290
+ eventStream$.sendActionExecutionResult({
291
+ actionExecutionId: actionExecution.id,
292
+ actionName: actionExecution.name,
293
+ result: encodeResult(result),
294
+ });
295
+ }
296
+
297
+ // unsupported type
298
+ else {
299
+ throw new Error("Invalid return type from LangChain function.");
300
+ }
301
+
302
+ eventStream$.complete();
303
+ }
304
+
305
+ function encodeResult(result: any): string {
306
+ if (result === undefined) {
307
+ return "";
308
+ } else if (typeof result === "string") {
309
+ return result;
310
+ } else {
311
+ return JSON.stringify(result);
312
+ }
313
+ }
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Copilot Runtime adapter for OpenAI.
3
+ *
4
+ * ## Example
5
+ *
6
+ * ```ts
7
+ * import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/runtime";
8
+ * import OpenAI from "openai";
9
+ *
10
+ * const copilotKit = new CopilotRuntime();
11
+ *
12
+ * const openai = new OpenAI({
13
+ * organization: "<your-organization-id>", // optional
14
+ * apiKey: "<your-api-key>",
15
+ * });
16
+ *
17
+ * return new OpenAIAdapter({ openai });
18
+ * ```
19
+ *
20
+ * ## Example with Azure OpenAI
21
+ *
22
+ * ```ts
23
+ * import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/runtime";
24
+ * import OpenAI from "openai";
25
+ *
26
+ * // The name of your Azure OpenAI Instance.
27
+ * // https://learn.microsoft.com/en-us/azure/cognitive-services/openai/how-to/create-resource?pivots=web-portal#create-a-resource
28
+ * const instance = "<your instance name>";
29
+ *
30
+ * // Corresponds to your Model deployment within your OpenAI resource, e.g. my-gpt35-16k-deployment
31
+ * // Navigate to the Azure OpenAI Studio to deploy a model.
32
+ * const model = "<your model>";
33
+ *
34
+ * const apiKey = process.env["AZURE_OPENAI_API_KEY"];
35
+ * if (!apiKey) {
36
+ * throw new Error("The AZURE_OPENAI_API_KEY environment variable is missing or empty.");
37
+ * }
38
+ *
39
+ * const copilotKit = new CopilotRuntime();
40
+ *
41
+ * const openai = new OpenAI({
42
+ * apiKey,
43
+ * baseURL: `https://${instance}.openai.azure.com/openai/deployments/${model}`,
44
+ * defaultQuery: { "api-version": "2024-04-01-preview" },
45
+ * defaultHeaders: { "api-key": apiKey },
46
+ * });
47
+ *
48
+ * return new OpenAIAdapter({ openai });
49
+ * ```
50
+ */
51
+ import type OpenAI from "openai";
52
+ import {
53
+ CopilotServiceAdapter,
54
+ CopilotRuntimeChatCompletionRequest,
55
+ CopilotRuntimeChatCompletionResponse,
56
+ } from "../service-adapter";
57
+ import {
58
+ convertActionInputToOpenAITool,
59
+ convertMessageToOpenAIMessage,
60
+ limitMessagesToTokenCount,
61
+ } from "./utils";
62
+ import { randomUUID } from "@copilotkit/shared";
63
+ import { convertServiceAdapterError } from "../shared";
64
+
65
+ const DEFAULT_MODEL = "gpt-4o";
66
+
67
+ export interface OpenAIAdapterParams {
68
+ /**
69
+ * An optional OpenAI instance to use. If not provided, a new instance will be
70
+ * created.
71
+ */
72
+ openai?: OpenAI;
73
+
74
+ /**
75
+ * The model to use.
76
+ */
77
+ model?: string;
78
+
79
+ /**
80
+ * Whether to disable parallel tool calls.
81
+ * You can disable parallel tool calls to force the model to execute tool calls sequentially.
82
+ * This is useful if you want to execute tool calls in a specific order so that the state changes
83
+ * introduced by one tool call are visible to the next tool call. (i.e. new actions or readables)
84
+ *
85
+ * @default false
86
+ */
87
+ disableParallelToolCalls?: boolean;
88
+
89
+ /**
90
+ * Whether to keep the role in system messages as "System".
91
+ * By default, it is converted to "developer", which is used by newer OpenAI models
92
+ *
93
+ * @default false
94
+ */
95
+ keepSystemRole?: boolean;
96
+ }
97
+
98
+ export class OpenAIAdapter implements CopilotServiceAdapter {
99
+ public model: string = DEFAULT_MODEL;
100
+ public provider = "openai";
101
+
102
+ private disableParallelToolCalls: boolean = false;
103
+ private _openai: OpenAI;
104
+ private keepSystemRole: boolean = false;
105
+
106
+ public get openai(): OpenAI {
107
+ return this._openai;
108
+ }
109
+ public get name() {
110
+ return "OpenAIAdapter";
111
+ }
112
+
113
+ constructor(params?: OpenAIAdapterParams) {
114
+ if (params?.openai) {
115
+ this._openai = params.openai;
116
+ }
117
+ // If no instance provided, we'll lazy-load in ensureOpenAI()
118
+
119
+ if (params?.model) {
120
+ this.model = params.model;
121
+ }
122
+ this.disableParallelToolCalls = params?.disableParallelToolCalls || false;
123
+ this.keepSystemRole = params?.keepSystemRole ?? false;
124
+ }
125
+
126
+ private ensureOpenAI(): OpenAI {
127
+ if (!this._openai) {
128
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
129
+ const OpenAI = require("openai").default;
130
+ this._openai = new OpenAI();
131
+ }
132
+ return this._openai;
133
+ }
134
+
135
+ async process(
136
+ request: CopilotRuntimeChatCompletionRequest,
137
+ ): Promise<CopilotRuntimeChatCompletionResponse> {
138
+ const {
139
+ threadId: threadIdFromRequest,
140
+ model = this.model,
141
+ messages,
142
+ actions,
143
+ eventSource,
144
+ forwardedParameters,
145
+ } = request;
146
+ const tools = actions.map(convertActionInputToOpenAITool);
147
+ const threadId = threadIdFromRequest ?? randomUUID();
148
+
149
+ // ALLOWLIST APPROACH: Only include tool_result messages that correspond to valid tool_calls
150
+ // Step 1: Extract valid tool_call IDs
151
+ const validToolUseIds = new Set<string>();
152
+
153
+ for (const message of messages) {
154
+ if (message.isActionExecutionMessage()) {
155
+ validToolUseIds.add(message.id);
156
+ }
157
+ }
158
+
159
+ // Step 2: Filter messages, keeping only those with valid tool_call IDs
160
+ const filteredMessages = messages.filter((message) => {
161
+ if (message.isResultMessage()) {
162
+ // Skip if there's no corresponding tool_call
163
+ if (!validToolUseIds.has(message.actionExecutionId)) {
164
+ return false;
165
+ }
166
+
167
+ // Remove this ID from valid IDs so we don't process duplicates
168
+ validToolUseIds.delete(message.actionExecutionId);
169
+ return true;
170
+ }
171
+
172
+ // Keep all non-tool-result messages
173
+ return true;
174
+ });
175
+
176
+ let openaiMessages = filteredMessages.map((m) =>
177
+ convertMessageToOpenAIMessage(m, { keepSystemRole: this.keepSystemRole }),
178
+ );
179
+ openaiMessages = limitMessagesToTokenCount(openaiMessages, tools, model);
180
+
181
+ let toolChoice: any = forwardedParameters?.toolChoice;
182
+ if (forwardedParameters?.toolChoice === "function") {
183
+ toolChoice = {
184
+ type: "function",
185
+ function: { name: forwardedParameters.toolChoiceFunctionName },
186
+ };
187
+ }
188
+
189
+ try {
190
+ const openai = this.ensureOpenAI();
191
+ const stream = openai.beta.chat.completions.stream({
192
+ model: model,
193
+ stream: true,
194
+ messages: openaiMessages,
195
+ ...(tools.length > 0 && { tools }),
196
+ ...(forwardedParameters?.maxTokens && {
197
+ max_completion_tokens: forwardedParameters.maxTokens,
198
+ }),
199
+ ...(forwardedParameters?.stop && { stop: forwardedParameters.stop }),
200
+ ...(toolChoice && { tool_choice: toolChoice }),
201
+ ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
202
+ ...(forwardedParameters?.temperature && { temperature: forwardedParameters.temperature }),
203
+ });
204
+
205
+ eventSource.stream(async (eventStream$) => {
206
+ let mode: "function" | "message" | null = null;
207
+ let currentMessageId: string;
208
+ let currentToolCallId: string;
209
+
210
+ try {
211
+ for await (const chunk of stream) {
212
+ if (chunk.choices.length === 0) {
213
+ continue;
214
+ }
215
+
216
+ const toolCall = chunk.choices[0].delta.tool_calls?.[0];
217
+ const content = chunk.choices[0].delta.content;
218
+
219
+ // When switching from message to function or vice versa,
220
+ // send the respective end event.
221
+ // If toolCall?.id is defined, it means a new tool call starts.
222
+ if (mode === "message" && toolCall?.id) {
223
+ mode = null;
224
+ eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
225
+ } else if (mode === "function" && (toolCall === undefined || toolCall?.id)) {
226
+ mode = null;
227
+ eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
228
+ }
229
+
230
+ // If we send a new message type, send the appropriate start event.
231
+ if (mode === null) {
232
+ if (toolCall?.id) {
233
+ mode = "function";
234
+ currentToolCallId = toolCall!.id;
235
+ eventStream$.sendActionExecutionStart({
236
+ actionExecutionId: currentToolCallId,
237
+ parentMessageId: chunk.id,
238
+ actionName: toolCall!.function!.name,
239
+ });
240
+ } else if (content) {
241
+ mode = "message";
242
+ currentMessageId = chunk.id;
243
+ eventStream$.sendTextMessageStart({ messageId: currentMessageId });
244
+ }
245
+ }
246
+
247
+ // send the content events
248
+ if (mode === "message" && content) {
249
+ eventStream$.sendTextMessageContent({
250
+ messageId: currentMessageId,
251
+ content: content,
252
+ });
253
+ } else if (mode === "function" && toolCall?.function?.arguments) {
254
+ eventStream$.sendActionExecutionArgs({
255
+ actionExecutionId: currentToolCallId,
256
+ args: toolCall.function.arguments,
257
+ });
258
+ }
259
+ }
260
+
261
+ // send the end events
262
+ if (mode === "message") {
263
+ eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
264
+ } else if (mode === "function") {
265
+ eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
266
+ }
267
+ } catch (error) {
268
+ console.error("[OpenAI] Error during API call:", error);
269
+ throw convertServiceAdapterError(error, "OpenAI");
270
+ }
271
+
272
+ eventStream$.complete();
273
+ });
274
+ } catch (error) {
275
+ console.error("[OpenAI] Error during API call:", error);
276
+ throw convertServiceAdapterError(error, "OpenAI");
277
+ }
278
+
279
+ return {
280
+ threadId,
281
+ };
282
+ }
283
+ }