@langchain/openai 0.4.1 → 0.4.2

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.
@@ -50,138 +50,6 @@ function messageToOpenAIRole(message) {
50
50
  }
51
51
  }
52
52
  exports.messageToOpenAIRole = messageToOpenAIRole;
53
- function openAIResponseToChatMessage(message, rawResponse, includeRawResponse) {
54
- const rawToolCalls = message.tool_calls;
55
- switch (message.role) {
56
- case "assistant": {
57
- const toolCalls = [];
58
- const invalidToolCalls = [];
59
- for (const rawToolCall of rawToolCalls ?? []) {
60
- try {
61
- toolCalls.push((0, openai_tools_1.parseToolCall)(rawToolCall, { returnId: true }));
62
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
- }
64
- catch (e) {
65
- invalidToolCalls.push((0, openai_tools_1.makeInvalidToolCall)(rawToolCall, e.message));
66
- }
67
- }
68
- const additional_kwargs = {
69
- function_call: message.function_call,
70
- tool_calls: rawToolCalls,
71
- };
72
- if (includeRawResponse !== undefined) {
73
- additional_kwargs.__raw_response = rawResponse;
74
- }
75
- const response_metadata = {
76
- model_name: rawResponse.model,
77
- ...(rawResponse.system_fingerprint
78
- ? {
79
- usage: { ...rawResponse.usage },
80
- system_fingerprint: rawResponse.system_fingerprint,
81
- }
82
- : {}),
83
- };
84
- if (message.audio) {
85
- additional_kwargs.audio = message.audio;
86
- }
87
- return new messages_1.AIMessage({
88
- content: message.content || "",
89
- tool_calls: toolCalls,
90
- invalid_tool_calls: invalidToolCalls,
91
- additional_kwargs,
92
- response_metadata,
93
- id: rawResponse.id,
94
- });
95
- }
96
- default:
97
- return new messages_1.ChatMessage(message.content || "", message.role ?? "unknown");
98
- }
99
- }
100
- function _convertDeltaToMessageChunk(
101
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
- delta, rawResponse, defaultRole, includeRawResponse) {
103
- const role = delta.role ?? defaultRole;
104
- const content = delta.content ?? "";
105
- let additional_kwargs;
106
- if (delta.function_call) {
107
- additional_kwargs = {
108
- function_call: delta.function_call,
109
- };
110
- }
111
- else if (delta.tool_calls) {
112
- additional_kwargs = {
113
- tool_calls: delta.tool_calls,
114
- };
115
- }
116
- else {
117
- additional_kwargs = {};
118
- }
119
- if (includeRawResponse) {
120
- additional_kwargs.__raw_response = rawResponse;
121
- }
122
- if (delta.audio) {
123
- additional_kwargs.audio = {
124
- ...delta.audio,
125
- index: rawResponse.choices[0].index,
126
- };
127
- }
128
- const response_metadata = { usage: { ...rawResponse.usage } };
129
- if (role === "user") {
130
- return new messages_1.HumanMessageChunk({ content, response_metadata });
131
- }
132
- else if (role === "assistant") {
133
- const toolCallChunks = [];
134
- if (Array.isArray(delta.tool_calls)) {
135
- for (const rawToolCall of delta.tool_calls) {
136
- toolCallChunks.push({
137
- name: rawToolCall.function?.name,
138
- args: rawToolCall.function?.arguments,
139
- id: rawToolCall.id,
140
- index: rawToolCall.index,
141
- type: "tool_call_chunk",
142
- });
143
- }
144
- }
145
- return new messages_1.AIMessageChunk({
146
- content,
147
- tool_call_chunks: toolCallChunks,
148
- additional_kwargs,
149
- id: rawResponse.id,
150
- response_metadata,
151
- });
152
- }
153
- else if (role === "system") {
154
- return new messages_1.SystemMessageChunk({ content, response_metadata });
155
- }
156
- else if (role === "developer") {
157
- return new messages_1.SystemMessageChunk({
158
- content,
159
- response_metadata,
160
- additional_kwargs: {
161
- __openai_role__: "developer",
162
- },
163
- });
164
- }
165
- else if (role === "function") {
166
- return new messages_1.FunctionMessageChunk({
167
- content,
168
- additional_kwargs,
169
- name: delta.name,
170
- response_metadata,
171
- });
172
- }
173
- else if (role === "tool") {
174
- return new messages_1.ToolMessageChunk({
175
- content,
176
- additional_kwargs,
177
- tool_call_id: delta.tool_call_id,
178
- response_metadata,
179
- });
180
- }
181
- else {
182
- return new messages_1.ChatMessageChunk({ content, role, response_metadata });
183
- }
184
- }
185
53
  // Used in LangSmith, export is important here
186
54
  function _convertMessagesToOpenAIParams(messages, model) {
187
55
  // TODO: Function messages do not support array content, fix cast
@@ -1136,6 +1004,138 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1136
1004
  }
1137
1005
  return params;
1138
1006
  }
1007
+ _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
1008
+ const rawToolCalls = message.tool_calls;
1009
+ switch (message.role) {
1010
+ case "assistant": {
1011
+ const toolCalls = [];
1012
+ const invalidToolCalls = [];
1013
+ for (const rawToolCall of rawToolCalls ?? []) {
1014
+ try {
1015
+ toolCalls.push((0, openai_tools_1.parseToolCall)(rawToolCall, { returnId: true }));
1016
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1017
+ }
1018
+ catch (e) {
1019
+ invalidToolCalls.push((0, openai_tools_1.makeInvalidToolCall)(rawToolCall, e.message));
1020
+ }
1021
+ }
1022
+ const additional_kwargs = {
1023
+ function_call: message.function_call,
1024
+ tool_calls: rawToolCalls,
1025
+ };
1026
+ if (this.__includeRawResponse !== undefined) {
1027
+ additional_kwargs.__raw_response = rawResponse;
1028
+ }
1029
+ const response_metadata = {
1030
+ model_name: rawResponse.model,
1031
+ ...(rawResponse.system_fingerprint
1032
+ ? {
1033
+ usage: { ...rawResponse.usage },
1034
+ system_fingerprint: rawResponse.system_fingerprint,
1035
+ }
1036
+ : {}),
1037
+ };
1038
+ if (message.audio) {
1039
+ additional_kwargs.audio = message.audio;
1040
+ }
1041
+ return new messages_1.AIMessage({
1042
+ content: message.content || "",
1043
+ tool_calls: toolCalls,
1044
+ invalid_tool_calls: invalidToolCalls,
1045
+ additional_kwargs,
1046
+ response_metadata,
1047
+ id: rawResponse.id,
1048
+ });
1049
+ }
1050
+ default:
1051
+ return new messages_1.ChatMessage(message.content || "", message.role ?? "unknown");
1052
+ }
1053
+ }
1054
+ _convertOpenAIDeltaToBaseMessageChunk(
1055
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1056
+ delta, rawResponse, defaultRole) {
1057
+ const role = delta.role ?? defaultRole;
1058
+ const content = delta.content ?? "";
1059
+ let additional_kwargs;
1060
+ if (delta.function_call) {
1061
+ additional_kwargs = {
1062
+ function_call: delta.function_call,
1063
+ };
1064
+ }
1065
+ else if (delta.tool_calls) {
1066
+ additional_kwargs = {
1067
+ tool_calls: delta.tool_calls,
1068
+ };
1069
+ }
1070
+ else {
1071
+ additional_kwargs = {};
1072
+ }
1073
+ if (this.__includeRawResponse) {
1074
+ additional_kwargs.__raw_response = rawResponse;
1075
+ }
1076
+ if (delta.audio) {
1077
+ additional_kwargs.audio = {
1078
+ ...delta.audio,
1079
+ index: rawResponse.choices[0].index,
1080
+ };
1081
+ }
1082
+ const response_metadata = { usage: { ...rawResponse.usage } };
1083
+ if (role === "user") {
1084
+ return new messages_1.HumanMessageChunk({ content, response_metadata });
1085
+ }
1086
+ else if (role === "assistant") {
1087
+ const toolCallChunks = [];
1088
+ if (Array.isArray(delta.tool_calls)) {
1089
+ for (const rawToolCall of delta.tool_calls) {
1090
+ toolCallChunks.push({
1091
+ name: rawToolCall.function?.name,
1092
+ args: rawToolCall.function?.arguments,
1093
+ id: rawToolCall.id,
1094
+ index: rawToolCall.index,
1095
+ type: "tool_call_chunk",
1096
+ });
1097
+ }
1098
+ }
1099
+ return new messages_1.AIMessageChunk({
1100
+ content,
1101
+ tool_call_chunks: toolCallChunks,
1102
+ additional_kwargs,
1103
+ id: rawResponse.id,
1104
+ response_metadata,
1105
+ });
1106
+ }
1107
+ else if (role === "system") {
1108
+ return new messages_1.SystemMessageChunk({ content, response_metadata });
1109
+ }
1110
+ else if (role === "developer") {
1111
+ return new messages_1.SystemMessageChunk({
1112
+ content,
1113
+ response_metadata,
1114
+ additional_kwargs: {
1115
+ __openai_role__: "developer",
1116
+ },
1117
+ });
1118
+ }
1119
+ else if (role === "function") {
1120
+ return new messages_1.FunctionMessageChunk({
1121
+ content,
1122
+ additional_kwargs,
1123
+ name: delta.name,
1124
+ response_metadata,
1125
+ });
1126
+ }
1127
+ else if (role === "tool") {
1128
+ return new messages_1.ToolMessageChunk({
1129
+ content,
1130
+ additional_kwargs,
1131
+ tool_call_id: delta.tool_call_id,
1132
+ response_metadata,
1133
+ });
1134
+ }
1135
+ else {
1136
+ return new messages_1.ChatMessageChunk({ content, role, response_metadata });
1137
+ }
1138
+ }
1139
1139
  /** @ignore */
1140
1140
  _identifyingParams() {
1141
1141
  return {
@@ -1168,7 +1168,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1168
1168
  if (!delta) {
1169
1169
  continue;
1170
1170
  }
1171
- const chunk = _convertDeltaToMessageChunk(delta, data, defaultRole, this.__includeRawResponse);
1171
+ const chunk = this._convertOpenAIDeltaToBaseMessageChunk(delta, data, defaultRole);
1172
1172
  defaultRole = delta.role ?? defaultRole;
1173
1173
  const newTokenIndices = {
1174
1174
  prompt: options.promptIndex ?? 0,
@@ -1354,7 +1354,7 @@ class ChatOpenAI extends chat_models_1.BaseChatModel {
1354
1354
  const text = part.message?.content ?? "";
1355
1355
  const generation = {
1356
1356
  text,
1357
- message: openAIResponseToChatMessage(part.message ?? { role: "assistant" }, data, this.__includeRawResponse),
1357
+ message: this._convertOpenAIChatCompletionMessageToBaseMessage(part.message ?? { role: "assistant" }, data),
1358
1358
  };
1359
1359
  generation.generationInfo = {
1360
1360
  ...(part.finish_reason ? { finish_reason: part.finish_reason } : {}),
@@ -1,6 +1,6 @@
1
1
  import { type ClientOptions, OpenAI as OpenAIClient } from "openai";
2
2
  import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager";
3
- import { AIMessageChunk, type BaseMessage } from "@langchain/core/messages";
3
+ import { AIMessageChunk, type BaseMessage, ChatMessageChunk, FunctionMessageChunk, HumanMessageChunk, SystemMessageChunk, ToolMessageChunk } from "@langchain/core/messages";
4
4
  import { ChatGenerationChunk, type ChatResult } from "@langchain/core/outputs";
5
5
  import { BaseChatModel, BindToolsInput, LangSmithParams, type BaseChatModelParams } from "@langchain/core/language_models/chat_models";
6
6
  import { type BaseFunctionCallOptions, type BaseLanguageModelInput, type StructuredOutputMethodOptions } from "@langchain/core/language_models/base";
@@ -698,6 +698,8 @@ export declare class ChatOpenAI<CallOptions extends ChatOpenAICallOptions = Chat
698
698
  invocationParams(options?: this["ParsedCallOptions"], extra?: {
699
699
  streaming?: boolean;
700
700
  }): Omit<OpenAIClient.Chat.ChatCompletionCreateParams, "messages">;
701
+ protected _convertOpenAIChatCompletionMessageToBaseMessage(message: OpenAIClient.Chat.Completions.ChatCompletionMessage, rawResponse: OpenAIClient.Chat.Completions.ChatCompletion): BaseMessage;
702
+ protected _convertOpenAIDeltaToBaseMessageChunk(delta: Record<string, any>, rawResponse: OpenAIClient.Chat.Completions.ChatCompletionChunk, defaultRole?: OpenAIRoleEnum): AIMessageChunk | HumanMessageChunk | SystemMessageChunk | FunctionMessageChunk | ToolMessageChunk | ChatMessageChunk;
701
703
  /** @ignore */
702
704
  _identifyingParams(): Omit<OpenAIClient.Chat.ChatCompletionCreateParams, "messages"> & {
703
705
  model_name: string;
@@ -46,138 +46,6 @@ export function messageToOpenAIRole(message) {
46
46
  throw new Error(`Unknown message type: ${type}`);
47
47
  }
48
48
  }
49
- function openAIResponseToChatMessage(message, rawResponse, includeRawResponse) {
50
- const rawToolCalls = message.tool_calls;
51
- switch (message.role) {
52
- case "assistant": {
53
- const toolCalls = [];
54
- const invalidToolCalls = [];
55
- for (const rawToolCall of rawToolCalls ?? []) {
56
- try {
57
- toolCalls.push(parseToolCall(rawToolCall, { returnId: true }));
58
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
59
- }
60
- catch (e) {
61
- invalidToolCalls.push(makeInvalidToolCall(rawToolCall, e.message));
62
- }
63
- }
64
- const additional_kwargs = {
65
- function_call: message.function_call,
66
- tool_calls: rawToolCalls,
67
- };
68
- if (includeRawResponse !== undefined) {
69
- additional_kwargs.__raw_response = rawResponse;
70
- }
71
- const response_metadata = {
72
- model_name: rawResponse.model,
73
- ...(rawResponse.system_fingerprint
74
- ? {
75
- usage: { ...rawResponse.usage },
76
- system_fingerprint: rawResponse.system_fingerprint,
77
- }
78
- : {}),
79
- };
80
- if (message.audio) {
81
- additional_kwargs.audio = message.audio;
82
- }
83
- return new AIMessage({
84
- content: message.content || "",
85
- tool_calls: toolCalls,
86
- invalid_tool_calls: invalidToolCalls,
87
- additional_kwargs,
88
- response_metadata,
89
- id: rawResponse.id,
90
- });
91
- }
92
- default:
93
- return new ChatMessage(message.content || "", message.role ?? "unknown");
94
- }
95
- }
96
- function _convertDeltaToMessageChunk(
97
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
- delta, rawResponse, defaultRole, includeRawResponse) {
99
- const role = delta.role ?? defaultRole;
100
- const content = delta.content ?? "";
101
- let additional_kwargs;
102
- if (delta.function_call) {
103
- additional_kwargs = {
104
- function_call: delta.function_call,
105
- };
106
- }
107
- else if (delta.tool_calls) {
108
- additional_kwargs = {
109
- tool_calls: delta.tool_calls,
110
- };
111
- }
112
- else {
113
- additional_kwargs = {};
114
- }
115
- if (includeRawResponse) {
116
- additional_kwargs.__raw_response = rawResponse;
117
- }
118
- if (delta.audio) {
119
- additional_kwargs.audio = {
120
- ...delta.audio,
121
- index: rawResponse.choices[0].index,
122
- };
123
- }
124
- const response_metadata = { usage: { ...rawResponse.usage } };
125
- if (role === "user") {
126
- return new HumanMessageChunk({ content, response_metadata });
127
- }
128
- else if (role === "assistant") {
129
- const toolCallChunks = [];
130
- if (Array.isArray(delta.tool_calls)) {
131
- for (const rawToolCall of delta.tool_calls) {
132
- toolCallChunks.push({
133
- name: rawToolCall.function?.name,
134
- args: rawToolCall.function?.arguments,
135
- id: rawToolCall.id,
136
- index: rawToolCall.index,
137
- type: "tool_call_chunk",
138
- });
139
- }
140
- }
141
- return new AIMessageChunk({
142
- content,
143
- tool_call_chunks: toolCallChunks,
144
- additional_kwargs,
145
- id: rawResponse.id,
146
- response_metadata,
147
- });
148
- }
149
- else if (role === "system") {
150
- return new SystemMessageChunk({ content, response_metadata });
151
- }
152
- else if (role === "developer") {
153
- return new SystemMessageChunk({
154
- content,
155
- response_metadata,
156
- additional_kwargs: {
157
- __openai_role__: "developer",
158
- },
159
- });
160
- }
161
- else if (role === "function") {
162
- return new FunctionMessageChunk({
163
- content,
164
- additional_kwargs,
165
- name: delta.name,
166
- response_metadata,
167
- });
168
- }
169
- else if (role === "tool") {
170
- return new ToolMessageChunk({
171
- content,
172
- additional_kwargs,
173
- tool_call_id: delta.tool_call_id,
174
- response_metadata,
175
- });
176
- }
177
- else {
178
- return new ChatMessageChunk({ content, role, response_metadata });
179
- }
180
- }
181
49
  // Used in LangSmith, export is important here
182
50
  export function _convertMessagesToOpenAIParams(messages, model) {
183
51
  // TODO: Function messages do not support array content, fix cast
@@ -1131,6 +999,138 @@ export class ChatOpenAI extends BaseChatModel {
1131
999
  }
1132
1000
  return params;
1133
1001
  }
1002
+ _convertOpenAIChatCompletionMessageToBaseMessage(message, rawResponse) {
1003
+ const rawToolCalls = message.tool_calls;
1004
+ switch (message.role) {
1005
+ case "assistant": {
1006
+ const toolCalls = [];
1007
+ const invalidToolCalls = [];
1008
+ for (const rawToolCall of rawToolCalls ?? []) {
1009
+ try {
1010
+ toolCalls.push(parseToolCall(rawToolCall, { returnId: true }));
1011
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1012
+ }
1013
+ catch (e) {
1014
+ invalidToolCalls.push(makeInvalidToolCall(rawToolCall, e.message));
1015
+ }
1016
+ }
1017
+ const additional_kwargs = {
1018
+ function_call: message.function_call,
1019
+ tool_calls: rawToolCalls,
1020
+ };
1021
+ if (this.__includeRawResponse !== undefined) {
1022
+ additional_kwargs.__raw_response = rawResponse;
1023
+ }
1024
+ const response_metadata = {
1025
+ model_name: rawResponse.model,
1026
+ ...(rawResponse.system_fingerprint
1027
+ ? {
1028
+ usage: { ...rawResponse.usage },
1029
+ system_fingerprint: rawResponse.system_fingerprint,
1030
+ }
1031
+ : {}),
1032
+ };
1033
+ if (message.audio) {
1034
+ additional_kwargs.audio = message.audio;
1035
+ }
1036
+ return new AIMessage({
1037
+ content: message.content || "",
1038
+ tool_calls: toolCalls,
1039
+ invalid_tool_calls: invalidToolCalls,
1040
+ additional_kwargs,
1041
+ response_metadata,
1042
+ id: rawResponse.id,
1043
+ });
1044
+ }
1045
+ default:
1046
+ return new ChatMessage(message.content || "", message.role ?? "unknown");
1047
+ }
1048
+ }
1049
+ _convertOpenAIDeltaToBaseMessageChunk(
1050
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1051
+ delta, rawResponse, defaultRole) {
1052
+ const role = delta.role ?? defaultRole;
1053
+ const content = delta.content ?? "";
1054
+ let additional_kwargs;
1055
+ if (delta.function_call) {
1056
+ additional_kwargs = {
1057
+ function_call: delta.function_call,
1058
+ };
1059
+ }
1060
+ else if (delta.tool_calls) {
1061
+ additional_kwargs = {
1062
+ tool_calls: delta.tool_calls,
1063
+ };
1064
+ }
1065
+ else {
1066
+ additional_kwargs = {};
1067
+ }
1068
+ if (this.__includeRawResponse) {
1069
+ additional_kwargs.__raw_response = rawResponse;
1070
+ }
1071
+ if (delta.audio) {
1072
+ additional_kwargs.audio = {
1073
+ ...delta.audio,
1074
+ index: rawResponse.choices[0].index,
1075
+ };
1076
+ }
1077
+ const response_metadata = { usage: { ...rawResponse.usage } };
1078
+ if (role === "user") {
1079
+ return new HumanMessageChunk({ content, response_metadata });
1080
+ }
1081
+ else if (role === "assistant") {
1082
+ const toolCallChunks = [];
1083
+ if (Array.isArray(delta.tool_calls)) {
1084
+ for (const rawToolCall of delta.tool_calls) {
1085
+ toolCallChunks.push({
1086
+ name: rawToolCall.function?.name,
1087
+ args: rawToolCall.function?.arguments,
1088
+ id: rawToolCall.id,
1089
+ index: rawToolCall.index,
1090
+ type: "tool_call_chunk",
1091
+ });
1092
+ }
1093
+ }
1094
+ return new AIMessageChunk({
1095
+ content,
1096
+ tool_call_chunks: toolCallChunks,
1097
+ additional_kwargs,
1098
+ id: rawResponse.id,
1099
+ response_metadata,
1100
+ });
1101
+ }
1102
+ else if (role === "system") {
1103
+ return new SystemMessageChunk({ content, response_metadata });
1104
+ }
1105
+ else if (role === "developer") {
1106
+ return new SystemMessageChunk({
1107
+ content,
1108
+ response_metadata,
1109
+ additional_kwargs: {
1110
+ __openai_role__: "developer",
1111
+ },
1112
+ });
1113
+ }
1114
+ else if (role === "function") {
1115
+ return new FunctionMessageChunk({
1116
+ content,
1117
+ additional_kwargs,
1118
+ name: delta.name,
1119
+ response_metadata,
1120
+ });
1121
+ }
1122
+ else if (role === "tool") {
1123
+ return new ToolMessageChunk({
1124
+ content,
1125
+ additional_kwargs,
1126
+ tool_call_id: delta.tool_call_id,
1127
+ response_metadata,
1128
+ });
1129
+ }
1130
+ else {
1131
+ return new ChatMessageChunk({ content, role, response_metadata });
1132
+ }
1133
+ }
1134
1134
  /** @ignore */
1135
1135
  _identifyingParams() {
1136
1136
  return {
@@ -1163,7 +1163,7 @@ export class ChatOpenAI extends BaseChatModel {
1163
1163
  if (!delta) {
1164
1164
  continue;
1165
1165
  }
1166
- const chunk = _convertDeltaToMessageChunk(delta, data, defaultRole, this.__includeRawResponse);
1166
+ const chunk = this._convertOpenAIDeltaToBaseMessageChunk(delta, data, defaultRole);
1167
1167
  defaultRole = delta.role ?? defaultRole;
1168
1168
  const newTokenIndices = {
1169
1169
  prompt: options.promptIndex ?? 0,
@@ -1349,7 +1349,7 @@ export class ChatOpenAI extends BaseChatModel {
1349
1349
  const text = part.message?.content ?? "";
1350
1350
  const generation = {
1351
1351
  text,
1352
- message: openAIResponseToChatMessage(part.message ?? { role: "assistant" }, data, this.__includeRawResponse),
1352
+ message: this._convertOpenAIChatCompletionMessageToBaseMessage(part.message ?? { role: "assistant" }, data),
1353
1353
  };
1354
1354
  generation.generationInfo = {
1355
1355
  ...(part.finish_reason ? { finish_reason: part.finish_reason } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@langchain/openai",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "OpenAI integrations for LangChain.js",
5
5
  "type": "module",
6
6
  "engines": {