@ai-sdk/openai 0.0.0-013d7476-20250808163325 → 0.0.0-2f1ae29d-20260122140908
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1324 -228
- package/dist/index.d.mts +832 -85
- package/dist/index.d.ts +832 -85
- package/dist/index.js +3940 -1418
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4039 -1469
- package/dist/index.mjs.map +1 -1
- package/dist/internal/index.d.mts +954 -116
- package/dist/internal/index.d.ts +954 -116
- package/dist/internal/index.js +3798 -1342
- package/dist/internal/index.js.map +1 -1
- package/dist/internal/index.mjs +3728 -1245
- package/dist/internal/index.mjs.map +1 -1
- package/docs/03-openai.mdx +2018 -0
- package/package.json +13 -6
- package/src/chat/__fixtures__/azure-model-router.1.chunks.txt +8 -0
- package/src/chat/__snapshots__/openai-chat-language-model.test.ts.snap +88 -0
- package/src/chat/convert-openai-chat-usage.ts +57 -0
- package/src/chat/convert-to-openai-chat-messages.test.ts +516 -0
- package/src/chat/convert-to-openai-chat-messages.ts +225 -0
- package/src/chat/get-response-metadata.ts +15 -0
- package/src/chat/map-openai-finish-reason.ts +19 -0
- package/src/chat/openai-chat-api.ts +198 -0
- package/src/chat/openai-chat-language-model.test.ts +3496 -0
- package/src/chat/openai-chat-language-model.ts +700 -0
- package/src/chat/openai-chat-options.ts +186 -0
- package/src/chat/openai-chat-prepare-tools.test.ts +322 -0
- package/src/chat/openai-chat-prepare-tools.ts +84 -0
- package/src/chat/openai-chat-prompt.ts +70 -0
- package/src/completion/convert-openai-completion-usage.ts +46 -0
- package/src/completion/convert-to-openai-completion-prompt.ts +93 -0
- package/src/completion/get-response-metadata.ts +15 -0
- package/src/completion/map-openai-finish-reason.ts +19 -0
- package/src/completion/openai-completion-api.ts +81 -0
- package/src/completion/openai-completion-language-model.test.ts +752 -0
- package/src/completion/openai-completion-language-model.ts +336 -0
- package/src/completion/openai-completion-options.ts +58 -0
- package/src/embedding/__snapshots__/openai-embedding-model.test.ts.snap +43 -0
- package/src/embedding/openai-embedding-api.ts +13 -0
- package/src/embedding/openai-embedding-model.test.ts +146 -0
- package/src/embedding/openai-embedding-model.ts +95 -0
- package/src/embedding/openai-embedding-options.ts +30 -0
- package/src/image/openai-image-api.ts +35 -0
- package/src/image/openai-image-model.test.ts +722 -0
- package/src/image/openai-image-model.ts +305 -0
- package/src/image/openai-image-options.ts +28 -0
- package/src/index.ts +9 -0
- package/src/internal/index.ts +19 -0
- package/src/openai-config.ts +18 -0
- package/src/openai-error.test.ts +34 -0
- package/src/openai-error.ts +22 -0
- package/src/openai-language-model-capabilities.test.ts +93 -0
- package/src/openai-language-model-capabilities.ts +54 -0
- package/src/openai-provider.test.ts +98 -0
- package/src/openai-provider.ts +270 -0
- package/src/openai-tools.ts +114 -0
- package/src/responses/__fixtures__/openai-apply-patch-tool-delete.1.chunks.txt +5 -0
- package/src/responses/__fixtures__/openai-apply-patch-tool.1.chunks.txt +38 -0
- package/src/responses/__fixtures__/openai-apply-patch-tool.1.json +69 -0
- package/src/responses/__fixtures__/openai-code-interpreter-tool.1.chunks.txt +393 -0
- package/src/responses/__fixtures__/openai-code-interpreter-tool.1.json +137 -0
- package/src/responses/__fixtures__/openai-error.1.chunks.txt +4 -0
- package/src/responses/__fixtures__/openai-error.1.json +8 -0
- package/src/responses/__fixtures__/openai-file-search-tool.1.chunks.txt +94 -0
- package/src/responses/__fixtures__/openai-file-search-tool.1.json +89 -0
- package/src/responses/__fixtures__/openai-file-search-tool.2.chunks.txt +93 -0
- package/src/responses/__fixtures__/openai-file-search-tool.2.json +112 -0
- package/src/responses/__fixtures__/openai-image-generation-tool.1.chunks.txt +16 -0
- package/src/responses/__fixtures__/openai-image-generation-tool.1.json +96 -0
- package/src/responses/__fixtures__/openai-local-shell-tool.1.chunks.txt +7 -0
- package/src/responses/__fixtures__/openai-local-shell-tool.1.json +70 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.1.chunks.txt +11 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.1.json +169 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.2.chunks.txt +123 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.2.json +176 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.3.chunks.txt +11 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.3.json +169 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.4.chunks.txt +84 -0
- package/src/responses/__fixtures__/openai-mcp-tool-approval.4.json +182 -0
- package/src/responses/__fixtures__/openai-mcp-tool.1.chunks.txt +373 -0
- package/src/responses/__fixtures__/openai-mcp-tool.1.json +159 -0
- package/src/responses/__fixtures__/openai-reasoning-encrypted-content.1.chunks.txt +110 -0
- package/src/responses/__fixtures__/openai-reasoning-encrypted-content.1.json +117 -0
- package/src/responses/__fixtures__/openai-shell-tool.1.chunks.txt +182 -0
- package/src/responses/__fixtures__/openai-shell-tool.1.json +73 -0
- package/src/responses/__fixtures__/openai-web-search-tool.1.chunks.txt +185 -0
- package/src/responses/__fixtures__/openai-web-search-tool.1.json +266 -0
- package/src/responses/__snapshots__/openai-responses-language-model.test.ts.snap +10955 -0
- package/src/responses/convert-openai-responses-usage.ts +53 -0
- package/src/responses/convert-to-openai-responses-input.test.ts +2976 -0
- package/src/responses/convert-to-openai-responses-input.ts +578 -0
- package/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/responses/openai-responses-api.test.ts +89 -0
- package/src/responses/openai-responses-api.ts +1086 -0
- package/src/responses/openai-responses-language-model.test.ts +6927 -0
- package/src/responses/openai-responses-language-model.ts +1932 -0
- package/src/responses/openai-responses-options.ts +312 -0
- package/src/responses/openai-responses-prepare-tools.test.ts +924 -0
- package/src/responses/openai-responses-prepare-tools.ts +264 -0
- package/src/responses/openai-responses-provider-metadata.ts +39 -0
- package/src/speech/openai-speech-api.ts +38 -0
- package/src/speech/openai-speech-model.test.ts +202 -0
- package/src/speech/openai-speech-model.ts +137 -0
- package/src/speech/openai-speech-options.ts +22 -0
- package/src/tool/apply-patch.ts +141 -0
- package/src/tool/code-interpreter.ts +104 -0
- package/src/tool/file-search.ts +145 -0
- package/src/tool/image-generation.ts +126 -0
- package/src/tool/local-shell.test-d.ts +20 -0
- package/src/tool/local-shell.ts +72 -0
- package/src/tool/mcp.ts +125 -0
- package/src/tool/shell.ts +85 -0
- package/src/tool/web-search-preview.ts +139 -0
- package/src/tool/web-search.test-d.ts +13 -0
- package/src/tool/web-search.ts +179 -0
- package/src/transcription/openai-transcription-api.ts +37 -0
- package/src/transcription/openai-transcription-model.test.ts +507 -0
- package/src/transcription/openai-transcription-model.ts +232 -0
- package/src/transcription/openai-transcription-options.ts +50 -0
- package/src/transcription/transcription-test.mp3 +0 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,2976 @@
|
|
|
1
|
+
import { ToolNameMapping } from '../../../provider-utils/src/create-tool-name-mapping';
|
|
2
|
+
import { convertToOpenAIResponsesInput } from './convert-to-openai-responses-input';
|
|
3
|
+
import { describe, it, expect } from 'vitest';
|
|
4
|
+
|
|
5
|
+
const testToolNameMapping: ToolNameMapping = {
|
|
6
|
+
toProviderToolName: (customToolName: string) => customToolName,
|
|
7
|
+
toCustomToolName: (providerToolName: string) => providerToolName,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
describe('convertToOpenAIResponsesInput', () => {
|
|
11
|
+
describe('system messages', () => {
|
|
12
|
+
it('should convert system messages to system role', async () => {
|
|
13
|
+
const result = await convertToOpenAIResponsesInput({
|
|
14
|
+
prompt: [{ role: 'system', content: 'Hello' }],
|
|
15
|
+
toolNameMapping: testToolNameMapping,
|
|
16
|
+
systemMessageMode: 'system',
|
|
17
|
+
providerOptionsName: 'openai',
|
|
18
|
+
store: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(result.input).toEqual([{ role: 'system', content: 'Hello' }]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should convert system messages to developer role', async () => {
|
|
25
|
+
const result = await convertToOpenAIResponsesInput({
|
|
26
|
+
prompt: [{ role: 'system', content: 'Hello' }],
|
|
27
|
+
toolNameMapping: testToolNameMapping,
|
|
28
|
+
systemMessageMode: 'developer',
|
|
29
|
+
providerOptionsName: 'openai',
|
|
30
|
+
store: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.input).toEqual([{ role: 'developer', content: 'Hello' }]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should remove system messages', async () => {
|
|
37
|
+
const result = await convertToOpenAIResponsesInput({
|
|
38
|
+
prompt: [{ role: 'system', content: 'Hello' }],
|
|
39
|
+
toolNameMapping: testToolNameMapping,
|
|
40
|
+
systemMessageMode: 'remove',
|
|
41
|
+
providerOptionsName: 'openai',
|
|
42
|
+
store: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result.input).toEqual([]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('user messages', () => {
|
|
50
|
+
it('should convert messages with only a text part to a string content', async () => {
|
|
51
|
+
const result = await convertToOpenAIResponsesInput({
|
|
52
|
+
prompt: [
|
|
53
|
+
{
|
|
54
|
+
role: 'user',
|
|
55
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
toolNameMapping: testToolNameMapping,
|
|
59
|
+
systemMessageMode: 'system',
|
|
60
|
+
providerOptionsName: 'openai',
|
|
61
|
+
store: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(result.input).toEqual([
|
|
65
|
+
{ role: 'user', content: [{ type: 'input_text', text: 'Hello' }] },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should convert messages with image parts using URL', async () => {
|
|
70
|
+
const result = await convertToOpenAIResponsesInput({
|
|
71
|
+
prompt: [
|
|
72
|
+
{
|
|
73
|
+
role: 'user',
|
|
74
|
+
content: [
|
|
75
|
+
{ type: 'text', text: 'Hello' },
|
|
76
|
+
{
|
|
77
|
+
type: 'file',
|
|
78
|
+
mediaType: 'image/*',
|
|
79
|
+
data: new URL('https://example.com/image.jpg'),
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
toolNameMapping: testToolNameMapping,
|
|
85
|
+
systemMessageMode: 'system',
|
|
86
|
+
providerOptionsName: 'openai',
|
|
87
|
+
store: true,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(result.input).toEqual([
|
|
91
|
+
{
|
|
92
|
+
role: 'user',
|
|
93
|
+
content: [
|
|
94
|
+
{ type: 'input_text', text: 'Hello' },
|
|
95
|
+
{
|
|
96
|
+
type: 'input_image',
|
|
97
|
+
image_url: 'https://example.com/image.jpg',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should convert messages with image parts using binary data', async () => {
|
|
105
|
+
const result = await convertToOpenAIResponsesInput({
|
|
106
|
+
prompt: [
|
|
107
|
+
{
|
|
108
|
+
role: 'user',
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'file',
|
|
112
|
+
mediaType: 'image/png',
|
|
113
|
+
data: Buffer.from([0, 1, 2, 3]).toString('base64'),
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
toolNameMapping: testToolNameMapping,
|
|
119
|
+
systemMessageMode: 'system',
|
|
120
|
+
providerOptionsName: 'openai',
|
|
121
|
+
store: true,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(result.input).toEqual([
|
|
125
|
+
{
|
|
126
|
+
role: 'user',
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: 'input_image',
|
|
130
|
+
image_url: 'data:image/png;base64,AAECAw==',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should convert messages with image parts using Uint8Array', async () => {
|
|
138
|
+
const result = await convertToOpenAIResponsesInput({
|
|
139
|
+
prompt: [
|
|
140
|
+
{
|
|
141
|
+
role: 'user',
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: 'file',
|
|
145
|
+
mediaType: 'image/png',
|
|
146
|
+
data: new Uint8Array([0, 1, 2, 3]),
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
toolNameMapping: testToolNameMapping,
|
|
152
|
+
systemMessageMode: 'system',
|
|
153
|
+
providerOptionsName: 'openai',
|
|
154
|
+
store: true,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result.input).toEqual([
|
|
158
|
+
{
|
|
159
|
+
role: 'user',
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'input_image',
|
|
163
|
+
image_url: 'data:image/png;base64,AAECAw==',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should convert messages with image parts using file_id', async () => {
|
|
171
|
+
const result = await convertToOpenAIResponsesInput({
|
|
172
|
+
prompt: [
|
|
173
|
+
{
|
|
174
|
+
role: 'user',
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'file',
|
|
178
|
+
mediaType: 'image/png',
|
|
179
|
+
data: 'file-12345',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
toolNameMapping: testToolNameMapping,
|
|
185
|
+
systemMessageMode: 'system',
|
|
186
|
+
providerOptionsName: 'openai',
|
|
187
|
+
fileIdPrefixes: ['file-'],
|
|
188
|
+
store: true,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(result.input).toEqual([
|
|
192
|
+
{
|
|
193
|
+
role: 'user',
|
|
194
|
+
content: [
|
|
195
|
+
{
|
|
196
|
+
type: 'input_image',
|
|
197
|
+
file_id: 'file-12345',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
},
|
|
201
|
+
]);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should use default mime type for binary images', async () => {
|
|
205
|
+
const result = await convertToOpenAIResponsesInput({
|
|
206
|
+
prompt: [
|
|
207
|
+
{
|
|
208
|
+
role: 'user',
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: 'file',
|
|
212
|
+
mediaType: 'image/*',
|
|
213
|
+
data: Buffer.from([0, 1, 2, 3]).toString('base64'),
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
toolNameMapping: testToolNameMapping,
|
|
219
|
+
systemMessageMode: 'system',
|
|
220
|
+
providerOptionsName: 'openai',
|
|
221
|
+
store: true,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
expect(result.input).toEqual([
|
|
225
|
+
{
|
|
226
|
+
role: 'user',
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: 'input_image',
|
|
230
|
+
image_url: 'data:image/jpeg;base64,AAECAw==',
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should add image detail when specified through extension', async () => {
|
|
238
|
+
const result = await convertToOpenAIResponsesInput({
|
|
239
|
+
prompt: [
|
|
240
|
+
{
|
|
241
|
+
role: 'user',
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: 'file',
|
|
245
|
+
mediaType: 'image/png',
|
|
246
|
+
data: Buffer.from([0, 1, 2, 3]).toString('base64'),
|
|
247
|
+
providerOptions: {
|
|
248
|
+
openai: {
|
|
249
|
+
imageDetail: 'low',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
toolNameMapping: testToolNameMapping,
|
|
257
|
+
systemMessageMode: 'system',
|
|
258
|
+
providerOptionsName: 'openai',
|
|
259
|
+
store: true,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
expect(result.input).toEqual([
|
|
263
|
+
{
|
|
264
|
+
role: 'user',
|
|
265
|
+
content: [
|
|
266
|
+
{
|
|
267
|
+
type: 'input_image',
|
|
268
|
+
image_url: 'data:image/png;base64,AAECAw==',
|
|
269
|
+
detail: 'low',
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should read image detail from providerOptions when providerOptionsName is azure', async () => {
|
|
277
|
+
const result = await convertToOpenAIResponsesInput({
|
|
278
|
+
prompt: [
|
|
279
|
+
{
|
|
280
|
+
role: 'user',
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: 'file',
|
|
284
|
+
mediaType: 'image/png',
|
|
285
|
+
data: Buffer.from([0, 1, 2, 3]).toString('base64'),
|
|
286
|
+
providerOptions: {
|
|
287
|
+
azure: {
|
|
288
|
+
imageDetail: 'low',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
toolNameMapping: testToolNameMapping,
|
|
296
|
+
systemMessageMode: 'system',
|
|
297
|
+
providerOptionsName: 'azure',
|
|
298
|
+
store: true,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(result.input).toEqual([
|
|
302
|
+
{
|
|
303
|
+
role: 'user',
|
|
304
|
+
content: [
|
|
305
|
+
{
|
|
306
|
+
type: 'input_image',
|
|
307
|
+
image_url: 'data:image/png;base64,AAECAw==',
|
|
308
|
+
detail: 'low',
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should convert messages with PDF file parts', async () => {
|
|
316
|
+
const base64Data = 'AQIDBAU='; // Base64 encoding of pdfData
|
|
317
|
+
|
|
318
|
+
const result = await convertToOpenAIResponsesInput({
|
|
319
|
+
prompt: [
|
|
320
|
+
{
|
|
321
|
+
role: 'user',
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: 'file',
|
|
325
|
+
mediaType: 'application/pdf',
|
|
326
|
+
data: base64Data,
|
|
327
|
+
filename: 'document.pdf',
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
toolNameMapping: testToolNameMapping,
|
|
333
|
+
systemMessageMode: 'system',
|
|
334
|
+
providerOptionsName: 'openai',
|
|
335
|
+
store: true,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
expect(result.input).toEqual([
|
|
339
|
+
{
|
|
340
|
+
role: 'user',
|
|
341
|
+
content: [
|
|
342
|
+
{
|
|
343
|
+
type: 'input_file',
|
|
344
|
+
filename: 'document.pdf',
|
|
345
|
+
file_data: 'data:application/pdf;base64,AQIDBAU=',
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
},
|
|
349
|
+
]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should convert messages with PDF file parts using file_id', async () => {
|
|
353
|
+
const result = await convertToOpenAIResponsesInput({
|
|
354
|
+
prompt: [
|
|
355
|
+
{
|
|
356
|
+
role: 'user',
|
|
357
|
+
content: [
|
|
358
|
+
{
|
|
359
|
+
type: 'file',
|
|
360
|
+
mediaType: 'application/pdf',
|
|
361
|
+
data: 'file-pdf-12345',
|
|
362
|
+
},
|
|
363
|
+
],
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
toolNameMapping: testToolNameMapping,
|
|
367
|
+
systemMessageMode: 'system',
|
|
368
|
+
providerOptionsName: 'openai',
|
|
369
|
+
fileIdPrefixes: ['file-'],
|
|
370
|
+
store: true,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
expect(result.input).toEqual([
|
|
374
|
+
{
|
|
375
|
+
role: 'user',
|
|
376
|
+
content: [
|
|
377
|
+
{
|
|
378
|
+
type: 'input_file',
|
|
379
|
+
file_id: 'file-pdf-12345',
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
},
|
|
383
|
+
]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('should use default filename for PDF file parts when not provided', async () => {
|
|
387
|
+
const base64Data = 'AQIDBAU=';
|
|
388
|
+
|
|
389
|
+
const result = await convertToOpenAIResponsesInput({
|
|
390
|
+
prompt: [
|
|
391
|
+
{
|
|
392
|
+
role: 'user',
|
|
393
|
+
content: [
|
|
394
|
+
{
|
|
395
|
+
type: 'file',
|
|
396
|
+
mediaType: 'application/pdf',
|
|
397
|
+
data: base64Data,
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
toolNameMapping: testToolNameMapping,
|
|
403
|
+
systemMessageMode: 'system',
|
|
404
|
+
providerOptionsName: 'openai',
|
|
405
|
+
store: true,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
expect(result.input).toEqual([
|
|
409
|
+
{
|
|
410
|
+
role: 'user',
|
|
411
|
+
content: [
|
|
412
|
+
{
|
|
413
|
+
type: 'input_file',
|
|
414
|
+
filename: 'part-0.pdf',
|
|
415
|
+
file_data: 'data:application/pdf;base64,AQIDBAU=',
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should throw error for unsupported file types', async () => {
|
|
423
|
+
const base64Data = 'AQIDBAU=';
|
|
424
|
+
|
|
425
|
+
await expect(
|
|
426
|
+
convertToOpenAIResponsesInput({
|
|
427
|
+
prompt: [
|
|
428
|
+
{
|
|
429
|
+
role: 'user',
|
|
430
|
+
content: [
|
|
431
|
+
{
|
|
432
|
+
type: 'file',
|
|
433
|
+
mediaType: 'text/plain',
|
|
434
|
+
data: base64Data,
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
toolNameMapping: testToolNameMapping,
|
|
440
|
+
systemMessageMode: 'system',
|
|
441
|
+
providerOptionsName: 'openai',
|
|
442
|
+
store: true,
|
|
443
|
+
}),
|
|
444
|
+
).rejects.toThrow('file part media type text/plain');
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it('should convert PDF file parts with URL to input_file with file_url', async () => {
|
|
448
|
+
const result = await convertToOpenAIResponsesInput({
|
|
449
|
+
prompt: [
|
|
450
|
+
{
|
|
451
|
+
role: 'user',
|
|
452
|
+
content: [
|
|
453
|
+
{
|
|
454
|
+
type: 'file',
|
|
455
|
+
mediaType: 'application/pdf',
|
|
456
|
+
data: new URL('https://example.com/document.pdf'),
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
},
|
|
460
|
+
],
|
|
461
|
+
toolNameMapping: testToolNameMapping,
|
|
462
|
+
systemMessageMode: 'system',
|
|
463
|
+
providerOptionsName: 'openai',
|
|
464
|
+
store: true,
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
expect(result.input).toEqual([
|
|
468
|
+
{
|
|
469
|
+
role: 'user',
|
|
470
|
+
content: [
|
|
471
|
+
{
|
|
472
|
+
type: 'input_file',
|
|
473
|
+
file_url: 'https://example.com/document.pdf',
|
|
474
|
+
},
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
describe('Azure OpenAI file ID support', () => {
|
|
481
|
+
it('should convert image parts with assistant- prefix', async () => {
|
|
482
|
+
const result = await convertToOpenAIResponsesInput({
|
|
483
|
+
toolNameMapping: testToolNameMapping,
|
|
484
|
+
prompt: [
|
|
485
|
+
{
|
|
486
|
+
role: 'user',
|
|
487
|
+
content: [
|
|
488
|
+
{
|
|
489
|
+
type: 'file',
|
|
490
|
+
mediaType: 'image/png',
|
|
491
|
+
data: 'assistant-img-abc123',
|
|
492
|
+
},
|
|
493
|
+
],
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
systemMessageMode: 'system',
|
|
497
|
+
providerOptionsName: 'openai',
|
|
498
|
+
fileIdPrefixes: ['assistant-'],
|
|
499
|
+
store: true,
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
expect(result.input).toEqual([
|
|
503
|
+
{
|
|
504
|
+
role: 'user',
|
|
505
|
+
content: [
|
|
506
|
+
{
|
|
507
|
+
type: 'input_image',
|
|
508
|
+
file_id: 'assistant-img-abc123',
|
|
509
|
+
},
|
|
510
|
+
],
|
|
511
|
+
},
|
|
512
|
+
]);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should convert PDF parts with assistant- prefix', async () => {
|
|
516
|
+
const result = await convertToOpenAIResponsesInput({
|
|
517
|
+
prompt: [
|
|
518
|
+
{
|
|
519
|
+
role: 'user',
|
|
520
|
+
content: [
|
|
521
|
+
{
|
|
522
|
+
type: 'file',
|
|
523
|
+
mediaType: 'application/pdf',
|
|
524
|
+
data: 'assistant-pdf-abc123',
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
toolNameMapping: testToolNameMapping,
|
|
530
|
+
systemMessageMode: 'system',
|
|
531
|
+
providerOptionsName: 'openai',
|
|
532
|
+
fileIdPrefixes: ['assistant-'],
|
|
533
|
+
store: true,
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
expect(result.input).toEqual([
|
|
537
|
+
{
|
|
538
|
+
role: 'user',
|
|
539
|
+
content: [
|
|
540
|
+
{
|
|
541
|
+
type: 'input_file',
|
|
542
|
+
file_id: 'assistant-pdf-abc123',
|
|
543
|
+
},
|
|
544
|
+
],
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it('should support multiple file ID prefixes', async () => {
|
|
550
|
+
const result = await convertToOpenAIResponsesInput({
|
|
551
|
+
prompt: [
|
|
552
|
+
{
|
|
553
|
+
role: 'user',
|
|
554
|
+
content: [
|
|
555
|
+
{
|
|
556
|
+
type: 'file',
|
|
557
|
+
mediaType: 'image/png',
|
|
558
|
+
data: 'assistant-img-abc123',
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
type: 'file',
|
|
562
|
+
mediaType: 'application/pdf',
|
|
563
|
+
data: 'file-pdf-xyz789',
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
toolNameMapping: testToolNameMapping,
|
|
569
|
+
systemMessageMode: 'system',
|
|
570
|
+
providerOptionsName: 'openai',
|
|
571
|
+
fileIdPrefixes: ['assistant-', 'file-'],
|
|
572
|
+
store: true,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
expect(result.input).toEqual([
|
|
576
|
+
{
|
|
577
|
+
role: 'user',
|
|
578
|
+
content: [
|
|
579
|
+
{
|
|
580
|
+
type: 'input_image',
|
|
581
|
+
file_id: 'assistant-img-abc123',
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
type: 'input_file',
|
|
585
|
+
file_id: 'file-pdf-xyz789',
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
},
|
|
589
|
+
]);
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
describe('fileIdPrefixes undefined behavior', () => {
|
|
594
|
+
it('should treat all file data as base64 when fileIdPrefixes is undefined', async () => {
|
|
595
|
+
const result = await convertToOpenAIResponsesInput({
|
|
596
|
+
prompt: [
|
|
597
|
+
{
|
|
598
|
+
role: 'user',
|
|
599
|
+
content: [
|
|
600
|
+
{
|
|
601
|
+
type: 'file',
|
|
602
|
+
mediaType: 'image/png',
|
|
603
|
+
data: 'file-12345', // Looks like file ID but should be treated as base64
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
type: 'file',
|
|
607
|
+
mediaType: 'application/pdf',
|
|
608
|
+
data: 'assistant-abc123', // Looks like file ID but should be treated as base64
|
|
609
|
+
filename: 'test.pdf',
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
toolNameMapping: testToolNameMapping,
|
|
615
|
+
systemMessageMode: 'system',
|
|
616
|
+
providerOptionsName: 'openai',
|
|
617
|
+
// fileIdPrefixes intentionally omitted
|
|
618
|
+
store: true,
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
expect(result.input).toEqual([
|
|
622
|
+
{
|
|
623
|
+
role: 'user',
|
|
624
|
+
content: [
|
|
625
|
+
{
|
|
626
|
+
type: 'input_image',
|
|
627
|
+
image_url: 'data:image/png;base64,file-12345',
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
type: 'input_file',
|
|
631
|
+
filename: 'test.pdf',
|
|
632
|
+
file_data: 'data:application/pdf;base64,assistant-abc123',
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
]);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('should handle empty fileIdPrefixes array', async () => {
|
|
640
|
+
const result = await convertToOpenAIResponsesInput({
|
|
641
|
+
prompt: [
|
|
642
|
+
{
|
|
643
|
+
role: 'user',
|
|
644
|
+
content: [
|
|
645
|
+
{
|
|
646
|
+
type: 'file',
|
|
647
|
+
mediaType: 'image/png',
|
|
648
|
+
data: 'file-12345',
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
toolNameMapping: testToolNameMapping,
|
|
654
|
+
systemMessageMode: 'system',
|
|
655
|
+
providerOptionsName: 'openai',
|
|
656
|
+
fileIdPrefixes: [], // Empty array should disable file ID detection
|
|
657
|
+
store: true,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
expect(result.input).toEqual([
|
|
661
|
+
{
|
|
662
|
+
role: 'user',
|
|
663
|
+
content: [
|
|
664
|
+
{
|
|
665
|
+
type: 'input_image',
|
|
666
|
+
image_url: 'data:image/png;base64,file-12345',
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
},
|
|
670
|
+
]);
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe('assistant messages', () => {
|
|
676
|
+
it('should convert messages with only a text part to a string content', async () => {
|
|
677
|
+
const result = await convertToOpenAIResponsesInput({
|
|
678
|
+
prompt: [
|
|
679
|
+
{ role: 'assistant', content: [{ type: 'text', text: 'Hello' }] },
|
|
680
|
+
],
|
|
681
|
+
toolNameMapping: testToolNameMapping,
|
|
682
|
+
systemMessageMode: 'system',
|
|
683
|
+
providerOptionsName: 'openai',
|
|
684
|
+
store: true,
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
expect(result.input).toEqual([
|
|
688
|
+
{
|
|
689
|
+
role: 'assistant',
|
|
690
|
+
content: [{ type: 'output_text', text: 'Hello' }],
|
|
691
|
+
},
|
|
692
|
+
]);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
it('should convert messages with tool call parts', async () => {
|
|
696
|
+
const result = await convertToOpenAIResponsesInput({
|
|
697
|
+
toolNameMapping: testToolNameMapping,
|
|
698
|
+
prompt: [
|
|
699
|
+
{
|
|
700
|
+
role: 'assistant',
|
|
701
|
+
content: [
|
|
702
|
+
{ type: 'text', text: 'I will search for that information.' },
|
|
703
|
+
{
|
|
704
|
+
type: 'tool-call',
|
|
705
|
+
toolCallId: 'call_123',
|
|
706
|
+
toolName: 'search',
|
|
707
|
+
input: { query: 'weather in San Francisco' },
|
|
708
|
+
},
|
|
709
|
+
],
|
|
710
|
+
},
|
|
711
|
+
],
|
|
712
|
+
systemMessageMode: 'system',
|
|
713
|
+
providerOptionsName: 'openai',
|
|
714
|
+
store: true,
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
expect(result.input).toEqual([
|
|
718
|
+
{
|
|
719
|
+
role: 'assistant',
|
|
720
|
+
content: [
|
|
721
|
+
{
|
|
722
|
+
type: 'output_text',
|
|
723
|
+
text: 'I will search for that information.',
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
type: 'function_call',
|
|
729
|
+
call_id: 'call_123',
|
|
730
|
+
name: 'search',
|
|
731
|
+
arguments: JSON.stringify({ query: 'weather in San Francisco' }),
|
|
732
|
+
},
|
|
733
|
+
]);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it('should convert messages with tool call parts that have ids', async () => {
|
|
737
|
+
const result = await convertToOpenAIResponsesInput({
|
|
738
|
+
toolNameMapping: testToolNameMapping,
|
|
739
|
+
prompt: [
|
|
740
|
+
{
|
|
741
|
+
role: 'assistant',
|
|
742
|
+
content: [
|
|
743
|
+
{
|
|
744
|
+
type: 'text',
|
|
745
|
+
text: 'I will search for that information.',
|
|
746
|
+
providerOptions: {
|
|
747
|
+
openai: {
|
|
748
|
+
itemId: 'id_123',
|
|
749
|
+
},
|
|
750
|
+
},
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
type: 'tool-call',
|
|
754
|
+
toolCallId: 'call_123',
|
|
755
|
+
toolName: 'search',
|
|
756
|
+
input: { query: 'weather in San Francisco' },
|
|
757
|
+
providerOptions: {
|
|
758
|
+
openai: {
|
|
759
|
+
itemId: 'id_456',
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
systemMessageMode: 'system',
|
|
767
|
+
providerOptionsName: 'openai',
|
|
768
|
+
store: true,
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
772
|
+
[
|
|
773
|
+
{
|
|
774
|
+
"id": "id_123",
|
|
775
|
+
"type": "item_reference",
|
|
776
|
+
},
|
|
777
|
+
{
|
|
778
|
+
"id": "id_456",
|
|
779
|
+
"type": "item_reference",
|
|
780
|
+
},
|
|
781
|
+
]
|
|
782
|
+
`);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('should convert multiple tool call parts in a single message', async () => {
|
|
786
|
+
const result = await convertToOpenAIResponsesInput({
|
|
787
|
+
toolNameMapping: testToolNameMapping,
|
|
788
|
+
prompt: [
|
|
789
|
+
{
|
|
790
|
+
role: 'assistant',
|
|
791
|
+
content: [
|
|
792
|
+
{
|
|
793
|
+
type: 'tool-call',
|
|
794
|
+
toolCallId: 'call_123',
|
|
795
|
+
toolName: 'search',
|
|
796
|
+
input: { query: 'weather in San Francisco' },
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
type: 'tool-call',
|
|
800
|
+
toolCallId: 'call_456',
|
|
801
|
+
toolName: 'calculator',
|
|
802
|
+
input: { expression: '2 + 2' },
|
|
803
|
+
},
|
|
804
|
+
],
|
|
805
|
+
},
|
|
806
|
+
],
|
|
807
|
+
systemMessageMode: 'system',
|
|
808
|
+
providerOptionsName: 'openai',
|
|
809
|
+
store: true,
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
expect(result.input).toEqual([
|
|
813
|
+
{
|
|
814
|
+
type: 'function_call',
|
|
815
|
+
call_id: 'call_123',
|
|
816
|
+
name: 'search',
|
|
817
|
+
arguments: JSON.stringify({ query: 'weather in San Francisco' }),
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
type: 'function_call',
|
|
821
|
+
call_id: 'call_456',
|
|
822
|
+
name: 'calculator',
|
|
823
|
+
arguments: JSON.stringify({ expression: '2 + 2' }),
|
|
824
|
+
},
|
|
825
|
+
]);
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
describe('reasoning messages (store: false)', () => {
|
|
829
|
+
describe('single summary part', () => {
|
|
830
|
+
it('should convert single reasoning part with text', async () => {
|
|
831
|
+
const result = await convertToOpenAIResponsesInput({
|
|
832
|
+
toolNameMapping: testToolNameMapping,
|
|
833
|
+
prompt: [
|
|
834
|
+
{
|
|
835
|
+
role: 'assistant',
|
|
836
|
+
content: [
|
|
837
|
+
{
|
|
838
|
+
type: 'reasoning',
|
|
839
|
+
text: 'Analyzing the problem step by step',
|
|
840
|
+
providerOptions: {
|
|
841
|
+
openai: {
|
|
842
|
+
itemId: 'reasoning_001',
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
],
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
systemMessageMode: 'system',
|
|
850
|
+
providerOptionsName: 'openai',
|
|
851
|
+
store: false,
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
expect(result.input).toEqual([
|
|
855
|
+
{
|
|
856
|
+
type: 'reasoning',
|
|
857
|
+
id: 'reasoning_001',
|
|
858
|
+
encrypted_content: undefined,
|
|
859
|
+
summary: [
|
|
860
|
+
{
|
|
861
|
+
type: 'summary_text',
|
|
862
|
+
text: 'Analyzing the problem step by step',
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
},
|
|
866
|
+
]);
|
|
867
|
+
|
|
868
|
+
expect(result.warnings).toHaveLength(0);
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it('should convert single reasoning part with encrypted content', async () => {
|
|
872
|
+
const result = await convertToOpenAIResponsesInput({
|
|
873
|
+
toolNameMapping: testToolNameMapping,
|
|
874
|
+
prompt: [
|
|
875
|
+
{
|
|
876
|
+
role: 'assistant',
|
|
877
|
+
content: [
|
|
878
|
+
{
|
|
879
|
+
type: 'reasoning',
|
|
880
|
+
text: 'Analyzing the problem step by step',
|
|
881
|
+
providerOptions: {
|
|
882
|
+
openai: {
|
|
883
|
+
itemId: 'reasoning_001',
|
|
884
|
+
reasoningEncryptedContent: 'encrypted_content_001',
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
],
|
|
889
|
+
},
|
|
890
|
+
],
|
|
891
|
+
systemMessageMode: 'system',
|
|
892
|
+
providerOptionsName: 'openai',
|
|
893
|
+
store: false,
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
expect(result.input).toEqual([
|
|
897
|
+
{
|
|
898
|
+
type: 'reasoning',
|
|
899
|
+
id: 'reasoning_001',
|
|
900
|
+
encrypted_content: 'encrypted_content_001',
|
|
901
|
+
summary: [
|
|
902
|
+
{
|
|
903
|
+
type: 'summary_text',
|
|
904
|
+
text: 'Analyzing the problem step by step',
|
|
905
|
+
},
|
|
906
|
+
],
|
|
907
|
+
},
|
|
908
|
+
]);
|
|
909
|
+
|
|
910
|
+
expect(result.warnings).toHaveLength(0);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it('should convert single reasoning part with null encrypted content', async () => {
|
|
914
|
+
const result = await convertToOpenAIResponsesInput({
|
|
915
|
+
toolNameMapping: testToolNameMapping,
|
|
916
|
+
prompt: [
|
|
917
|
+
{
|
|
918
|
+
role: 'assistant',
|
|
919
|
+
content: [
|
|
920
|
+
{
|
|
921
|
+
type: 'reasoning',
|
|
922
|
+
text: 'Analyzing the problem step by step',
|
|
923
|
+
providerOptions: {
|
|
924
|
+
openai: {
|
|
925
|
+
itemId: 'reasoning_001',
|
|
926
|
+
reasoningEncryptedContent: null,
|
|
927
|
+
},
|
|
928
|
+
},
|
|
929
|
+
},
|
|
930
|
+
],
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
systemMessageMode: 'system',
|
|
934
|
+
providerOptionsName: 'openai',
|
|
935
|
+
store: false,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
expect(result.input).toEqual([
|
|
939
|
+
{
|
|
940
|
+
type: 'reasoning',
|
|
941
|
+
id: 'reasoning_001',
|
|
942
|
+
encrypted_content: null,
|
|
943
|
+
summary: [
|
|
944
|
+
{
|
|
945
|
+
type: 'summary_text',
|
|
946
|
+
text: 'Analyzing the problem step by step',
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
},
|
|
950
|
+
]);
|
|
951
|
+
|
|
952
|
+
expect(result.warnings).toHaveLength(0);
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
describe('single summary part with empty text', () => {
|
|
957
|
+
it('should create empty summary for initial empty text', async () => {
|
|
958
|
+
const result = await convertToOpenAIResponsesInput({
|
|
959
|
+
toolNameMapping: testToolNameMapping,
|
|
960
|
+
prompt: [
|
|
961
|
+
{
|
|
962
|
+
role: 'assistant',
|
|
963
|
+
content: [
|
|
964
|
+
{
|
|
965
|
+
type: 'reasoning',
|
|
966
|
+
text: '', // Empty text should NOT generate warning when it's the first reasoning part
|
|
967
|
+
providerOptions: {
|
|
968
|
+
openai: {
|
|
969
|
+
itemId: 'reasoning_001',
|
|
970
|
+
},
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
],
|
|
974
|
+
},
|
|
975
|
+
],
|
|
976
|
+
systemMessageMode: 'system',
|
|
977
|
+
providerOptionsName: 'openai',
|
|
978
|
+
store: false,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
expect(result.input).toEqual([
|
|
982
|
+
{
|
|
983
|
+
type: 'reasoning',
|
|
984
|
+
id: 'reasoning_001',
|
|
985
|
+
encrypted_content: undefined,
|
|
986
|
+
summary: [],
|
|
987
|
+
},
|
|
988
|
+
]);
|
|
989
|
+
|
|
990
|
+
expect(result.warnings).toHaveLength(0);
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it('should create empty summary for initial empty text with encrypted content', async () => {
|
|
994
|
+
const result = await convertToOpenAIResponsesInput({
|
|
995
|
+
toolNameMapping: testToolNameMapping,
|
|
996
|
+
prompt: [
|
|
997
|
+
{
|
|
998
|
+
role: 'assistant',
|
|
999
|
+
content: [
|
|
1000
|
+
{
|
|
1001
|
+
type: 'reasoning',
|
|
1002
|
+
text: '', // Empty text should NOT generate warning when it's the first reasoning part
|
|
1003
|
+
providerOptions: {
|
|
1004
|
+
openai: {
|
|
1005
|
+
itemId: 'reasoning_001',
|
|
1006
|
+
reasoningEncryptedContent: 'encrypted_content_001',
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
},
|
|
1010
|
+
],
|
|
1011
|
+
},
|
|
1012
|
+
],
|
|
1013
|
+
systemMessageMode: 'system',
|
|
1014
|
+
providerOptionsName: 'openai',
|
|
1015
|
+
store: false,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
expect(result.input).toEqual([
|
|
1019
|
+
{
|
|
1020
|
+
type: 'reasoning',
|
|
1021
|
+
id: 'reasoning_001',
|
|
1022
|
+
encrypted_content: 'encrypted_content_001',
|
|
1023
|
+
summary: [],
|
|
1024
|
+
},
|
|
1025
|
+
]);
|
|
1026
|
+
|
|
1027
|
+
expect(result.warnings).toHaveLength(0);
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
it('should warn when appending empty text to existing sequence', async () => {
|
|
1031
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1032
|
+
toolNameMapping: testToolNameMapping,
|
|
1033
|
+
prompt: [
|
|
1034
|
+
{
|
|
1035
|
+
role: 'assistant',
|
|
1036
|
+
content: [
|
|
1037
|
+
{
|
|
1038
|
+
type: 'reasoning',
|
|
1039
|
+
text: 'First reasoning step',
|
|
1040
|
+
providerOptions: {
|
|
1041
|
+
openai: {
|
|
1042
|
+
itemId: 'reasoning_001',
|
|
1043
|
+
},
|
|
1044
|
+
},
|
|
1045
|
+
},
|
|
1046
|
+
{
|
|
1047
|
+
type: 'reasoning',
|
|
1048
|
+
text: '', // Empty text should generate warning when appending to existing reasoning sequence
|
|
1049
|
+
providerOptions: {
|
|
1050
|
+
openai: {
|
|
1051
|
+
itemId: 'reasoning_001',
|
|
1052
|
+
},
|
|
1053
|
+
},
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
},
|
|
1057
|
+
],
|
|
1058
|
+
systemMessageMode: 'system',
|
|
1059
|
+
providerOptionsName: 'openai',
|
|
1060
|
+
store: false,
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
expect(result.input).toEqual([
|
|
1064
|
+
{
|
|
1065
|
+
type: 'reasoning',
|
|
1066
|
+
id: 'reasoning_001',
|
|
1067
|
+
encrypted_content: undefined,
|
|
1068
|
+
summary: [
|
|
1069
|
+
{
|
|
1070
|
+
type: 'summary_text',
|
|
1071
|
+
text: 'First reasoning step',
|
|
1072
|
+
},
|
|
1073
|
+
],
|
|
1074
|
+
},
|
|
1075
|
+
]);
|
|
1076
|
+
|
|
1077
|
+
expect(result.warnings).toMatchInlineSnapshot(`
|
|
1078
|
+
[
|
|
1079
|
+
{
|
|
1080
|
+
"message": "Cannot append empty reasoning part to existing reasoning sequence. Skipping reasoning part: {"type":"reasoning","text":"","providerOptions":{"openai":{"itemId":"reasoning_001"}}}.",
|
|
1081
|
+
"type": "other",
|
|
1082
|
+
},
|
|
1083
|
+
]
|
|
1084
|
+
`);
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
describe('merging and sequencing', () => {
|
|
1089
|
+
it('should merge consecutive parts with same reasoning ID', async () => {
|
|
1090
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1091
|
+
toolNameMapping: testToolNameMapping,
|
|
1092
|
+
prompt: [
|
|
1093
|
+
{
|
|
1094
|
+
role: 'assistant',
|
|
1095
|
+
content: [
|
|
1096
|
+
{
|
|
1097
|
+
type: 'reasoning',
|
|
1098
|
+
text: 'First reasoning step',
|
|
1099
|
+
providerOptions: {
|
|
1100
|
+
openai: {
|
|
1101
|
+
itemId: 'reasoning_001',
|
|
1102
|
+
},
|
|
1103
|
+
},
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
type: 'reasoning',
|
|
1107
|
+
text: 'Second reasoning step',
|
|
1108
|
+
providerOptions: {
|
|
1109
|
+
openai: {
|
|
1110
|
+
itemId: 'reasoning_001',
|
|
1111
|
+
// encrypted content is stored in the last summary part
|
|
1112
|
+
reasoningEncryptedContent: 'encrypted_content_001',
|
|
1113
|
+
},
|
|
1114
|
+
},
|
|
1115
|
+
},
|
|
1116
|
+
],
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
systemMessageMode: 'system',
|
|
1120
|
+
providerOptionsName: 'openai',
|
|
1121
|
+
store: false,
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1125
|
+
[
|
|
1126
|
+
{
|
|
1127
|
+
"encrypted_content": "encrypted_content_001",
|
|
1128
|
+
"id": "reasoning_001",
|
|
1129
|
+
"summary": [
|
|
1130
|
+
{
|
|
1131
|
+
"text": "First reasoning step",
|
|
1132
|
+
"type": "summary_text",
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
"text": "Second reasoning step",
|
|
1136
|
+
"type": "summary_text",
|
|
1137
|
+
},
|
|
1138
|
+
],
|
|
1139
|
+
"type": "reasoning",
|
|
1140
|
+
},
|
|
1141
|
+
]
|
|
1142
|
+
`);
|
|
1143
|
+
|
|
1144
|
+
expect(result.warnings).toHaveLength(0);
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
it('should create separate messages for different reasoning IDs', async () => {
|
|
1148
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1149
|
+
toolNameMapping: testToolNameMapping,
|
|
1150
|
+
prompt: [
|
|
1151
|
+
{
|
|
1152
|
+
role: 'assistant',
|
|
1153
|
+
content: [
|
|
1154
|
+
{
|
|
1155
|
+
type: 'reasoning',
|
|
1156
|
+
text: 'First reasoning block',
|
|
1157
|
+
providerOptions: {
|
|
1158
|
+
openai: {
|
|
1159
|
+
itemId: 'reasoning_001',
|
|
1160
|
+
},
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
type: 'reasoning',
|
|
1165
|
+
text: 'Second reasoning block',
|
|
1166
|
+
providerOptions: {
|
|
1167
|
+
openai: {
|
|
1168
|
+
itemId: 'reasoning_002',
|
|
1169
|
+
},
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
],
|
|
1173
|
+
},
|
|
1174
|
+
],
|
|
1175
|
+
systemMessageMode: 'system',
|
|
1176
|
+
providerOptionsName: 'openai',
|
|
1177
|
+
store: false,
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
expect(result.input).toEqual([
|
|
1181
|
+
{
|
|
1182
|
+
type: 'reasoning',
|
|
1183
|
+
id: 'reasoning_001',
|
|
1184
|
+
encrypted_content: undefined,
|
|
1185
|
+
summary: [
|
|
1186
|
+
{
|
|
1187
|
+
type: 'summary_text',
|
|
1188
|
+
text: 'First reasoning block',
|
|
1189
|
+
},
|
|
1190
|
+
],
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
type: 'reasoning',
|
|
1194
|
+
id: 'reasoning_002',
|
|
1195
|
+
encrypted_content: undefined,
|
|
1196
|
+
summary: [
|
|
1197
|
+
{
|
|
1198
|
+
type: 'summary_text',
|
|
1199
|
+
text: 'Second reasoning block',
|
|
1200
|
+
},
|
|
1201
|
+
],
|
|
1202
|
+
},
|
|
1203
|
+
]);
|
|
1204
|
+
|
|
1205
|
+
expect(result.warnings).toHaveLength(0);
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
it('should handle reasoning across multiple assistant messages', async () => {
|
|
1209
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1210
|
+
toolNameMapping: testToolNameMapping,
|
|
1211
|
+
prompt: [
|
|
1212
|
+
{
|
|
1213
|
+
role: 'user',
|
|
1214
|
+
content: [{ type: 'text', text: 'First user question' }],
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
role: 'assistant',
|
|
1218
|
+
content: [
|
|
1219
|
+
{
|
|
1220
|
+
type: 'reasoning',
|
|
1221
|
+
text: 'First reasoning step (message 1)',
|
|
1222
|
+
providerOptions: {
|
|
1223
|
+
openai: {
|
|
1224
|
+
itemId: 'reasoning_001',
|
|
1225
|
+
},
|
|
1226
|
+
},
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
type: 'reasoning',
|
|
1230
|
+
text: 'Second reasoning step (message 1)',
|
|
1231
|
+
providerOptions: {
|
|
1232
|
+
openai: {
|
|
1233
|
+
itemId: 'reasoning_001',
|
|
1234
|
+
},
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1237
|
+
{ type: 'text', text: 'First response' },
|
|
1238
|
+
],
|
|
1239
|
+
},
|
|
1240
|
+
{
|
|
1241
|
+
role: 'user',
|
|
1242
|
+
content: [{ type: 'text', text: 'Second user question' }],
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
role: 'assistant',
|
|
1246
|
+
content: [
|
|
1247
|
+
{
|
|
1248
|
+
type: 'reasoning',
|
|
1249
|
+
text: 'First reasoning step (message 2)',
|
|
1250
|
+
providerOptions: {
|
|
1251
|
+
openai: {
|
|
1252
|
+
itemId: 'reasoning_002',
|
|
1253
|
+
},
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
{ type: 'text', text: 'Second response' },
|
|
1257
|
+
],
|
|
1258
|
+
},
|
|
1259
|
+
],
|
|
1260
|
+
systemMessageMode: 'system',
|
|
1261
|
+
providerOptionsName: 'openai',
|
|
1262
|
+
store: true,
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1266
|
+
[
|
|
1267
|
+
{
|
|
1268
|
+
"content": [
|
|
1269
|
+
{
|
|
1270
|
+
"text": "First user question",
|
|
1271
|
+
"type": "input_text",
|
|
1272
|
+
},
|
|
1273
|
+
],
|
|
1274
|
+
"role": "user",
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
"id": "reasoning_001",
|
|
1278
|
+
"type": "item_reference",
|
|
1279
|
+
},
|
|
1280
|
+
{
|
|
1281
|
+
"content": [
|
|
1282
|
+
{
|
|
1283
|
+
"text": "First response",
|
|
1284
|
+
"type": "output_text",
|
|
1285
|
+
},
|
|
1286
|
+
],
|
|
1287
|
+
"id": undefined,
|
|
1288
|
+
"role": "assistant",
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
"content": [
|
|
1292
|
+
{
|
|
1293
|
+
"text": "Second user question",
|
|
1294
|
+
"type": "input_text",
|
|
1295
|
+
},
|
|
1296
|
+
],
|
|
1297
|
+
"role": "user",
|
|
1298
|
+
},
|
|
1299
|
+
{
|
|
1300
|
+
"id": "reasoning_002",
|
|
1301
|
+
"type": "item_reference",
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
"content": [
|
|
1305
|
+
{
|
|
1306
|
+
"text": "Second response",
|
|
1307
|
+
"type": "output_text",
|
|
1308
|
+
},
|
|
1309
|
+
],
|
|
1310
|
+
"id": undefined,
|
|
1311
|
+
"role": "assistant",
|
|
1312
|
+
},
|
|
1313
|
+
]
|
|
1314
|
+
`);
|
|
1315
|
+
|
|
1316
|
+
expect(result.warnings).toMatchInlineSnapshot(`[]`);
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
it('should handle reasoning across multiple assistant messages', async () => {
|
|
1320
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1321
|
+
toolNameMapping: testToolNameMapping,
|
|
1322
|
+
prompt: [
|
|
1323
|
+
{
|
|
1324
|
+
role: 'user',
|
|
1325
|
+
content: [{ type: 'text', text: 'First user question' }],
|
|
1326
|
+
},
|
|
1327
|
+
{
|
|
1328
|
+
role: 'assistant',
|
|
1329
|
+
content: [
|
|
1330
|
+
{
|
|
1331
|
+
type: 'reasoning',
|
|
1332
|
+
text: 'First reasoning step (message 1)',
|
|
1333
|
+
providerOptions: {
|
|
1334
|
+
openai: {
|
|
1335
|
+
itemId: 'reasoning_001',
|
|
1336
|
+
},
|
|
1337
|
+
},
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
type: 'reasoning',
|
|
1341
|
+
text: 'Second reasoning step (message 1)',
|
|
1342
|
+
providerOptions: {
|
|
1343
|
+
openai: {
|
|
1344
|
+
itemId: 'reasoning_001',
|
|
1345
|
+
},
|
|
1346
|
+
},
|
|
1347
|
+
},
|
|
1348
|
+
{ type: 'text', text: 'First response' },
|
|
1349
|
+
],
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
role: 'user',
|
|
1353
|
+
content: [{ type: 'text', text: 'Second user question' }],
|
|
1354
|
+
},
|
|
1355
|
+
{
|
|
1356
|
+
role: 'assistant',
|
|
1357
|
+
content: [
|
|
1358
|
+
{
|
|
1359
|
+
type: 'reasoning',
|
|
1360
|
+
text: 'First reasoning step (message 2)',
|
|
1361
|
+
providerOptions: {
|
|
1362
|
+
openai: {
|
|
1363
|
+
itemId: 'reasoning_002',
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
{ type: 'text', text: 'Second response' },
|
|
1368
|
+
],
|
|
1369
|
+
},
|
|
1370
|
+
],
|
|
1371
|
+
systemMessageMode: 'system',
|
|
1372
|
+
providerOptionsName: 'openai',
|
|
1373
|
+
store: false,
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
expect(result.input).toEqual([
|
|
1377
|
+
{
|
|
1378
|
+
role: 'user',
|
|
1379
|
+
content: [{ type: 'input_text', text: 'First user question' }],
|
|
1380
|
+
},
|
|
1381
|
+
{
|
|
1382
|
+
type: 'reasoning',
|
|
1383
|
+
id: 'reasoning_001',
|
|
1384
|
+
encrypted_content: undefined,
|
|
1385
|
+
summary: [
|
|
1386
|
+
{
|
|
1387
|
+
type: 'summary_text',
|
|
1388
|
+
text: 'First reasoning step (message 1)',
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
type: 'summary_text',
|
|
1392
|
+
text: 'Second reasoning step (message 1)',
|
|
1393
|
+
},
|
|
1394
|
+
],
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
role: 'assistant',
|
|
1398
|
+
content: [{ type: 'output_text', text: 'First response' }],
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
role: 'user',
|
|
1402
|
+
content: [{ type: 'input_text', text: 'Second user question' }],
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
type: 'reasoning',
|
|
1406
|
+
id: 'reasoning_002',
|
|
1407
|
+
encrypted_content: undefined,
|
|
1408
|
+
summary: [
|
|
1409
|
+
{
|
|
1410
|
+
type: 'summary_text',
|
|
1411
|
+
text: 'First reasoning step (message 2)',
|
|
1412
|
+
},
|
|
1413
|
+
],
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
role: 'assistant',
|
|
1417
|
+
content: [{ type: 'output_text', text: 'Second response' }],
|
|
1418
|
+
},
|
|
1419
|
+
]);
|
|
1420
|
+
|
|
1421
|
+
expect(result.warnings).toHaveLength(0);
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
it('should handle complex reasoning sequences with tool interactions', async () => {
|
|
1425
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1426
|
+
toolNameMapping: testToolNameMapping,
|
|
1427
|
+
prompt: [
|
|
1428
|
+
{
|
|
1429
|
+
role: 'assistant',
|
|
1430
|
+
content: [
|
|
1431
|
+
// First reasoning block: reasoning → reasoning
|
|
1432
|
+
{
|
|
1433
|
+
type: 'reasoning',
|
|
1434
|
+
text: 'Initial analysis step 1',
|
|
1435
|
+
providerOptions: {
|
|
1436
|
+
openai: {
|
|
1437
|
+
itemId: 'reasoning_001',
|
|
1438
|
+
reasoningEncryptedContent: 'encrypted_content_001',
|
|
1439
|
+
},
|
|
1440
|
+
},
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
type: 'reasoning',
|
|
1444
|
+
text: 'Initial analysis step 2',
|
|
1445
|
+
providerOptions: {
|
|
1446
|
+
openai: {
|
|
1447
|
+
itemId: 'reasoning_001',
|
|
1448
|
+
reasoningEncryptedContent: 'encrypted_content_001',
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1451
|
+
},
|
|
1452
|
+
// First tool interaction: tool-call
|
|
1453
|
+
{
|
|
1454
|
+
type: 'tool-call',
|
|
1455
|
+
toolCallId: 'call_001',
|
|
1456
|
+
toolName: 'search',
|
|
1457
|
+
input: { query: 'initial search' },
|
|
1458
|
+
},
|
|
1459
|
+
],
|
|
1460
|
+
},
|
|
1461
|
+
// Tool result comes as separate message
|
|
1462
|
+
{
|
|
1463
|
+
role: 'tool',
|
|
1464
|
+
content: [
|
|
1465
|
+
{
|
|
1466
|
+
type: 'tool-result',
|
|
1467
|
+
toolCallId: 'call_001',
|
|
1468
|
+
toolName: 'search',
|
|
1469
|
+
output: {
|
|
1470
|
+
type: 'json',
|
|
1471
|
+
value: { results: ['result1', 'result2'] },
|
|
1472
|
+
},
|
|
1473
|
+
},
|
|
1474
|
+
],
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
role: 'assistant',
|
|
1478
|
+
content: [
|
|
1479
|
+
// Second reasoning block: reasoning → reasoning → reasoning
|
|
1480
|
+
{
|
|
1481
|
+
type: 'reasoning',
|
|
1482
|
+
text: 'Processing results step 1',
|
|
1483
|
+
providerOptions: {
|
|
1484
|
+
openai: {
|
|
1485
|
+
itemId: 'reasoning_002',
|
|
1486
|
+
reasoningEncryptedContent: 'encrypted_content_002',
|
|
1487
|
+
},
|
|
1488
|
+
},
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
type: 'reasoning',
|
|
1492
|
+
text: 'Processing results step 2',
|
|
1493
|
+
providerOptions: {
|
|
1494
|
+
openai: {
|
|
1495
|
+
itemId: 'reasoning_002',
|
|
1496
|
+
reasoningEncryptedContent: 'encrypted_content_002',
|
|
1497
|
+
},
|
|
1498
|
+
},
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
type: 'reasoning',
|
|
1502
|
+
text: 'Processing results step 3',
|
|
1503
|
+
providerOptions: {
|
|
1504
|
+
openai: {
|
|
1505
|
+
itemId: 'reasoning_002',
|
|
1506
|
+
reasoningEncryptedContent: 'encrypted_content_002',
|
|
1507
|
+
},
|
|
1508
|
+
},
|
|
1509
|
+
},
|
|
1510
|
+
// Second tool interaction: tool-call
|
|
1511
|
+
{
|
|
1512
|
+
type: 'tool-call',
|
|
1513
|
+
toolCallId: 'call_002',
|
|
1514
|
+
toolName: 'calculator',
|
|
1515
|
+
input: { expression: '2 + 2' },
|
|
1516
|
+
},
|
|
1517
|
+
],
|
|
1518
|
+
},
|
|
1519
|
+
// Second tool result
|
|
1520
|
+
{
|
|
1521
|
+
role: 'tool',
|
|
1522
|
+
content: [
|
|
1523
|
+
{
|
|
1524
|
+
type: 'tool-result',
|
|
1525
|
+
toolCallId: 'call_002',
|
|
1526
|
+
toolName: 'calculator',
|
|
1527
|
+
output: {
|
|
1528
|
+
type: 'json',
|
|
1529
|
+
value: { result: 4 },
|
|
1530
|
+
},
|
|
1531
|
+
},
|
|
1532
|
+
],
|
|
1533
|
+
},
|
|
1534
|
+
{
|
|
1535
|
+
role: 'assistant',
|
|
1536
|
+
content: [
|
|
1537
|
+
// Final text output
|
|
1538
|
+
{
|
|
1539
|
+
type: 'text',
|
|
1540
|
+
text: 'Based on my analysis and calculations, here is the final answer.',
|
|
1541
|
+
},
|
|
1542
|
+
],
|
|
1543
|
+
},
|
|
1544
|
+
],
|
|
1545
|
+
systemMessageMode: 'system',
|
|
1546
|
+
providerOptionsName: 'openai',
|
|
1547
|
+
store: false,
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
expect(result.input).toEqual([
|
|
1551
|
+
// First reasoning block (2 parts merged)
|
|
1552
|
+
{
|
|
1553
|
+
type: 'reasoning',
|
|
1554
|
+
id: 'reasoning_001',
|
|
1555
|
+
encrypted_content: 'encrypted_content_001',
|
|
1556
|
+
summary: [
|
|
1557
|
+
{
|
|
1558
|
+
type: 'summary_text',
|
|
1559
|
+
text: 'Initial analysis step 1',
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
type: 'summary_text',
|
|
1563
|
+
text: 'Initial analysis step 2',
|
|
1564
|
+
},
|
|
1565
|
+
],
|
|
1566
|
+
},
|
|
1567
|
+
// First tool call
|
|
1568
|
+
{
|
|
1569
|
+
type: 'function_call',
|
|
1570
|
+
call_id: 'call_001',
|
|
1571
|
+
name: 'search',
|
|
1572
|
+
arguments: JSON.stringify({ query: 'initial search' }),
|
|
1573
|
+
},
|
|
1574
|
+
// First tool result
|
|
1575
|
+
{
|
|
1576
|
+
type: 'function_call_output',
|
|
1577
|
+
call_id: 'call_001',
|
|
1578
|
+
output: JSON.stringify({ results: ['result1', 'result2'] }),
|
|
1579
|
+
},
|
|
1580
|
+
// Second reasoning block (3 parts merged)
|
|
1581
|
+
{
|
|
1582
|
+
type: 'reasoning',
|
|
1583
|
+
id: 'reasoning_002',
|
|
1584
|
+
encrypted_content: 'encrypted_content_002',
|
|
1585
|
+
summary: [
|
|
1586
|
+
{
|
|
1587
|
+
type: 'summary_text',
|
|
1588
|
+
text: 'Processing results step 1',
|
|
1589
|
+
},
|
|
1590
|
+
{
|
|
1591
|
+
type: 'summary_text',
|
|
1592
|
+
text: 'Processing results step 2',
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
type: 'summary_text',
|
|
1596
|
+
text: 'Processing results step 3',
|
|
1597
|
+
},
|
|
1598
|
+
],
|
|
1599
|
+
},
|
|
1600
|
+
// Second tool call
|
|
1601
|
+
{
|
|
1602
|
+
type: 'function_call',
|
|
1603
|
+
call_id: 'call_002',
|
|
1604
|
+
name: 'calculator',
|
|
1605
|
+
arguments: JSON.stringify({ expression: '2 + 2' }),
|
|
1606
|
+
},
|
|
1607
|
+
// Second tool result
|
|
1608
|
+
{
|
|
1609
|
+
type: 'function_call_output',
|
|
1610
|
+
call_id: 'call_002',
|
|
1611
|
+
output: JSON.stringify({ result: 4 }),
|
|
1612
|
+
},
|
|
1613
|
+
// Final text output
|
|
1614
|
+
{
|
|
1615
|
+
role: 'assistant',
|
|
1616
|
+
content: [
|
|
1617
|
+
{
|
|
1618
|
+
type: 'output_text',
|
|
1619
|
+
text: 'Based on my analysis and calculations, here is the final answer.',
|
|
1620
|
+
},
|
|
1621
|
+
],
|
|
1622
|
+
},
|
|
1623
|
+
]);
|
|
1624
|
+
|
|
1625
|
+
expect(result.warnings).toHaveLength(0);
|
|
1626
|
+
});
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
describe('error handling', () => {
|
|
1630
|
+
it('should warn when reasoning part has no provider options', async () => {
|
|
1631
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1632
|
+
toolNameMapping: testToolNameMapping,
|
|
1633
|
+
prompt: [
|
|
1634
|
+
{
|
|
1635
|
+
role: 'assistant',
|
|
1636
|
+
content: [
|
|
1637
|
+
{
|
|
1638
|
+
type: 'reasoning',
|
|
1639
|
+
text: 'This is a reasoning part without any provider options',
|
|
1640
|
+
},
|
|
1641
|
+
],
|
|
1642
|
+
},
|
|
1643
|
+
],
|
|
1644
|
+
systemMessageMode: 'system',
|
|
1645
|
+
providerOptionsName: 'openai',
|
|
1646
|
+
store: false,
|
|
1647
|
+
});
|
|
1648
|
+
|
|
1649
|
+
expect(result.input).toHaveLength(0);
|
|
1650
|
+
|
|
1651
|
+
expect(result.warnings).toMatchInlineSnapshot(`
|
|
1652
|
+
[
|
|
1653
|
+
{
|
|
1654
|
+
"message": "Non-OpenAI reasoning parts are not supported. Skipping reasoning part: {"type":"reasoning","text":"This is a reasoning part without any provider options"}.",
|
|
1655
|
+
"type": "other",
|
|
1656
|
+
},
|
|
1657
|
+
]
|
|
1658
|
+
`);
|
|
1659
|
+
});
|
|
1660
|
+
|
|
1661
|
+
it('should warn when reasoning part lacks OpenAI-specific reasoning ID provider options', async () => {
|
|
1662
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1663
|
+
toolNameMapping: testToolNameMapping,
|
|
1664
|
+
prompt: [
|
|
1665
|
+
{
|
|
1666
|
+
role: 'assistant',
|
|
1667
|
+
content: [
|
|
1668
|
+
{
|
|
1669
|
+
type: 'reasoning',
|
|
1670
|
+
text: 'This is a reasoning part without OpenAI-specific reasoning id provider options',
|
|
1671
|
+
providerOptions: {
|
|
1672
|
+
openai: {
|
|
1673
|
+
reasoning: {
|
|
1674
|
+
encryptedContent: 'encrypted_content_001',
|
|
1675
|
+
},
|
|
1676
|
+
},
|
|
1677
|
+
},
|
|
1678
|
+
},
|
|
1679
|
+
],
|
|
1680
|
+
},
|
|
1681
|
+
],
|
|
1682
|
+
systemMessageMode: 'system',
|
|
1683
|
+
providerOptionsName: 'openai',
|
|
1684
|
+
store: false,
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
expect(result.input).toHaveLength(0);
|
|
1688
|
+
|
|
1689
|
+
expect(result.warnings).toMatchInlineSnapshot(`
|
|
1690
|
+
[
|
|
1691
|
+
{
|
|
1692
|
+
"message": "Non-OpenAI reasoning parts are not supported. Skipping reasoning part: {"type":"reasoning","text":"This is a reasoning part without OpenAI-specific reasoning id provider options","providerOptions":{"openai":{"reasoning":{"encryptedContent":"encrypted_content_001"}}}}.",
|
|
1693
|
+
"type": "other",
|
|
1694
|
+
},
|
|
1695
|
+
]
|
|
1696
|
+
`);
|
|
1697
|
+
});
|
|
1698
|
+
});
|
|
1699
|
+
});
|
|
1700
|
+
});
|
|
1701
|
+
|
|
1702
|
+
describe('tool messages', () => {
|
|
1703
|
+
it('should convert single tool result part with json value', async () => {
|
|
1704
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1705
|
+
toolNameMapping: testToolNameMapping,
|
|
1706
|
+
prompt: [
|
|
1707
|
+
{
|
|
1708
|
+
role: 'tool',
|
|
1709
|
+
content: [
|
|
1710
|
+
{
|
|
1711
|
+
type: 'tool-result',
|
|
1712
|
+
toolCallId: 'call_123',
|
|
1713
|
+
toolName: 'search',
|
|
1714
|
+
output: {
|
|
1715
|
+
type: 'json',
|
|
1716
|
+
value: { temperature: '72°F', condition: 'Sunny' },
|
|
1717
|
+
},
|
|
1718
|
+
},
|
|
1719
|
+
],
|
|
1720
|
+
},
|
|
1721
|
+
],
|
|
1722
|
+
systemMessageMode: 'system',
|
|
1723
|
+
providerOptionsName: 'openai',
|
|
1724
|
+
store: true,
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1728
|
+
[
|
|
1729
|
+
{
|
|
1730
|
+
"call_id": "call_123",
|
|
1731
|
+
"output": "{"temperature":"72°F","condition":"Sunny"}",
|
|
1732
|
+
"type": "function_call_output",
|
|
1733
|
+
},
|
|
1734
|
+
]
|
|
1735
|
+
`);
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
it('should convert single tool result part with text value', async () => {
|
|
1739
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1740
|
+
toolNameMapping: testToolNameMapping,
|
|
1741
|
+
prompt: [
|
|
1742
|
+
{
|
|
1743
|
+
role: 'tool',
|
|
1744
|
+
content: [
|
|
1745
|
+
{
|
|
1746
|
+
type: 'tool-result',
|
|
1747
|
+
toolCallId: 'call_123',
|
|
1748
|
+
toolName: 'search',
|
|
1749
|
+
output: {
|
|
1750
|
+
type: 'text',
|
|
1751
|
+
value: 'The weather in San Francisco is 72°F',
|
|
1752
|
+
},
|
|
1753
|
+
},
|
|
1754
|
+
],
|
|
1755
|
+
},
|
|
1756
|
+
],
|
|
1757
|
+
systemMessageMode: 'system',
|
|
1758
|
+
providerOptionsName: 'openai',
|
|
1759
|
+
store: true,
|
|
1760
|
+
});
|
|
1761
|
+
|
|
1762
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1763
|
+
[
|
|
1764
|
+
{
|
|
1765
|
+
"call_id": "call_123",
|
|
1766
|
+
"output": "The weather in San Francisco is 72°F",
|
|
1767
|
+
"type": "function_call_output",
|
|
1768
|
+
},
|
|
1769
|
+
]
|
|
1770
|
+
`);
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1773
|
+
it('should convert single tool result part with multipart that contains text', async () => {
|
|
1774
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1775
|
+
toolNameMapping: testToolNameMapping,
|
|
1776
|
+
prompt: [
|
|
1777
|
+
{
|
|
1778
|
+
role: 'tool',
|
|
1779
|
+
content: [
|
|
1780
|
+
{
|
|
1781
|
+
type: 'tool-result',
|
|
1782
|
+
toolCallId: 'call_123',
|
|
1783
|
+
toolName: 'search',
|
|
1784
|
+
output: {
|
|
1785
|
+
type: 'content',
|
|
1786
|
+
value: [
|
|
1787
|
+
{
|
|
1788
|
+
type: 'text',
|
|
1789
|
+
text: 'The weather in San Francisco is 72°F',
|
|
1790
|
+
},
|
|
1791
|
+
],
|
|
1792
|
+
},
|
|
1793
|
+
},
|
|
1794
|
+
],
|
|
1795
|
+
},
|
|
1796
|
+
],
|
|
1797
|
+
systemMessageMode: 'system',
|
|
1798
|
+
providerOptionsName: 'openai',
|
|
1799
|
+
store: true,
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1803
|
+
[
|
|
1804
|
+
{
|
|
1805
|
+
"call_id": "call_123",
|
|
1806
|
+
"output": [
|
|
1807
|
+
{
|
|
1808
|
+
"text": "The weather in San Francisco is 72°F",
|
|
1809
|
+
"type": "input_text",
|
|
1810
|
+
},
|
|
1811
|
+
],
|
|
1812
|
+
"type": "function_call_output",
|
|
1813
|
+
},
|
|
1814
|
+
]
|
|
1815
|
+
`);
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
it('should convert single tool result part with multipart that contains image', async () => {
|
|
1819
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1820
|
+
toolNameMapping: testToolNameMapping,
|
|
1821
|
+
prompt: [
|
|
1822
|
+
{
|
|
1823
|
+
role: 'tool',
|
|
1824
|
+
content: [
|
|
1825
|
+
{
|
|
1826
|
+
type: 'tool-result',
|
|
1827
|
+
toolCallId: 'call_123',
|
|
1828
|
+
toolName: 'search',
|
|
1829
|
+
output: {
|
|
1830
|
+
type: 'content',
|
|
1831
|
+
value: [
|
|
1832
|
+
{
|
|
1833
|
+
type: 'image-data',
|
|
1834
|
+
mediaType: 'image/png',
|
|
1835
|
+
data: 'base64_data',
|
|
1836
|
+
},
|
|
1837
|
+
],
|
|
1838
|
+
},
|
|
1839
|
+
},
|
|
1840
|
+
],
|
|
1841
|
+
},
|
|
1842
|
+
],
|
|
1843
|
+
systemMessageMode: 'system',
|
|
1844
|
+
providerOptionsName: 'openai',
|
|
1845
|
+
store: true,
|
|
1846
|
+
});
|
|
1847
|
+
|
|
1848
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1849
|
+
[
|
|
1850
|
+
{
|
|
1851
|
+
"call_id": "call_123",
|
|
1852
|
+
"output": [
|
|
1853
|
+
{
|
|
1854
|
+
"image_url": "data:image/png;base64,base64_data",
|
|
1855
|
+
"type": "input_image",
|
|
1856
|
+
},
|
|
1857
|
+
],
|
|
1858
|
+
"type": "function_call_output",
|
|
1859
|
+
},
|
|
1860
|
+
]
|
|
1861
|
+
`);
|
|
1862
|
+
});
|
|
1863
|
+
|
|
1864
|
+
it('should convert single tool result part with multipart that contains image URL', async () => {
|
|
1865
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1866
|
+
toolNameMapping: testToolNameMapping,
|
|
1867
|
+
prompt: [
|
|
1868
|
+
{
|
|
1869
|
+
role: 'tool',
|
|
1870
|
+
content: [
|
|
1871
|
+
{
|
|
1872
|
+
type: 'tool-result',
|
|
1873
|
+
toolCallId: 'call_123',
|
|
1874
|
+
toolName: 'screenshot',
|
|
1875
|
+
output: {
|
|
1876
|
+
type: 'content',
|
|
1877
|
+
value: [
|
|
1878
|
+
{
|
|
1879
|
+
type: 'image-url',
|
|
1880
|
+
url: 'https://example.com/screenshot.png',
|
|
1881
|
+
},
|
|
1882
|
+
],
|
|
1883
|
+
},
|
|
1884
|
+
},
|
|
1885
|
+
],
|
|
1886
|
+
},
|
|
1887
|
+
],
|
|
1888
|
+
systemMessageMode: 'system',
|
|
1889
|
+
providerOptionsName: 'openai',
|
|
1890
|
+
store: true,
|
|
1891
|
+
});
|
|
1892
|
+
|
|
1893
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1894
|
+
[
|
|
1895
|
+
{
|
|
1896
|
+
"call_id": "call_123",
|
|
1897
|
+
"output": [
|
|
1898
|
+
{
|
|
1899
|
+
"image_url": "https://example.com/screenshot.png",
|
|
1900
|
+
"type": "input_image",
|
|
1901
|
+
},
|
|
1902
|
+
],
|
|
1903
|
+
"type": "function_call_output",
|
|
1904
|
+
},
|
|
1905
|
+
]
|
|
1906
|
+
`);
|
|
1907
|
+
});
|
|
1908
|
+
|
|
1909
|
+
it('should convert single tool result part with multipart that contains file (PDF)', async () => {
|
|
1910
|
+
const base64Data = 'AQIDBAU=';
|
|
1911
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1912
|
+
toolNameMapping: testToolNameMapping,
|
|
1913
|
+
prompt: [
|
|
1914
|
+
{
|
|
1915
|
+
role: 'tool',
|
|
1916
|
+
content: [
|
|
1917
|
+
{
|
|
1918
|
+
type: 'tool-result',
|
|
1919
|
+
toolCallId: 'call_123',
|
|
1920
|
+
toolName: 'search',
|
|
1921
|
+
output: {
|
|
1922
|
+
type: 'content',
|
|
1923
|
+
value: [
|
|
1924
|
+
{
|
|
1925
|
+
type: 'file-data',
|
|
1926
|
+
mediaType: 'application/pdf',
|
|
1927
|
+
data: base64Data,
|
|
1928
|
+
filename: 'document.pdf',
|
|
1929
|
+
},
|
|
1930
|
+
],
|
|
1931
|
+
},
|
|
1932
|
+
},
|
|
1933
|
+
],
|
|
1934
|
+
},
|
|
1935
|
+
],
|
|
1936
|
+
systemMessageMode: 'system',
|
|
1937
|
+
providerOptionsName: 'openai',
|
|
1938
|
+
store: true,
|
|
1939
|
+
});
|
|
1940
|
+
|
|
1941
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1942
|
+
[
|
|
1943
|
+
{
|
|
1944
|
+
"call_id": "call_123",
|
|
1945
|
+
"output": [
|
|
1946
|
+
{
|
|
1947
|
+
"file_data": "data:application/pdf;base64,AQIDBAU=",
|
|
1948
|
+
"filename": "document.pdf",
|
|
1949
|
+
"type": "input_file",
|
|
1950
|
+
},
|
|
1951
|
+
],
|
|
1952
|
+
"type": "function_call_output",
|
|
1953
|
+
},
|
|
1954
|
+
]
|
|
1955
|
+
`);
|
|
1956
|
+
});
|
|
1957
|
+
|
|
1958
|
+
it('should convert single tool result part with multipart with mixed content (text, image, file)', async () => {
|
|
1959
|
+
const base64Data = 'AQIDBAU=';
|
|
1960
|
+
const result = await convertToOpenAIResponsesInput({
|
|
1961
|
+
toolNameMapping: testToolNameMapping,
|
|
1962
|
+
prompt: [
|
|
1963
|
+
{
|
|
1964
|
+
role: 'tool',
|
|
1965
|
+
content: [
|
|
1966
|
+
{
|
|
1967
|
+
type: 'tool-result',
|
|
1968
|
+
toolCallId: 'call_123',
|
|
1969
|
+
toolName: 'search',
|
|
1970
|
+
output: {
|
|
1971
|
+
type: 'content',
|
|
1972
|
+
value: [
|
|
1973
|
+
{
|
|
1974
|
+
type: 'text',
|
|
1975
|
+
text: 'The weather in San Francisco is 72°F',
|
|
1976
|
+
},
|
|
1977
|
+
{
|
|
1978
|
+
type: 'image-data',
|
|
1979
|
+
mediaType: 'image/png',
|
|
1980
|
+
data: 'base64_data',
|
|
1981
|
+
},
|
|
1982
|
+
{
|
|
1983
|
+
type: 'file-data',
|
|
1984
|
+
mediaType: 'application/pdf',
|
|
1985
|
+
data: base64Data,
|
|
1986
|
+
},
|
|
1987
|
+
],
|
|
1988
|
+
},
|
|
1989
|
+
},
|
|
1990
|
+
],
|
|
1991
|
+
},
|
|
1992
|
+
],
|
|
1993
|
+
systemMessageMode: 'system',
|
|
1994
|
+
providerOptionsName: 'openai',
|
|
1995
|
+
store: true,
|
|
1996
|
+
});
|
|
1997
|
+
|
|
1998
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
1999
|
+
[
|
|
2000
|
+
{
|
|
2001
|
+
"call_id": "call_123",
|
|
2002
|
+
"output": [
|
|
2003
|
+
{
|
|
2004
|
+
"text": "The weather in San Francisco is 72°F",
|
|
2005
|
+
"type": "input_text",
|
|
2006
|
+
},
|
|
2007
|
+
{
|
|
2008
|
+
"image_url": "data:image/png;base64,base64_data",
|
|
2009
|
+
"type": "input_image",
|
|
2010
|
+
},
|
|
2011
|
+
{
|
|
2012
|
+
"file_data": "data:application/pdf;base64,AQIDBAU=",
|
|
2013
|
+
"filename": "data",
|
|
2014
|
+
"type": "input_file",
|
|
2015
|
+
},
|
|
2016
|
+
],
|
|
2017
|
+
"type": "function_call_output",
|
|
2018
|
+
},
|
|
2019
|
+
]
|
|
2020
|
+
`);
|
|
2021
|
+
});
|
|
2022
|
+
|
|
2023
|
+
it('should convert multiple tool result parts in a single message', async () => {
|
|
2024
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2025
|
+
toolNameMapping: testToolNameMapping,
|
|
2026
|
+
prompt: [
|
|
2027
|
+
{
|
|
2028
|
+
role: 'tool',
|
|
2029
|
+
content: [
|
|
2030
|
+
{
|
|
2031
|
+
type: 'tool-result',
|
|
2032
|
+
toolCallId: 'call_123',
|
|
2033
|
+
toolName: 'search',
|
|
2034
|
+
output: {
|
|
2035
|
+
type: 'json',
|
|
2036
|
+
value: { temperature: '72°F', condition: 'Sunny' },
|
|
2037
|
+
},
|
|
2038
|
+
},
|
|
2039
|
+
{
|
|
2040
|
+
type: 'tool-result',
|
|
2041
|
+
toolCallId: 'call_456',
|
|
2042
|
+
toolName: 'calculator',
|
|
2043
|
+
output: { type: 'json', value: 4 },
|
|
2044
|
+
},
|
|
2045
|
+
],
|
|
2046
|
+
},
|
|
2047
|
+
],
|
|
2048
|
+
systemMessageMode: 'system',
|
|
2049
|
+
providerOptionsName: 'openai',
|
|
2050
|
+
store: true,
|
|
2051
|
+
});
|
|
2052
|
+
|
|
2053
|
+
expect(result.input).toEqual([
|
|
2054
|
+
{
|
|
2055
|
+
type: 'function_call_output',
|
|
2056
|
+
call_id: 'call_123',
|
|
2057
|
+
output: JSON.stringify({ temperature: '72°F', condition: 'Sunny' }),
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
type: 'function_call_output',
|
|
2061
|
+
call_id: 'call_456',
|
|
2062
|
+
output: JSON.stringify(4),
|
|
2063
|
+
},
|
|
2064
|
+
]);
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
|
|
2068
|
+
describe('provider-defined tools', () => {
|
|
2069
|
+
it('should convert single provider-executed tool call and result into item reference with store: true', async () => {
|
|
2070
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2071
|
+
toolNameMapping: testToolNameMapping,
|
|
2072
|
+
prompt: [
|
|
2073
|
+
{
|
|
2074
|
+
role: 'assistant',
|
|
2075
|
+
content: [
|
|
2076
|
+
{
|
|
2077
|
+
input: { code: 'example code', containerId: 'container_123' },
|
|
2078
|
+
providerExecuted: true,
|
|
2079
|
+
toolCallId:
|
|
2080
|
+
'ci_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f',
|
|
2081
|
+
toolName: 'code_interpreter',
|
|
2082
|
+
type: 'tool-call',
|
|
2083
|
+
},
|
|
2084
|
+
{
|
|
2085
|
+
output: {
|
|
2086
|
+
type: 'json',
|
|
2087
|
+
value: {
|
|
2088
|
+
outputs: [{ type: 'logs', logs: 'example logs' }],
|
|
2089
|
+
},
|
|
2090
|
+
},
|
|
2091
|
+
toolCallId:
|
|
2092
|
+
'ci_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f',
|
|
2093
|
+
toolName: 'code_interpreter',
|
|
2094
|
+
type: 'tool-result',
|
|
2095
|
+
},
|
|
2096
|
+
],
|
|
2097
|
+
},
|
|
2098
|
+
],
|
|
2099
|
+
systemMessageMode: 'system',
|
|
2100
|
+
providerOptionsName: 'openai',
|
|
2101
|
+
store: true,
|
|
2102
|
+
});
|
|
2103
|
+
|
|
2104
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2105
|
+
[
|
|
2106
|
+
{
|
|
2107
|
+
"id": "ci_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f",
|
|
2108
|
+
"type": "item_reference",
|
|
2109
|
+
},
|
|
2110
|
+
]
|
|
2111
|
+
`);
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
it('should exclude provider-executed tool calls and results from prompt with store: false', async () => {
|
|
2115
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2116
|
+
toolNameMapping: testToolNameMapping,
|
|
2117
|
+
prompt: [
|
|
2118
|
+
{
|
|
2119
|
+
role: 'assistant',
|
|
2120
|
+
content: [
|
|
2121
|
+
{
|
|
2122
|
+
type: 'text',
|
|
2123
|
+
text: 'Let me search for recent news from San Francisco.',
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
type: 'tool-call',
|
|
2127
|
+
toolCallId: 'ws_67cf2b3051e88190b006770db6fdb13d',
|
|
2128
|
+
toolName: 'web_search',
|
|
2129
|
+
input: {
|
|
2130
|
+
query: 'San Francisco major news events June 22 2025',
|
|
2131
|
+
},
|
|
2132
|
+
providerExecuted: true,
|
|
2133
|
+
},
|
|
2134
|
+
{
|
|
2135
|
+
type: 'tool-result',
|
|
2136
|
+
toolCallId: 'ws_67cf2b3051e88190b006770db6fdb13d',
|
|
2137
|
+
toolName: 'web_search',
|
|
2138
|
+
output: {
|
|
2139
|
+
type: 'json',
|
|
2140
|
+
value: {
|
|
2141
|
+
action: {
|
|
2142
|
+
type: 'search',
|
|
2143
|
+
query: 'San Francisco major news events June 22 2025',
|
|
2144
|
+
},
|
|
2145
|
+
sources: [
|
|
2146
|
+
{
|
|
2147
|
+
type: 'url',
|
|
2148
|
+
url: 'https://patch.com/california/san-francisco/calendar',
|
|
2149
|
+
},
|
|
2150
|
+
],
|
|
2151
|
+
},
|
|
2152
|
+
},
|
|
2153
|
+
},
|
|
2154
|
+
{
|
|
2155
|
+
type: 'text',
|
|
2156
|
+
text: 'Based on the search results, several significant events took place in San Francisco yesterday (June 22, 2025).',
|
|
2157
|
+
},
|
|
2158
|
+
],
|
|
2159
|
+
},
|
|
2160
|
+
],
|
|
2161
|
+
systemMessageMode: 'system',
|
|
2162
|
+
providerOptionsName: 'openai',
|
|
2163
|
+
store: false,
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
expect(result).toMatchInlineSnapshot(`
|
|
2167
|
+
{
|
|
2168
|
+
"input": [
|
|
2169
|
+
{
|
|
2170
|
+
"content": [
|
|
2171
|
+
{
|
|
2172
|
+
"text": "Let me search for recent news from San Francisco.",
|
|
2173
|
+
"type": "output_text",
|
|
2174
|
+
},
|
|
2175
|
+
],
|
|
2176
|
+
"id": undefined,
|
|
2177
|
+
"role": "assistant",
|
|
2178
|
+
},
|
|
2179
|
+
{
|
|
2180
|
+
"content": [
|
|
2181
|
+
{
|
|
2182
|
+
"text": "Based on the search results, several significant events took place in San Francisco yesterday (June 22, 2025).",
|
|
2183
|
+
"type": "output_text",
|
|
2184
|
+
},
|
|
2185
|
+
],
|
|
2186
|
+
"id": undefined,
|
|
2187
|
+
"role": "assistant",
|
|
2188
|
+
},
|
|
2189
|
+
],
|
|
2190
|
+
"warnings": [
|
|
2191
|
+
{
|
|
2192
|
+
"message": "Results for OpenAI tool web_search are not sent to the API when store is false",
|
|
2193
|
+
"type": "other",
|
|
2194
|
+
},
|
|
2195
|
+
],
|
|
2196
|
+
}
|
|
2197
|
+
`);
|
|
2198
|
+
});
|
|
2199
|
+
|
|
2200
|
+
describe('local shell', () => {
|
|
2201
|
+
it('should convert local shell tool call and result into item reference with store: true', async () => {
|
|
2202
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2203
|
+
toolNameMapping: testToolNameMapping,
|
|
2204
|
+
prompt: [
|
|
2205
|
+
{
|
|
2206
|
+
role: 'assistant',
|
|
2207
|
+
content: [
|
|
2208
|
+
{
|
|
2209
|
+
type: 'tool-call',
|
|
2210
|
+
toolCallId: 'call_XWgeTylovOiS8xLNz2TONOgO',
|
|
2211
|
+
toolName: 'local_shell',
|
|
2212
|
+
input: { action: { type: 'exec', command: ['ls'] } },
|
|
2213
|
+
providerOptions: {
|
|
2214
|
+
openai: {
|
|
2215
|
+
itemId:
|
|
2216
|
+
'lsh_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f',
|
|
2217
|
+
},
|
|
2218
|
+
},
|
|
2219
|
+
},
|
|
2220
|
+
],
|
|
2221
|
+
},
|
|
2222
|
+
{
|
|
2223
|
+
role: 'tool',
|
|
2224
|
+
content: [
|
|
2225
|
+
{
|
|
2226
|
+
type: 'tool-result',
|
|
2227
|
+
toolCallId: 'call_XWgeTylovOiS8xLNz2TONOgO',
|
|
2228
|
+
toolName: 'local_shell',
|
|
2229
|
+
output: { type: 'json', value: { output: 'example output' } },
|
|
2230
|
+
},
|
|
2231
|
+
],
|
|
2232
|
+
},
|
|
2233
|
+
],
|
|
2234
|
+
systemMessageMode: 'system',
|
|
2235
|
+
providerOptionsName: 'openai',
|
|
2236
|
+
store: true,
|
|
2237
|
+
hasLocalShellTool: true,
|
|
2238
|
+
});
|
|
2239
|
+
|
|
2240
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2241
|
+
[
|
|
2242
|
+
{
|
|
2243
|
+
"id": "lsh_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f",
|
|
2244
|
+
"type": "item_reference",
|
|
2245
|
+
},
|
|
2246
|
+
{
|
|
2247
|
+
"call_id": "call_XWgeTylovOiS8xLNz2TONOgO",
|
|
2248
|
+
"output": "example output",
|
|
2249
|
+
"type": "local_shell_call_output",
|
|
2250
|
+
},
|
|
2251
|
+
]
|
|
2252
|
+
`);
|
|
2253
|
+
});
|
|
2254
|
+
|
|
2255
|
+
it('should convert local shell tool call and result into item reference with store: false', async () => {
|
|
2256
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2257
|
+
toolNameMapping: testToolNameMapping,
|
|
2258
|
+
prompt: [
|
|
2259
|
+
{
|
|
2260
|
+
role: 'assistant',
|
|
2261
|
+
content: [
|
|
2262
|
+
{
|
|
2263
|
+
type: 'tool-call',
|
|
2264
|
+
toolCallId: 'call_XWgeTylovOiS8xLNz2TONOgO',
|
|
2265
|
+
toolName: 'local_shell',
|
|
2266
|
+
input: { action: { type: 'exec', command: ['ls'] } },
|
|
2267
|
+
providerOptions: {
|
|
2268
|
+
openai: {
|
|
2269
|
+
itemId:
|
|
2270
|
+
'lsh_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f',
|
|
2271
|
+
},
|
|
2272
|
+
},
|
|
2273
|
+
},
|
|
2274
|
+
],
|
|
2275
|
+
},
|
|
2276
|
+
{
|
|
2277
|
+
role: 'tool',
|
|
2278
|
+
content: [
|
|
2279
|
+
{
|
|
2280
|
+
type: 'tool-result',
|
|
2281
|
+
toolCallId: 'call_XWgeTylovOiS8xLNz2TONOgO',
|
|
2282
|
+
toolName: 'local_shell',
|
|
2283
|
+
output: { type: 'json', value: { output: 'example output' } },
|
|
2284
|
+
},
|
|
2285
|
+
],
|
|
2286
|
+
},
|
|
2287
|
+
],
|
|
2288
|
+
systemMessageMode: 'system',
|
|
2289
|
+
providerOptionsName: 'openai',
|
|
2290
|
+
store: false,
|
|
2291
|
+
hasLocalShellTool: true,
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2295
|
+
[
|
|
2296
|
+
{
|
|
2297
|
+
"action": {
|
|
2298
|
+
"command": [
|
|
2299
|
+
"ls",
|
|
2300
|
+
],
|
|
2301
|
+
"env": undefined,
|
|
2302
|
+
"timeout_ms": undefined,
|
|
2303
|
+
"type": "exec",
|
|
2304
|
+
"user": undefined,
|
|
2305
|
+
"working_directory": undefined,
|
|
2306
|
+
},
|
|
2307
|
+
"call_id": "call_XWgeTylovOiS8xLNz2TONOgO",
|
|
2308
|
+
"id": "lsh_68c2e2cf522c81908f3e2c1bccd1493b0b24aae9c6c01e4f",
|
|
2309
|
+
"type": "local_shell_call",
|
|
2310
|
+
},
|
|
2311
|
+
{
|
|
2312
|
+
"call_id": "call_XWgeTylovOiS8xLNz2TONOgO",
|
|
2313
|
+
"output": "example output",
|
|
2314
|
+
"type": "local_shell_call_output",
|
|
2315
|
+
},
|
|
2316
|
+
]
|
|
2317
|
+
`);
|
|
2318
|
+
});
|
|
2319
|
+
});
|
|
2320
|
+
});
|
|
2321
|
+
|
|
2322
|
+
describe('provider tool outputs', () => {
|
|
2323
|
+
it('should include apply_patch output when multiple tool results are present', async () => {
|
|
2324
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2325
|
+
toolNameMapping: testToolNameMapping,
|
|
2326
|
+
prompt: [
|
|
2327
|
+
{
|
|
2328
|
+
role: 'tool',
|
|
2329
|
+
content: [
|
|
2330
|
+
{
|
|
2331
|
+
type: 'tool-result',
|
|
2332
|
+
toolCallId: 'call-shell',
|
|
2333
|
+
toolName: 'shell',
|
|
2334
|
+
output: {
|
|
2335
|
+
type: 'json',
|
|
2336
|
+
value: {
|
|
2337
|
+
output: [
|
|
2338
|
+
{
|
|
2339
|
+
stdout: 'hi\n',
|
|
2340
|
+
stderr: '',
|
|
2341
|
+
outcome: { type: 'exit', exitCode: 0 },
|
|
2342
|
+
},
|
|
2343
|
+
],
|
|
2344
|
+
},
|
|
2345
|
+
},
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
type: 'tool-result',
|
|
2349
|
+
toolCallId: 'call-apply',
|
|
2350
|
+
toolName: 'apply_patch',
|
|
2351
|
+
output: {
|
|
2352
|
+
type: 'json',
|
|
2353
|
+
value: {
|
|
2354
|
+
status: 'completed',
|
|
2355
|
+
output: 'patched',
|
|
2356
|
+
},
|
|
2357
|
+
},
|
|
2358
|
+
},
|
|
2359
|
+
],
|
|
2360
|
+
},
|
|
2361
|
+
],
|
|
2362
|
+
systemMessageMode: 'system',
|
|
2363
|
+
providerOptionsName: 'openai',
|
|
2364
|
+
store: true,
|
|
2365
|
+
hasShellTool: true,
|
|
2366
|
+
hasApplyPatchTool: true,
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
expect(result.input).toEqual([
|
|
2370
|
+
{
|
|
2371
|
+
type: 'shell_call_output',
|
|
2372
|
+
call_id: 'call-shell',
|
|
2373
|
+
output: [
|
|
2374
|
+
{
|
|
2375
|
+
stdout: 'hi\n',
|
|
2376
|
+
stderr: '',
|
|
2377
|
+
outcome: { type: 'exit', exit_code: 0 },
|
|
2378
|
+
},
|
|
2379
|
+
],
|
|
2380
|
+
},
|
|
2381
|
+
{
|
|
2382
|
+
type: 'apply_patch_call_output',
|
|
2383
|
+
call_id: 'call-apply',
|
|
2384
|
+
status: 'completed',
|
|
2385
|
+
output: 'patched',
|
|
2386
|
+
},
|
|
2387
|
+
]);
|
|
2388
|
+
});
|
|
2389
|
+
});
|
|
2390
|
+
|
|
2391
|
+
describe('function tools', () => {
|
|
2392
|
+
it('should include client-side tool calls in prompt', async () => {
|
|
2393
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2394
|
+
prompt: [
|
|
2395
|
+
{
|
|
2396
|
+
role: 'assistant',
|
|
2397
|
+
content: [
|
|
2398
|
+
{
|
|
2399
|
+
type: 'tool-call',
|
|
2400
|
+
toolCallId: 'call-1',
|
|
2401
|
+
toolName: 'calculator',
|
|
2402
|
+
input: { a: 1, b: 2 },
|
|
2403
|
+
providerExecuted: false,
|
|
2404
|
+
},
|
|
2405
|
+
],
|
|
2406
|
+
},
|
|
2407
|
+
],
|
|
2408
|
+
toolNameMapping: testToolNameMapping,
|
|
2409
|
+
systemMessageMode: 'system',
|
|
2410
|
+
providerOptionsName: 'openai',
|
|
2411
|
+
store: true,
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
expect(result).toMatchInlineSnapshot(`
|
|
2415
|
+
{
|
|
2416
|
+
"input": [
|
|
2417
|
+
{
|
|
2418
|
+
"arguments": "{"a":1,"b":2}",
|
|
2419
|
+
"call_id": "call-1",
|
|
2420
|
+
"id": undefined,
|
|
2421
|
+
"name": "calculator",
|
|
2422
|
+
"type": "function_call",
|
|
2423
|
+
},
|
|
2424
|
+
],
|
|
2425
|
+
"warnings": [],
|
|
2426
|
+
}
|
|
2427
|
+
`);
|
|
2428
|
+
});
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
describe('MCP tool approval responses', () => {
|
|
2432
|
+
it('should convert approved tool-approval-response to mcp_approval_response with store: true', async () => {
|
|
2433
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2434
|
+
toolNameMapping: testToolNameMapping,
|
|
2435
|
+
prompt: [
|
|
2436
|
+
{
|
|
2437
|
+
role: 'tool',
|
|
2438
|
+
content: [
|
|
2439
|
+
{
|
|
2440
|
+
type: 'tool-approval-response',
|
|
2441
|
+
approvalId: 'mcp-approval-123',
|
|
2442
|
+
approved: true,
|
|
2443
|
+
},
|
|
2444
|
+
],
|
|
2445
|
+
},
|
|
2446
|
+
],
|
|
2447
|
+
systemMessageMode: 'system',
|
|
2448
|
+
providerOptionsName: 'openai',
|
|
2449
|
+
store: true,
|
|
2450
|
+
});
|
|
2451
|
+
|
|
2452
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2453
|
+
[
|
|
2454
|
+
{
|
|
2455
|
+
"id": "mcp-approval-123",
|
|
2456
|
+
"type": "item_reference",
|
|
2457
|
+
},
|
|
2458
|
+
{
|
|
2459
|
+
"approval_request_id": "mcp-approval-123",
|
|
2460
|
+
"approve": true,
|
|
2461
|
+
"type": "mcp_approval_response",
|
|
2462
|
+
},
|
|
2463
|
+
]
|
|
2464
|
+
`);
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
it('should convert denied tool-approval-response to mcp_approval_response with store: true', async () => {
|
|
2468
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2469
|
+
toolNameMapping: testToolNameMapping,
|
|
2470
|
+
prompt: [
|
|
2471
|
+
{
|
|
2472
|
+
role: 'tool',
|
|
2473
|
+
content: [
|
|
2474
|
+
{
|
|
2475
|
+
type: 'tool-approval-response',
|
|
2476
|
+
approvalId: 'mcp-approval-456',
|
|
2477
|
+
approved: false,
|
|
2478
|
+
},
|
|
2479
|
+
],
|
|
2480
|
+
},
|
|
2481
|
+
],
|
|
2482
|
+
systemMessageMode: 'system',
|
|
2483
|
+
providerOptionsName: 'openai',
|
|
2484
|
+
store: true,
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2488
|
+
[
|
|
2489
|
+
{
|
|
2490
|
+
"id": "mcp-approval-456",
|
|
2491
|
+
"type": "item_reference",
|
|
2492
|
+
},
|
|
2493
|
+
{
|
|
2494
|
+
"approval_request_id": "mcp-approval-456",
|
|
2495
|
+
"approve": false,
|
|
2496
|
+
"type": "mcp_approval_response",
|
|
2497
|
+
},
|
|
2498
|
+
]
|
|
2499
|
+
`);
|
|
2500
|
+
});
|
|
2501
|
+
|
|
2502
|
+
it('should convert tool-approval-response to mcp_approval_response without item_reference when store: false', async () => {
|
|
2503
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2504
|
+
toolNameMapping: testToolNameMapping,
|
|
2505
|
+
prompt: [
|
|
2506
|
+
{
|
|
2507
|
+
role: 'tool',
|
|
2508
|
+
content: [
|
|
2509
|
+
{
|
|
2510
|
+
type: 'tool-approval-response',
|
|
2511
|
+
approvalId: 'mcp-approval-789',
|
|
2512
|
+
approved: true,
|
|
2513
|
+
},
|
|
2514
|
+
],
|
|
2515
|
+
},
|
|
2516
|
+
],
|
|
2517
|
+
systemMessageMode: 'system',
|
|
2518
|
+
providerOptionsName: 'openai',
|
|
2519
|
+
store: false,
|
|
2520
|
+
});
|
|
2521
|
+
|
|
2522
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2523
|
+
[
|
|
2524
|
+
{
|
|
2525
|
+
"approval_request_id": "mcp-approval-789",
|
|
2526
|
+
"approve": true,
|
|
2527
|
+
"type": "mcp_approval_response",
|
|
2528
|
+
},
|
|
2529
|
+
]
|
|
2530
|
+
`);
|
|
2531
|
+
});
|
|
2532
|
+
|
|
2533
|
+
it('should skip duplicate tool-approval-response with same approvalId', async () => {
|
|
2534
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2535
|
+
toolNameMapping: testToolNameMapping,
|
|
2536
|
+
prompt: [
|
|
2537
|
+
{
|
|
2538
|
+
role: 'tool',
|
|
2539
|
+
content: [
|
|
2540
|
+
{
|
|
2541
|
+
type: 'tool-approval-response',
|
|
2542
|
+
approvalId: 'duplicate-approval',
|
|
2543
|
+
approved: true,
|
|
2544
|
+
},
|
|
2545
|
+
{
|
|
2546
|
+
type: 'tool-approval-response',
|
|
2547
|
+
approvalId: 'duplicate-approval',
|
|
2548
|
+
approved: true,
|
|
2549
|
+
},
|
|
2550
|
+
],
|
|
2551
|
+
},
|
|
2552
|
+
],
|
|
2553
|
+
systemMessageMode: 'system',
|
|
2554
|
+
providerOptionsName: 'openai',
|
|
2555
|
+
store: true,
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2559
|
+
[
|
|
2560
|
+
{
|
|
2561
|
+
"id": "duplicate-approval",
|
|
2562
|
+
"type": "item_reference",
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
"approval_request_id": "duplicate-approval",
|
|
2566
|
+
"approve": true,
|
|
2567
|
+
"type": "mcp_approval_response",
|
|
2568
|
+
},
|
|
2569
|
+
]
|
|
2570
|
+
`);
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
it('should handle multiple different tool-approval-responses', async () => {
|
|
2574
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2575
|
+
toolNameMapping: testToolNameMapping,
|
|
2576
|
+
prompt: [
|
|
2577
|
+
{
|
|
2578
|
+
role: 'tool',
|
|
2579
|
+
content: [
|
|
2580
|
+
{
|
|
2581
|
+
type: 'tool-approval-response',
|
|
2582
|
+
approvalId: 'approval-1',
|
|
2583
|
+
approved: true,
|
|
2584
|
+
},
|
|
2585
|
+
{
|
|
2586
|
+
type: 'tool-approval-response',
|
|
2587
|
+
approvalId: 'approval-2',
|
|
2588
|
+
approved: false,
|
|
2589
|
+
},
|
|
2590
|
+
],
|
|
2591
|
+
},
|
|
2592
|
+
],
|
|
2593
|
+
systemMessageMode: 'system',
|
|
2594
|
+
providerOptionsName: 'openai',
|
|
2595
|
+
store: true,
|
|
2596
|
+
});
|
|
2597
|
+
|
|
2598
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2599
|
+
[
|
|
2600
|
+
{
|
|
2601
|
+
"id": "approval-1",
|
|
2602
|
+
"type": "item_reference",
|
|
2603
|
+
},
|
|
2604
|
+
{
|
|
2605
|
+
"approval_request_id": "approval-1",
|
|
2606
|
+
"approve": true,
|
|
2607
|
+
"type": "mcp_approval_response",
|
|
2608
|
+
},
|
|
2609
|
+
{
|
|
2610
|
+
"id": "approval-2",
|
|
2611
|
+
"type": "item_reference",
|
|
2612
|
+
},
|
|
2613
|
+
{
|
|
2614
|
+
"approval_request_id": "approval-2",
|
|
2615
|
+
"approve": false,
|
|
2616
|
+
"type": "mcp_approval_response",
|
|
2617
|
+
},
|
|
2618
|
+
]
|
|
2619
|
+
`);
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2622
|
+
it('should skip execution-denied output when it has approvalId in providerOptions', async () => {
|
|
2623
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2624
|
+
toolNameMapping: testToolNameMapping,
|
|
2625
|
+
prompt: [
|
|
2626
|
+
{
|
|
2627
|
+
role: 'tool',
|
|
2628
|
+
content: [
|
|
2629
|
+
{
|
|
2630
|
+
type: 'tool-approval-response',
|
|
2631
|
+
approvalId: 'denied-approval',
|
|
2632
|
+
approved: false,
|
|
2633
|
+
},
|
|
2634
|
+
{
|
|
2635
|
+
type: 'tool-result',
|
|
2636
|
+
toolCallId: 'call-123',
|
|
2637
|
+
toolName: 'mcp_tool',
|
|
2638
|
+
output: {
|
|
2639
|
+
type: 'execution-denied',
|
|
2640
|
+
reason: 'User denied the tool execution',
|
|
2641
|
+
providerOptions: {
|
|
2642
|
+
openai: {
|
|
2643
|
+
approvalId: 'denied-approval',
|
|
2644
|
+
},
|
|
2645
|
+
},
|
|
2646
|
+
},
|
|
2647
|
+
},
|
|
2648
|
+
],
|
|
2649
|
+
},
|
|
2650
|
+
],
|
|
2651
|
+
systemMessageMode: 'system',
|
|
2652
|
+
providerOptionsName: 'openai',
|
|
2653
|
+
store: true,
|
|
2654
|
+
});
|
|
2655
|
+
|
|
2656
|
+
// Only the mcp_approval_response should be present, not a function_call_output
|
|
2657
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2658
|
+
[
|
|
2659
|
+
{
|
|
2660
|
+
"id": "denied-approval",
|
|
2661
|
+
"type": "item_reference",
|
|
2662
|
+
},
|
|
2663
|
+
{
|
|
2664
|
+
"approval_request_id": "denied-approval",
|
|
2665
|
+
"approve": false,
|
|
2666
|
+
"type": "mcp_approval_response",
|
|
2667
|
+
},
|
|
2668
|
+
]
|
|
2669
|
+
`);
|
|
2670
|
+
});
|
|
2671
|
+
|
|
2672
|
+
it('should handle tool-approval-response mixed with regular tool results', async () => {
|
|
2673
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2674
|
+
toolNameMapping: testToolNameMapping,
|
|
2675
|
+
prompt: [
|
|
2676
|
+
{
|
|
2677
|
+
role: 'tool',
|
|
2678
|
+
content: [
|
|
2679
|
+
{
|
|
2680
|
+
type: 'tool-approval-response',
|
|
2681
|
+
approvalId: 'approval-for-mcp',
|
|
2682
|
+
approved: true,
|
|
2683
|
+
},
|
|
2684
|
+
{
|
|
2685
|
+
type: 'tool-result',
|
|
2686
|
+
toolCallId: 'regular-call-1',
|
|
2687
|
+
toolName: 'calculator',
|
|
2688
|
+
output: {
|
|
2689
|
+
type: 'json',
|
|
2690
|
+
value: { result: 42 },
|
|
2691
|
+
},
|
|
2692
|
+
},
|
|
2693
|
+
],
|
|
2694
|
+
},
|
|
2695
|
+
],
|
|
2696
|
+
systemMessageMode: 'system',
|
|
2697
|
+
providerOptionsName: 'openai',
|
|
2698
|
+
store: true,
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2701
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2702
|
+
[
|
|
2703
|
+
{
|
|
2704
|
+
"id": "approval-for-mcp",
|
|
2705
|
+
"type": "item_reference",
|
|
2706
|
+
},
|
|
2707
|
+
{
|
|
2708
|
+
"approval_request_id": "approval-for-mcp",
|
|
2709
|
+
"approve": true,
|
|
2710
|
+
"type": "mcp_approval_response",
|
|
2711
|
+
},
|
|
2712
|
+
{
|
|
2713
|
+
"call_id": "regular-call-1",
|
|
2714
|
+
"output": "{"result":42}",
|
|
2715
|
+
"type": "function_call_output",
|
|
2716
|
+
},
|
|
2717
|
+
]
|
|
2718
|
+
`);
|
|
2719
|
+
});
|
|
2720
|
+
});
|
|
2721
|
+
|
|
2722
|
+
describe('hasConversation', () => {
|
|
2723
|
+
it('should skip assistant text messages with item IDs when hasConversation is true', async () => {
|
|
2724
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2725
|
+
toolNameMapping: testToolNameMapping,
|
|
2726
|
+
prompt: [
|
|
2727
|
+
{
|
|
2728
|
+
role: 'user',
|
|
2729
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
2730
|
+
},
|
|
2731
|
+
{
|
|
2732
|
+
role: 'assistant',
|
|
2733
|
+
content: [
|
|
2734
|
+
{
|
|
2735
|
+
type: 'text',
|
|
2736
|
+
text: 'Hi there!',
|
|
2737
|
+
providerOptions: { openai: { itemId: 'msg_existing_123' } },
|
|
2738
|
+
},
|
|
2739
|
+
],
|
|
2740
|
+
},
|
|
2741
|
+
{
|
|
2742
|
+
role: 'user',
|
|
2743
|
+
content: [{ type: 'text', text: 'What is the weather?' }],
|
|
2744
|
+
},
|
|
2745
|
+
],
|
|
2746
|
+
systemMessageMode: 'system',
|
|
2747
|
+
providerOptionsName: 'openai',
|
|
2748
|
+
store: true,
|
|
2749
|
+
hasConversation: true,
|
|
2750
|
+
});
|
|
2751
|
+
|
|
2752
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2753
|
+
[
|
|
2754
|
+
{
|
|
2755
|
+
"content": [
|
|
2756
|
+
{
|
|
2757
|
+
"text": "Hello",
|
|
2758
|
+
"type": "input_text",
|
|
2759
|
+
},
|
|
2760
|
+
],
|
|
2761
|
+
"role": "user",
|
|
2762
|
+
},
|
|
2763
|
+
{
|
|
2764
|
+
"content": [
|
|
2765
|
+
{
|
|
2766
|
+
"text": "What is the weather?",
|
|
2767
|
+
"type": "input_text",
|
|
2768
|
+
},
|
|
2769
|
+
],
|
|
2770
|
+
"role": "user",
|
|
2771
|
+
},
|
|
2772
|
+
]
|
|
2773
|
+
`);
|
|
2774
|
+
});
|
|
2775
|
+
|
|
2776
|
+
it('should skip assistant tool-call messages with item IDs when hasConversation is true', async () => {
|
|
2777
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2778
|
+
toolNameMapping: testToolNameMapping,
|
|
2779
|
+
prompt: [
|
|
2780
|
+
{
|
|
2781
|
+
role: 'user',
|
|
2782
|
+
content: [{ type: 'text', text: 'What is the weather?' }],
|
|
2783
|
+
},
|
|
2784
|
+
{
|
|
2785
|
+
role: 'assistant',
|
|
2786
|
+
content: [
|
|
2787
|
+
{
|
|
2788
|
+
type: 'tool-call',
|
|
2789
|
+
toolCallId: 'call_123',
|
|
2790
|
+
toolName: 'getWeather',
|
|
2791
|
+
input: { location: 'San Francisco' },
|
|
2792
|
+
providerOptions: {
|
|
2793
|
+
openai: { itemId: 'fc_existing_456' },
|
|
2794
|
+
},
|
|
2795
|
+
},
|
|
2796
|
+
],
|
|
2797
|
+
},
|
|
2798
|
+
{
|
|
2799
|
+
role: 'tool',
|
|
2800
|
+
content: [
|
|
2801
|
+
{
|
|
2802
|
+
type: 'tool-result',
|
|
2803
|
+
toolCallId: 'call_123',
|
|
2804
|
+
toolName: 'getWeather',
|
|
2805
|
+
output: { type: 'json', value: { temp: 72 } },
|
|
2806
|
+
},
|
|
2807
|
+
],
|
|
2808
|
+
},
|
|
2809
|
+
],
|
|
2810
|
+
systemMessageMode: 'system',
|
|
2811
|
+
providerOptionsName: 'openai',
|
|
2812
|
+
store: true,
|
|
2813
|
+
hasConversation: true,
|
|
2814
|
+
});
|
|
2815
|
+
|
|
2816
|
+
// Tool call with itemId should be skipped, but tool output should remain
|
|
2817
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2818
|
+
[
|
|
2819
|
+
{
|
|
2820
|
+
"content": [
|
|
2821
|
+
{
|
|
2822
|
+
"text": "What is the weather?",
|
|
2823
|
+
"type": "input_text",
|
|
2824
|
+
},
|
|
2825
|
+
],
|
|
2826
|
+
"role": "user",
|
|
2827
|
+
},
|
|
2828
|
+
{
|
|
2829
|
+
"call_id": "call_123",
|
|
2830
|
+
"output": "{"temp":72}",
|
|
2831
|
+
"type": "function_call_output",
|
|
2832
|
+
},
|
|
2833
|
+
]
|
|
2834
|
+
`);
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2837
|
+
it('should include assistant messages without item IDs when hasConversation is true', async () => {
|
|
2838
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2839
|
+
toolNameMapping: testToolNameMapping,
|
|
2840
|
+
prompt: [
|
|
2841
|
+
{
|
|
2842
|
+
role: 'user',
|
|
2843
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
role: 'assistant',
|
|
2847
|
+
content: [
|
|
2848
|
+
{
|
|
2849
|
+
type: 'text',
|
|
2850
|
+
text: 'Hi there!',
|
|
2851
|
+
// No itemId - this is a new message
|
|
2852
|
+
},
|
|
2853
|
+
],
|
|
2854
|
+
},
|
|
2855
|
+
],
|
|
2856
|
+
systemMessageMode: 'system',
|
|
2857
|
+
providerOptionsName: 'openai',
|
|
2858
|
+
store: true,
|
|
2859
|
+
hasConversation: true,
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
// Assistant message without itemId should be included
|
|
2863
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2864
|
+
[
|
|
2865
|
+
{
|
|
2866
|
+
"content": [
|
|
2867
|
+
{
|
|
2868
|
+
"text": "Hello",
|
|
2869
|
+
"type": "input_text",
|
|
2870
|
+
},
|
|
2871
|
+
],
|
|
2872
|
+
"role": "user",
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
"content": [
|
|
2876
|
+
{
|
|
2877
|
+
"text": "Hi there!",
|
|
2878
|
+
"type": "output_text",
|
|
2879
|
+
},
|
|
2880
|
+
],
|
|
2881
|
+
"id": undefined,
|
|
2882
|
+
"role": "assistant",
|
|
2883
|
+
},
|
|
2884
|
+
]
|
|
2885
|
+
`);
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
it('should include assistant messages with item IDs when hasConversation is false', async () => {
|
|
2889
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2890
|
+
toolNameMapping: testToolNameMapping,
|
|
2891
|
+
prompt: [
|
|
2892
|
+
{
|
|
2893
|
+
role: 'user',
|
|
2894
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
role: 'assistant',
|
|
2898
|
+
content: [
|
|
2899
|
+
{
|
|
2900
|
+
type: 'text',
|
|
2901
|
+
text: 'Hi there!',
|
|
2902
|
+
providerOptions: { openai: { itemId: 'msg_existing_123' } },
|
|
2903
|
+
},
|
|
2904
|
+
],
|
|
2905
|
+
},
|
|
2906
|
+
],
|
|
2907
|
+
systemMessageMode: 'system',
|
|
2908
|
+
providerOptionsName: 'openai',
|
|
2909
|
+
store: true,
|
|
2910
|
+
hasConversation: false,
|
|
2911
|
+
});
|
|
2912
|
+
|
|
2913
|
+
// With hasConversation false, should use item_reference
|
|
2914
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2915
|
+
[
|
|
2916
|
+
{
|
|
2917
|
+
"content": [
|
|
2918
|
+
{
|
|
2919
|
+
"text": "Hello",
|
|
2920
|
+
"type": "input_text",
|
|
2921
|
+
},
|
|
2922
|
+
],
|
|
2923
|
+
"role": "user",
|
|
2924
|
+
},
|
|
2925
|
+
{
|
|
2926
|
+
"id": "msg_existing_123",
|
|
2927
|
+
"type": "item_reference",
|
|
2928
|
+
},
|
|
2929
|
+
]
|
|
2930
|
+
`);
|
|
2931
|
+
});
|
|
2932
|
+
|
|
2933
|
+
it('should skip reasoning parts with item IDs when hasConversation is true', async () => {
|
|
2934
|
+
const result = await convertToOpenAIResponsesInput({
|
|
2935
|
+
toolNameMapping: testToolNameMapping,
|
|
2936
|
+
prompt: [
|
|
2937
|
+
{
|
|
2938
|
+
role: 'user',
|
|
2939
|
+
content: [{ type: 'text', text: 'Hello' }],
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
role: 'assistant',
|
|
2943
|
+
content: [
|
|
2944
|
+
{
|
|
2945
|
+
type: 'reasoning',
|
|
2946
|
+
text: 'Let me think...',
|
|
2947
|
+
providerOptions: {
|
|
2948
|
+
openai: { itemId: 'reasoning_existing_789' },
|
|
2949
|
+
},
|
|
2950
|
+
},
|
|
2951
|
+
],
|
|
2952
|
+
},
|
|
2953
|
+
],
|
|
2954
|
+
systemMessageMode: 'system',
|
|
2955
|
+
providerOptionsName: 'openai',
|
|
2956
|
+
store: true,
|
|
2957
|
+
hasConversation: true,
|
|
2958
|
+
});
|
|
2959
|
+
|
|
2960
|
+
// Reasoning with itemId should be skipped
|
|
2961
|
+
expect(result.input).toMatchInlineSnapshot(`
|
|
2962
|
+
[
|
|
2963
|
+
{
|
|
2964
|
+
"content": [
|
|
2965
|
+
{
|
|
2966
|
+
"text": "Hello",
|
|
2967
|
+
"type": "input_text",
|
|
2968
|
+
},
|
|
2969
|
+
],
|
|
2970
|
+
"role": "user",
|
|
2971
|
+
},
|
|
2972
|
+
]
|
|
2973
|
+
`);
|
|
2974
|
+
});
|
|
2975
|
+
});
|
|
2976
|
+
});
|