@copilotkit/runtime 0.0.0-0.0.0-max-changeset-10101010101010-20260109191632
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +7 -0
- package/CHANGELOG.md +2905 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/__snapshots__/schema/schema.graphql +371 -0
- package/dist/index.d.ts +1495 -0
- package/dist/index.js +5644 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +5601 -0
- package/dist/index.mjs.map +1 -0
- package/dist/langgraph.d.ts +284 -0
- package/dist/langgraph.js +211 -0
- package/dist/langgraph.js.map +1 -0
- package/dist/langgraph.mjs +206 -0
- package/dist/langgraph.mjs.map +1 -0
- package/dist/v2/index.d.ts +2 -0
- package/dist/v2/index.js +22 -0
- package/dist/v2/index.js.map +1 -0
- package/dist/v2/index.mjs +5 -0
- package/dist/v2/index.mjs.map +1 -0
- package/jest.config.js +10 -0
- package/package.json +143 -0
- package/scripts/generate-gql-schema.ts +13 -0
- package/src/agents/langgraph/event-source.ts +329 -0
- package/src/agents/langgraph/events.ts +377 -0
- package/src/graphql/inputs/action.input.ts +16 -0
- package/src/graphql/inputs/agent-session.input.ts +13 -0
- package/src/graphql/inputs/agent-state.input.ts +13 -0
- package/src/graphql/inputs/cloud-guardrails.input.ts +16 -0
- package/src/graphql/inputs/cloud.input.ts +8 -0
- package/src/graphql/inputs/context-property.input.ts +10 -0
- package/src/graphql/inputs/copilot-context.input.ts +10 -0
- package/src/graphql/inputs/custom-property.input.ts +15 -0
- package/src/graphql/inputs/extensions.input.ts +21 -0
- package/src/graphql/inputs/forwarded-parameters.input.ts +22 -0
- package/src/graphql/inputs/frontend.input.ts +14 -0
- package/src/graphql/inputs/generate-copilot-response.input.ts +59 -0
- package/src/graphql/inputs/load-agent-state.input.ts +10 -0
- package/src/graphql/inputs/message.input.ts +110 -0
- package/src/graphql/inputs/meta-event.input.ts +18 -0
- package/src/graphql/message-conversion/agui-to-gql.test.ts +1263 -0
- package/src/graphql/message-conversion/agui-to-gql.ts +333 -0
- package/src/graphql/message-conversion/gql-to-agui.test.ts +1580 -0
- package/src/graphql/message-conversion/gql-to-agui.ts +278 -0
- package/src/graphql/message-conversion/index.ts +2 -0
- package/src/graphql/message-conversion/roundtrip-conversion.test.ts +526 -0
- package/src/graphql/resolvers/copilot.resolver.ts +708 -0
- package/src/graphql/resolvers/state.resolver.ts +27 -0
- package/src/graphql/types/agents-response.type.ts +19 -0
- package/src/graphql/types/base/index.ts +10 -0
- package/src/graphql/types/converted/index.ts +176 -0
- package/src/graphql/types/copilot-response.type.ts +138 -0
- package/src/graphql/types/enums.ts +38 -0
- package/src/graphql/types/extensions-response.type.ts +23 -0
- package/src/graphql/types/guardrails-result.type.ts +20 -0
- package/src/graphql/types/load-agent-state-response.type.ts +17 -0
- package/src/graphql/types/message-status.type.ts +42 -0
- package/src/graphql/types/meta-events.type.ts +71 -0
- package/src/graphql/types/response-status.type.ts +66 -0
- package/src/index.ts +4 -0
- package/src/langgraph.ts +1 -0
- package/src/lib/cloud/index.ts +4 -0
- package/src/lib/error-messages.ts +200 -0
- package/src/lib/index.ts +52 -0
- package/src/lib/integrations/index.ts +6 -0
- package/src/lib/integrations/nest/index.ts +14 -0
- package/src/lib/integrations/nextjs/app-router.ts +38 -0
- package/src/lib/integrations/nextjs/pages-router.ts +39 -0
- package/src/lib/integrations/node-express/index.ts +14 -0
- package/src/lib/integrations/node-http/index.ts +143 -0
- package/src/lib/integrations/node-http/request-handler.ts +111 -0
- package/src/lib/integrations/shared.ts +161 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/observability.ts +160 -0
- package/src/lib/runtime/__tests__/copilot-runtime-error.test.ts +169 -0
- package/src/lib/runtime/__tests__/mcp-tools-utils.test.ts +464 -0
- package/src/lib/runtime/agent-integrations/langgraph/agent.ts +209 -0
- package/src/lib/runtime/agent-integrations/langgraph/consts.ts +34 -0
- package/src/lib/runtime/agent-integrations/langgraph/index.ts +2 -0
- package/src/lib/runtime/copilot-runtime.ts +710 -0
- package/src/lib/runtime/mcp-tools-utils.ts +254 -0
- package/src/lib/runtime/retry-utils.ts +96 -0
- package/src/lib/runtime/telemetry-agent-runner.ts +139 -0
- package/src/lib/runtime/types.ts +49 -0
- package/src/lib/runtime/utils.ts +87 -0
- package/src/lib/streaming.ts +202 -0
- package/src/lib/telemetry-client.ts +64 -0
- package/src/service-adapters/anthropic/anthropic-adapter.ts +452 -0
- package/src/service-adapters/anthropic/utils.ts +152 -0
- package/src/service-adapters/bedrock/bedrock-adapter.ts +73 -0
- package/src/service-adapters/conversion.ts +67 -0
- package/src/service-adapters/empty/empty-adapter.ts +38 -0
- package/src/service-adapters/events.ts +294 -0
- package/src/service-adapters/experimental/ollama/ollama-adapter.ts +84 -0
- package/src/service-adapters/google/google-genai-adapter.test.ts +104 -0
- package/src/service-adapters/google/google-genai-adapter.ts +88 -0
- package/src/service-adapters/groq/groq-adapter.ts +203 -0
- package/src/service-adapters/index.ts +18 -0
- package/src/service-adapters/langchain/langchain-adapter.ts +111 -0
- package/src/service-adapters/langchain/langserve.ts +88 -0
- package/src/service-adapters/langchain/types.ts +14 -0
- package/src/service-adapters/langchain/utils.ts +313 -0
- package/src/service-adapters/openai/openai-adapter.ts +283 -0
- package/src/service-adapters/openai/openai-assistant-adapter.ts +344 -0
- package/src/service-adapters/openai/utils.ts +199 -0
- package/src/service-adapters/service-adapter.ts +41 -0
- package/src/service-adapters/shared/error-utils.ts +61 -0
- package/src/service-adapters/shared/index.ts +1 -0
- package/src/service-adapters/unify/unify-adapter.ts +151 -0
- package/src/utils/failed-response-status-reasons.ts +70 -0
- package/src/utils/index.ts +1 -0
- package/src/v2/index.ts +3 -0
- package/tests/global.d.ts +13 -0
- package/tests/service-adapters/anthropic/allowlist-approach.test.ts +226 -0
- package/tests/service-adapters/anthropic/anthropic-adapter.test.ts +389 -0
- package/tests/service-adapters/openai/allowlist-approach.test.ts +238 -0
- package/tests/service-adapters/openai/openai-adapter.test.ts +301 -0
- package/tests/setup.jest.ts +21 -0
- package/tests/tsconfig.json +10 -0
- package/tsconfig.json +13 -0
- package/tsup.config.ts +20 -0
- package/typedoc.json +4 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot Runtime adapter for the OpenAI Assistant API.
|
|
3
|
+
*
|
|
4
|
+
* ## Example
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { CopilotRuntime, OpenAIAssistantAdapter } 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>",
|
|
14
|
+
* apiKey: "<your-api-key>",
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* return new OpenAIAssistantAdapter({
|
|
18
|
+
* openai,
|
|
19
|
+
* assistantId: "<your-assistant-id>",
|
|
20
|
+
* codeInterpreterEnabled: true,
|
|
21
|
+
* fileSearchEnabled: true,
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
import type OpenAI from "openai";
|
|
26
|
+
import type { RunSubmitToolOutputsStreamParams } from "openai/resources/beta/threads/runs/runs";
|
|
27
|
+
import type { AssistantStream } from "openai/lib/AssistantStream";
|
|
28
|
+
import type { AssistantStreamEvent, AssistantTool } from "openai/resources/beta/assistants";
|
|
29
|
+
import {
|
|
30
|
+
CopilotServiceAdapter,
|
|
31
|
+
CopilotRuntimeChatCompletionRequest,
|
|
32
|
+
CopilotRuntimeChatCompletionResponse,
|
|
33
|
+
} from "../service-adapter";
|
|
34
|
+
import { Message, ResultMessage, TextMessage } from "../../graphql/types/converted";
|
|
35
|
+
import {
|
|
36
|
+
convertActionInputToOpenAITool,
|
|
37
|
+
convertMessageToOpenAIMessage,
|
|
38
|
+
convertSystemMessageToAssistantAPI,
|
|
39
|
+
} from "./utils";
|
|
40
|
+
import { RuntimeEventSource } from "../events";
|
|
41
|
+
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
42
|
+
import { ForwardedParametersInput } from "../../graphql/inputs/forwarded-parameters.input";
|
|
43
|
+
|
|
44
|
+
export interface OpenAIAssistantAdapterParams {
|
|
45
|
+
/**
|
|
46
|
+
* The ID of the assistant to use.
|
|
47
|
+
*/
|
|
48
|
+
assistantId: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* An optional OpenAI instance to use. If not provided, a new instance will be created.
|
|
52
|
+
*/
|
|
53
|
+
openai?: OpenAI;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether to enable code interpretation.
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
codeInterpreterEnabled?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether to enable file search.
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
fileSearchEnabled?: boolean;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether to disable parallel tool calls.
|
|
69
|
+
* You can disable parallel tool calls to force the model to execute tool calls sequentially.
|
|
70
|
+
* This is useful if you want to execute tool calls in a specific order so that the state changes
|
|
71
|
+
* introduced by one tool call are visible to the next tool call. (i.e. new actions or readables)
|
|
72
|
+
*
|
|
73
|
+
* @default false
|
|
74
|
+
*/
|
|
75
|
+
disableParallelToolCalls?: boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether to keep the role in system messages as "System".
|
|
79
|
+
* By default, it is converted to "developer", which is used by newer OpenAI models
|
|
80
|
+
*
|
|
81
|
+
* @default false
|
|
82
|
+
*/
|
|
83
|
+
keepSystemRole?: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class OpenAIAssistantAdapter implements CopilotServiceAdapter {
|
|
87
|
+
private _openai: OpenAI;
|
|
88
|
+
private codeInterpreterEnabled: boolean;
|
|
89
|
+
private assistantId: string;
|
|
90
|
+
private fileSearchEnabled: boolean;
|
|
91
|
+
private disableParallelToolCalls: boolean;
|
|
92
|
+
private keepSystemRole: boolean = false;
|
|
93
|
+
|
|
94
|
+
public get name() {
|
|
95
|
+
return "OpenAIAssistantAdapter";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
constructor(params: OpenAIAssistantAdapterParams) {
|
|
99
|
+
if (params.openai) {
|
|
100
|
+
this._openai = params.openai;
|
|
101
|
+
}
|
|
102
|
+
// If no instance provided, we'll lazy-load in ensureOpenAI()
|
|
103
|
+
this.codeInterpreterEnabled = params.codeInterpreterEnabled === false || true;
|
|
104
|
+
this.fileSearchEnabled = params.fileSearchEnabled === false || true;
|
|
105
|
+
this.assistantId = params.assistantId;
|
|
106
|
+
this.disableParallelToolCalls = params?.disableParallelToolCalls || false;
|
|
107
|
+
this.keepSystemRole = params?.keepSystemRole ?? false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private ensureOpenAI(): OpenAI {
|
|
111
|
+
if (!this._openai) {
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
113
|
+
const OpenAI = require("openai").default;
|
|
114
|
+
this._openai = new OpenAI({});
|
|
115
|
+
}
|
|
116
|
+
return this._openai;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async process(
|
|
120
|
+
request: CopilotRuntimeChatCompletionRequest,
|
|
121
|
+
): Promise<CopilotRuntimeChatCompletionResponse> {
|
|
122
|
+
const { messages, actions, eventSource, runId, forwardedParameters } = request;
|
|
123
|
+
|
|
124
|
+
// if we don't have a threadId, create a new thread
|
|
125
|
+
let threadId = request.extensions?.openaiAssistantAPI?.threadId;
|
|
126
|
+
const openai = this.ensureOpenAI();
|
|
127
|
+
|
|
128
|
+
if (!threadId) {
|
|
129
|
+
threadId = (await openai.beta.threads.create()).id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lastMessage = messages.at(-1);
|
|
133
|
+
|
|
134
|
+
let nextRunId: string | undefined = undefined;
|
|
135
|
+
|
|
136
|
+
// submit function outputs
|
|
137
|
+
if (lastMessage.isResultMessage() && runId) {
|
|
138
|
+
nextRunId = await this.submitToolOutputs(threadId, runId, messages, eventSource);
|
|
139
|
+
}
|
|
140
|
+
// submit user message
|
|
141
|
+
else if (lastMessage.isTextMessage()) {
|
|
142
|
+
nextRunId = await this.submitUserMessage(
|
|
143
|
+
threadId,
|
|
144
|
+
messages,
|
|
145
|
+
actions,
|
|
146
|
+
eventSource,
|
|
147
|
+
forwardedParameters,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
// unsupported message
|
|
151
|
+
else {
|
|
152
|
+
throw new Error("No actionable message found in the messages");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
runId: nextRunId,
|
|
157
|
+
threadId,
|
|
158
|
+
extensions: {
|
|
159
|
+
...request.extensions,
|
|
160
|
+
openaiAssistantAPI: {
|
|
161
|
+
threadId: threadId,
|
|
162
|
+
runId: nextRunId,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async submitToolOutputs(
|
|
169
|
+
threadId: string,
|
|
170
|
+
runId: string,
|
|
171
|
+
messages: Message[],
|
|
172
|
+
eventSource: RuntimeEventSource,
|
|
173
|
+
) {
|
|
174
|
+
const openai = this.ensureOpenAI();
|
|
175
|
+
let run = await openai.beta.threads.runs.retrieve(threadId, runId);
|
|
176
|
+
|
|
177
|
+
if (!run.required_action) {
|
|
178
|
+
throw new Error("No tool outputs required");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// get the required tool call ids
|
|
182
|
+
const toolCallsIds = run.required_action.submit_tool_outputs.tool_calls.map(
|
|
183
|
+
(toolCall) => toolCall.id,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// search for these tool calls
|
|
187
|
+
const resultMessages = messages.filter(
|
|
188
|
+
(message) => message.isResultMessage() && toolCallsIds.includes(message.actionExecutionId),
|
|
189
|
+
) as ResultMessage[];
|
|
190
|
+
|
|
191
|
+
if (toolCallsIds.length != resultMessages.length) {
|
|
192
|
+
throw new Error("Number of function results does not match the number of tool calls");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// submit the tool outputs
|
|
196
|
+
const toolOutputs: RunSubmitToolOutputsStreamParams.ToolOutput[] = resultMessages.map(
|
|
197
|
+
(message) => {
|
|
198
|
+
return {
|
|
199
|
+
tool_call_id: message.actionExecutionId,
|
|
200
|
+
output: message.result,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const stream = openai.beta.threads.runs.submitToolOutputsStream(threadId, runId, {
|
|
206
|
+
tool_outputs: toolOutputs,
|
|
207
|
+
...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await this.streamResponse(stream, eventSource);
|
|
211
|
+
return runId;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async submitUserMessage(
|
|
215
|
+
threadId: string,
|
|
216
|
+
messages: Message[],
|
|
217
|
+
actions: ActionInput[],
|
|
218
|
+
eventSource: RuntimeEventSource,
|
|
219
|
+
forwardedParameters: ForwardedParametersInput,
|
|
220
|
+
) {
|
|
221
|
+
const openai = this.ensureOpenAI();
|
|
222
|
+
messages = [...messages];
|
|
223
|
+
|
|
224
|
+
// get the instruction message
|
|
225
|
+
const instructionsMessage = messages.shift();
|
|
226
|
+
const instructions = instructionsMessage.isTextMessage() ? instructionsMessage.content : "";
|
|
227
|
+
|
|
228
|
+
// get the latest user message
|
|
229
|
+
const userMessage = messages
|
|
230
|
+
.map((m) => convertMessageToOpenAIMessage(m, { keepSystemRole: this.keepSystemRole }))
|
|
231
|
+
.map(convertSystemMessageToAssistantAPI)
|
|
232
|
+
.at(-1);
|
|
233
|
+
|
|
234
|
+
if (userMessage.role !== "user") {
|
|
235
|
+
throw new Error("No user message found");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
await openai.beta.threads.messages.create(threadId, {
|
|
239
|
+
role: "user",
|
|
240
|
+
content: userMessage.content,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const openaiTools = actions.map(convertActionInputToOpenAITool);
|
|
244
|
+
|
|
245
|
+
const tools = [
|
|
246
|
+
...openaiTools,
|
|
247
|
+
...(this.codeInterpreterEnabled ? [{ type: "code_interpreter" } as AssistantTool] : []),
|
|
248
|
+
...(this.fileSearchEnabled ? [{ type: "file_search" } as AssistantTool] : []),
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
let stream = openai.beta.threads.runs.stream(threadId, {
|
|
252
|
+
assistant_id: this.assistantId,
|
|
253
|
+
instructions,
|
|
254
|
+
tools: tools,
|
|
255
|
+
...(forwardedParameters?.maxTokens && {
|
|
256
|
+
max_completion_tokens: forwardedParameters.maxTokens,
|
|
257
|
+
}),
|
|
258
|
+
...(this.disableParallelToolCalls && { parallel_tool_calls: false }),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await this.streamResponse(stream, eventSource);
|
|
262
|
+
|
|
263
|
+
return getRunIdFromStream(stream);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private async streamResponse(stream: AssistantStream, eventSource: RuntimeEventSource) {
|
|
267
|
+
eventSource.stream(async (eventStream$) => {
|
|
268
|
+
let inFunctionCall = false;
|
|
269
|
+
let currentMessageId: string;
|
|
270
|
+
let currentToolCallId: string;
|
|
271
|
+
|
|
272
|
+
for await (const chunk of stream) {
|
|
273
|
+
switch (chunk.event) {
|
|
274
|
+
case "thread.message.created":
|
|
275
|
+
if (inFunctionCall) {
|
|
276
|
+
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
277
|
+
}
|
|
278
|
+
currentMessageId = chunk.data.id;
|
|
279
|
+
eventStream$.sendTextMessageStart({ messageId: currentMessageId });
|
|
280
|
+
break;
|
|
281
|
+
case "thread.message.delta":
|
|
282
|
+
if (chunk.data.delta.content?.[0].type === "text") {
|
|
283
|
+
eventStream$.sendTextMessageContent({
|
|
284
|
+
messageId: currentMessageId,
|
|
285
|
+
content: chunk.data.delta.content?.[0].text.value,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
case "thread.message.completed":
|
|
290
|
+
eventStream$.sendTextMessageEnd({ messageId: currentMessageId });
|
|
291
|
+
break;
|
|
292
|
+
case "thread.run.step.delta":
|
|
293
|
+
let toolCallId: string | undefined;
|
|
294
|
+
let toolCallName: string | undefined;
|
|
295
|
+
let toolCallArgs: string | undefined;
|
|
296
|
+
if (
|
|
297
|
+
chunk.data.delta.step_details.type === "tool_calls" &&
|
|
298
|
+
chunk.data.delta.step_details.tool_calls?.[0].type === "function"
|
|
299
|
+
) {
|
|
300
|
+
toolCallId = chunk.data.delta.step_details.tool_calls?.[0].id;
|
|
301
|
+
toolCallName = chunk.data.delta.step_details.tool_calls?.[0].function.name;
|
|
302
|
+
toolCallArgs = chunk.data.delta.step_details.tool_calls?.[0].function.arguments;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (toolCallName && toolCallId) {
|
|
306
|
+
if (inFunctionCall) {
|
|
307
|
+
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
308
|
+
}
|
|
309
|
+
inFunctionCall = true;
|
|
310
|
+
currentToolCallId = toolCallId;
|
|
311
|
+
eventStream$.sendActionExecutionStart({
|
|
312
|
+
actionExecutionId: currentToolCallId,
|
|
313
|
+
parentMessageId: chunk.data.id,
|
|
314
|
+
actionName: toolCallName,
|
|
315
|
+
});
|
|
316
|
+
} else if (toolCallArgs) {
|
|
317
|
+
eventStream$.sendActionExecutionArgs({
|
|
318
|
+
actionExecutionId: currentToolCallId,
|
|
319
|
+
args: toolCallArgs,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (inFunctionCall) {
|
|
326
|
+
eventStream$.sendActionExecutionEnd({ actionExecutionId: currentToolCallId });
|
|
327
|
+
}
|
|
328
|
+
eventStream$.complete();
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function getRunIdFromStream(stream: AssistantStream): Promise<string> {
|
|
334
|
+
return new Promise<string>((resolve, reject) => {
|
|
335
|
+
let runIdGetter = (event: AssistantStreamEvent) => {
|
|
336
|
+
if (event.event === "thread.run.created") {
|
|
337
|
+
const runId = event.data.id;
|
|
338
|
+
stream.off("event", runIdGetter);
|
|
339
|
+
resolve(runId);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
stream.on("event", runIdGetter);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Message } from "../../graphql/types/converted";
|
|
2
|
+
import { ActionInput } from "../../graphql/inputs/action.input";
|
|
3
|
+
import {
|
|
4
|
+
ChatCompletionAssistantMessageParam,
|
|
5
|
+
ChatCompletionMessageParam,
|
|
6
|
+
ChatCompletionSystemMessageParam,
|
|
7
|
+
ChatCompletionTool,
|
|
8
|
+
ChatCompletionUserMessageParam,
|
|
9
|
+
ChatCompletionDeveloperMessageParam,
|
|
10
|
+
} from "openai/resources/chat";
|
|
11
|
+
import { parseJson } from "@copilotkit/shared";
|
|
12
|
+
|
|
13
|
+
export function limitMessagesToTokenCount(
|
|
14
|
+
messages: any[],
|
|
15
|
+
tools: any[],
|
|
16
|
+
model: string,
|
|
17
|
+
maxTokens?: number,
|
|
18
|
+
): any[] {
|
|
19
|
+
maxTokens ||= maxTokensForOpenAIModel(model);
|
|
20
|
+
|
|
21
|
+
const result: any[] = [];
|
|
22
|
+
const toolsNumTokens = countToolsTokens(model, tools);
|
|
23
|
+
if (toolsNumTokens > maxTokens) {
|
|
24
|
+
throw new Error(`Too many tokens in function definitions: ${toolsNumTokens} > ${maxTokens}`);
|
|
25
|
+
}
|
|
26
|
+
maxTokens -= toolsNumTokens;
|
|
27
|
+
|
|
28
|
+
for (const message of messages) {
|
|
29
|
+
if (["system", "developer"].includes(message.role)) {
|
|
30
|
+
const numTokens = countMessageTokens(model, message);
|
|
31
|
+
maxTokens -= numTokens;
|
|
32
|
+
|
|
33
|
+
if (maxTokens < 0) {
|
|
34
|
+
throw new Error("Not enough tokens for system message.");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let cutoff: boolean = false;
|
|
40
|
+
|
|
41
|
+
const reversedMessages = [...messages].reverse();
|
|
42
|
+
for (const message of reversedMessages) {
|
|
43
|
+
if (["system", "developer"].includes(message.role)) {
|
|
44
|
+
result.unshift(message);
|
|
45
|
+
continue;
|
|
46
|
+
} else if (cutoff) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let numTokens = countMessageTokens(model, message);
|
|
50
|
+
if (maxTokens < numTokens) {
|
|
51
|
+
cutoff = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
result.unshift(message);
|
|
55
|
+
maxTokens -= numTokens;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function maxTokensForOpenAIModel(model: string): number {
|
|
62
|
+
return maxTokensByModel[model] || DEFAULT_MAX_TOKENS;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const DEFAULT_MAX_TOKENS = 128000;
|
|
66
|
+
|
|
67
|
+
const maxTokensByModel: { [key: string]: number } = {
|
|
68
|
+
// o1
|
|
69
|
+
o1: 200000,
|
|
70
|
+
"o1-2024-12-17": 200000,
|
|
71
|
+
"o1-mini": 128000,
|
|
72
|
+
"o1-mini-2024-09-12": 128000,
|
|
73
|
+
"o1-preview": 128000,
|
|
74
|
+
"o1-preview-2024-09-12": 128000,
|
|
75
|
+
// o3-mini
|
|
76
|
+
"o3-mini": 200000,
|
|
77
|
+
"o3-mini-2025-01-31": 200000,
|
|
78
|
+
// GPT-4
|
|
79
|
+
"gpt-4o": 128000,
|
|
80
|
+
"chatgpt-4o-latest": 128000,
|
|
81
|
+
"gpt-4o-2024-08-06": 128000,
|
|
82
|
+
"gpt-4o-2024-05-13": 128000,
|
|
83
|
+
"gpt-4o-mini": 128000,
|
|
84
|
+
"gpt-4o-mini-2024-07-18": 128000,
|
|
85
|
+
"gpt-4-turbo": 128000,
|
|
86
|
+
"gpt-4-turbo-2024-04-09": 128000,
|
|
87
|
+
"gpt-4-0125-preview": 128000,
|
|
88
|
+
"gpt-4-turbo-preview": 128000,
|
|
89
|
+
"gpt-4-1106-preview": 128000,
|
|
90
|
+
"gpt-4-vision-preview": 128000,
|
|
91
|
+
"gpt-4-1106-vision-preview": 128000,
|
|
92
|
+
"gpt-4-32k": 32768,
|
|
93
|
+
"gpt-4-32k-0613": 32768,
|
|
94
|
+
"gpt-4-32k-0314": 32768,
|
|
95
|
+
"gpt-4": 8192,
|
|
96
|
+
"gpt-4-0613": 8192,
|
|
97
|
+
"gpt-4-0314": 8192,
|
|
98
|
+
|
|
99
|
+
// GPT-3.5
|
|
100
|
+
"gpt-3.5-turbo-0125": 16385,
|
|
101
|
+
"gpt-3.5-turbo": 16385,
|
|
102
|
+
"gpt-3.5-turbo-1106": 16385,
|
|
103
|
+
"gpt-3.5-turbo-instruct": 4096,
|
|
104
|
+
"gpt-3.5-turbo-16k": 16385,
|
|
105
|
+
"gpt-3.5-turbo-0613": 4096,
|
|
106
|
+
"gpt-3.5-turbo-16k-0613": 16385,
|
|
107
|
+
"gpt-3.5-turbo-0301": 4097,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
function countToolsTokens(model: string, tools: any[]): number {
|
|
111
|
+
if (tools.length === 0) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
const json = JSON.stringify(tools);
|
|
115
|
+
return countTokens(model, json);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function countMessageTokens(model: string, message: any): number {
|
|
119
|
+
return countTokens(model, message.content || "");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function countTokens(model: string, text: string): number {
|
|
123
|
+
return text.length / 3;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function convertActionInputToOpenAITool(action: ActionInput): ChatCompletionTool {
|
|
127
|
+
return {
|
|
128
|
+
type: "function",
|
|
129
|
+
function: {
|
|
130
|
+
name: action.name,
|
|
131
|
+
description: action.description,
|
|
132
|
+
parameters: parseJson(action.jsonSchema, {}),
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
type UsedMessageParams =
|
|
138
|
+
| ChatCompletionUserMessageParam
|
|
139
|
+
| ChatCompletionAssistantMessageParam
|
|
140
|
+
| ChatCompletionDeveloperMessageParam
|
|
141
|
+
| ChatCompletionSystemMessageParam;
|
|
142
|
+
export function convertMessageToOpenAIMessage(
|
|
143
|
+
message: Message,
|
|
144
|
+
options?: { keepSystemRole: boolean },
|
|
145
|
+
): ChatCompletionMessageParam {
|
|
146
|
+
const { keepSystemRole } = options || { keepSystemRole: false };
|
|
147
|
+
if (message.isTextMessage()) {
|
|
148
|
+
let role = message.role as UsedMessageParams["role"];
|
|
149
|
+
if (message.role === "system" && !keepSystemRole) {
|
|
150
|
+
role = "developer";
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
role,
|
|
154
|
+
content: message.content,
|
|
155
|
+
} satisfies UsedMessageParams;
|
|
156
|
+
} else if (message.isImageMessage()) {
|
|
157
|
+
return {
|
|
158
|
+
role: "user",
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: "image_url",
|
|
162
|
+
image_url: {
|
|
163
|
+
url: `data:image/${message.format};base64,${message.bytes}`,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
} satisfies UsedMessageParams;
|
|
168
|
+
} else if (message.isActionExecutionMessage()) {
|
|
169
|
+
return {
|
|
170
|
+
role: "assistant",
|
|
171
|
+
tool_calls: [
|
|
172
|
+
{
|
|
173
|
+
id: message.id,
|
|
174
|
+
type: "function",
|
|
175
|
+
function: {
|
|
176
|
+
name: message.name,
|
|
177
|
+
arguments: JSON.stringify(message.arguments),
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
} else if (message.isResultMessage()) {
|
|
183
|
+
return {
|
|
184
|
+
role: "tool",
|
|
185
|
+
content: message.result,
|
|
186
|
+
tool_call_id: message.actionExecutionId,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function convertSystemMessageToAssistantAPI(message: ChatCompletionMessageParam) {
|
|
192
|
+
return {
|
|
193
|
+
...message,
|
|
194
|
+
...(["system", "developer"].includes(message.role) && {
|
|
195
|
+
role: "assistant",
|
|
196
|
+
content: "THE FOLLOWING MESSAGE IS A SYSTEM MESSAGE: " + message.content,
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Message } from "../graphql/types/converted";
|
|
2
|
+
import { RuntimeEventSource } from "./events";
|
|
3
|
+
import { ActionInput } from "../graphql/inputs/action.input";
|
|
4
|
+
import { ForwardedParametersInput } from "../graphql/inputs/forwarded-parameters.input";
|
|
5
|
+
import { ExtensionsInput } from "../graphql/inputs/extensions.input";
|
|
6
|
+
import { ExtensionsResponse } from "../graphql/types/extensions-response.type";
|
|
7
|
+
import { AgentSessionInput } from "../graphql/inputs/agent-session.input";
|
|
8
|
+
import { AgentStateInput } from "../graphql/inputs/agent-state.input";
|
|
9
|
+
|
|
10
|
+
export interface CopilotKitResponse {
|
|
11
|
+
stream: ReadableStream;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CopilotRuntimeChatCompletionRequest {
|
|
16
|
+
eventSource: RuntimeEventSource;
|
|
17
|
+
messages: Message[];
|
|
18
|
+
actions: ActionInput[];
|
|
19
|
+
model?: string;
|
|
20
|
+
threadId?: string;
|
|
21
|
+
runId?: string;
|
|
22
|
+
forwardedParameters?: ForwardedParametersInput;
|
|
23
|
+
extensions?: ExtensionsInput;
|
|
24
|
+
agentSession?: AgentSessionInput;
|
|
25
|
+
agentStates?: AgentStateInput[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CopilotRuntimeChatCompletionResponse {
|
|
29
|
+
threadId: string;
|
|
30
|
+
runId?: string;
|
|
31
|
+
extensions?: ExtensionsResponse;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface CopilotServiceAdapter {
|
|
35
|
+
provider?: string;
|
|
36
|
+
model?: string;
|
|
37
|
+
process(
|
|
38
|
+
request: CopilotRuntimeChatCompletionRequest,
|
|
39
|
+
): Promise<CopilotRuntimeChatCompletionResponse>;
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CopilotKitLowLevelError, CopilotKitErrorCode } from "@copilotkit/shared";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Converts service adapter errors to structured CopilotKitError format using HTTP status codes.
|
|
5
|
+
* This provides consistent error classification across all service adapters.
|
|
6
|
+
*/
|
|
7
|
+
export function convertServiceAdapterError(
|
|
8
|
+
error: any,
|
|
9
|
+
adapterName: string,
|
|
10
|
+
): CopilotKitLowLevelError {
|
|
11
|
+
const errorName = error?.constructor?.name || error.name;
|
|
12
|
+
const errorMessage = error?.message || String(error);
|
|
13
|
+
const statusCode = error.status || error.statusCode || error.response?.status;
|
|
14
|
+
const responseData = error.error || error.response?.data || error.data;
|
|
15
|
+
|
|
16
|
+
// Create the base error with the constructor signature
|
|
17
|
+
const structuredError = new CopilotKitLowLevelError({
|
|
18
|
+
error: error instanceof Error ? error : new Error(errorMessage),
|
|
19
|
+
url: `${adapterName} service adapter`,
|
|
20
|
+
message: `${adapterName} API error: ${errorMessage}`,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Add additional properties after construction
|
|
24
|
+
if (statusCode) {
|
|
25
|
+
(structuredError as any).statusCode = statusCode;
|
|
26
|
+
}
|
|
27
|
+
if (responseData) {
|
|
28
|
+
(structuredError as any).responseData = responseData;
|
|
29
|
+
}
|
|
30
|
+
if (errorName) {
|
|
31
|
+
(structuredError as any).originalErrorType = errorName;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Classify error based on HTTP status codes (reliable and provider-agnostic)
|
|
35
|
+
let newCode: CopilotKitErrorCode;
|
|
36
|
+
|
|
37
|
+
if (statusCode === 401) {
|
|
38
|
+
// 401 = Authentication/API key issues
|
|
39
|
+
newCode = CopilotKitErrorCode.AUTHENTICATION_ERROR;
|
|
40
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
41
|
+
// 4xx = Client errors (bad request, invalid params, etc.) - these are configuration issues
|
|
42
|
+
newCode = CopilotKitErrorCode.CONFIGURATION_ERROR;
|
|
43
|
+
} else if (statusCode >= 500) {
|
|
44
|
+
// 5xx = Server errors - keep as NETWORK_ERROR since it's infrastructure related
|
|
45
|
+
newCode = CopilotKitErrorCode.NETWORK_ERROR;
|
|
46
|
+
} else if (statusCode) {
|
|
47
|
+
// Any other HTTP status with an error - likely configuration
|
|
48
|
+
newCode = CopilotKitErrorCode.CONFIGURATION_ERROR;
|
|
49
|
+
} else {
|
|
50
|
+
// No status code - likely a genuine network/connection error
|
|
51
|
+
newCode = CopilotKitErrorCode.NETWORK_ERROR;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Update both the instance property and the extensions
|
|
55
|
+
(structuredError as any).code = newCode;
|
|
56
|
+
if ((structuredError as any).extensions) {
|
|
57
|
+
(structuredError as any).extensions.code = newCode;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return structuredError;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./error-utils";
|