@ai-sdk/openai 4.0.13 → 4.0.15

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.
@@ -38,6 +38,7 @@ import type {
38
38
  codeInterpreterInputSchema,
39
39
  codeInterpreterOutputSchema,
40
40
  } from '../tool/code-interpreter';
41
+ import type { computerInputSchema } from '../tool/computer';
41
42
  import type { fileSearchOutputSchema } from '../tool/file-search';
42
43
  import type { imageGenerationOutputSchema } from '../tool/image-generation';
43
44
  import type { localShellInputSchema } from '../tool/local-shell';
@@ -64,6 +65,7 @@ import {
64
65
  type OpenAIResponsesWebSearchAction,
65
66
  type OpenAIResponsesApplyPatchOperationDiffDeltaChunk,
66
67
  type OpenAIResponsesApplyPatchOperationDiffDoneChunk,
68
+ type OpenAIResponsesComputerAction,
67
69
  } from './openai-responses-api';
68
70
  import {
69
71
  openaiLanguageModelResponsesOptionsSchema,
@@ -104,6 +106,87 @@ function extractApprovalRequestIdToToolCallIdMapping(
104
106
  return mapping;
105
107
  }
106
108
 
109
+ function mapComputerAction(
110
+ action: OpenAIResponsesComputerAction,
111
+ ): InferSchema<typeof computerInputSchema>['actions'][number] {
112
+ switch (action.type) {
113
+ case 'click':
114
+ return {
115
+ type: 'click',
116
+ button: action.button,
117
+ x: action.x,
118
+ y: action.y,
119
+ ...(action.keys != null && { keys: action.keys }),
120
+ };
121
+ case 'double_click':
122
+ return {
123
+ type: 'double_click',
124
+ x: action.x,
125
+ y: action.y,
126
+ ...(action.keys != null && { keys: action.keys }),
127
+ };
128
+ case 'drag':
129
+ return {
130
+ type: 'drag',
131
+ path: action.path,
132
+ ...(action.keys != null && { keys: action.keys }),
133
+ };
134
+ case 'keypress':
135
+ return action;
136
+ case 'move':
137
+ return {
138
+ type: 'move',
139
+ x: action.x,
140
+ y: action.y,
141
+ ...(action.keys != null && { keys: action.keys }),
142
+ };
143
+ case 'screenshot':
144
+ return action;
145
+ case 'scroll':
146
+ return {
147
+ type: 'scroll',
148
+ x: action.x,
149
+ y: action.y,
150
+ scrollX: action.scroll_x,
151
+ scrollY: action.scroll_y,
152
+ ...(action.keys != null && { keys: action.keys }),
153
+ };
154
+ case 'type':
155
+ return action;
156
+ case 'wait':
157
+ return action;
158
+ }
159
+ }
160
+
161
+ function mapComputerCallInput({
162
+ action,
163
+ actions,
164
+ pending_safety_checks,
165
+ status,
166
+ }: {
167
+ action?: OpenAIResponsesComputerAction | null;
168
+ actions?: OpenAIResponsesComputerAction[] | null;
169
+ pending_safety_checks?: Array<{
170
+ id: string;
171
+ code?: string | null;
172
+ message?: string | null;
173
+ }> | null;
174
+ status: 'in_progress' | 'completed' | 'incomplete';
175
+ }): InferSchema<typeof computerInputSchema> {
176
+ return {
177
+ actions: (actions ?? (action != null ? [action] : [])).map(
178
+ mapComputerAction,
179
+ ),
180
+ pendingSafetyChecks:
181
+ pending_safety_checks?.map(safetyCheck => ({
182
+ id: safetyCheck.id,
183
+ ...(safetyCheck.code != null && { code: safetyCheck.code }),
184
+ ...(safetyCheck.message != null && { message: safetyCheck.message }),
185
+ })) ?? [],
186
+ status,
187
+ };
188
+ }
189
+
107
190
  export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
108
191
  readonly specificationVersion = 'v4';
109
192
 
@@ -220,6 +303,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
220
303
  tools,
221
304
  providerToolNames: {
222
305
  'openai.code_interpreter': 'code_interpreter',
306
+ 'openai.computer': 'computer',
223
307
  'openai.file_search': 'file_search',
224
308
  'openai.image_generation': 'image_generation',
225
309
  'openai.local_shell': 'local_shell',
@@ -264,6 +348,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
264
348
  hasLocalShellTool: hasOpenAITool('openai.local_shell'),
265
349
  hasShellTool: hasOpenAITool('openai.shell'),
266
350
  hasApplyPatchTool: hasOpenAITool('openai.apply_patch'),
351
+ hasComputerTool: hasOpenAITool('openai.computer'),
267
352
  customProviderToolNames:
268
353
  customProviderToolNames.size > 0
269
354
  ? customProviderToolNames
@@ -957,21 +1042,39 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
957
1042
  }
958
1043
 
959
1044
  case 'computer_call': {
960
- content.push({
961
- type: 'tool-call',
962
- toolCallId: part.id,
963
- toolName: toolNameMapping.toCustomToolName('computer_use'),
964
- input: '',
965
- providerExecuted: true,
966
- });
1045
+ if (part.call_id == null) {
1046
+ content.push({
1047
+ type: 'tool-call',
1048
+ toolCallId: part.id,
1049
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1050
+ input: '',
1051
+ providerExecuted: true,
1052
+ });
1053
+
1054
+ content.push({
1055
+ type: 'tool-result',
1056
+ toolCallId: part.id,
1057
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1058
+ result: {
1059
+ type: 'computer_use_tool_result',
1060
+ status: part.status,
1061
+ },
1062
+ });
1063
+ break;
1064
+ }
1065
+
1066
+ hasFunctionCall = true;
1067
+ const toolName = toolNameMapping.toCustomToolName('computer');
967
1068
 
968
1069
  content.push({
969
- type: 'tool-result',
970
- toolCallId: part.id,
971
- toolName: toolNameMapping.toCustomToolName('computer_use'),
972
- result: {
973
- type: 'computer_use_tool_result',
974
- status: part.status || 'completed',
1070
+ type: 'tool-call',
1071
+ toolCallId: part.call_id,
1072
+ toolName,
1073
+ input: JSON.stringify(mapComputerCallInput(part)),
1074
+ providerMetadata: {
1075
+ [providerOptionsName]: {
1076
+ itemId: part.id,
1077
+ },
975
1078
  },
976
1079
  });
977
1080
  break;
@@ -1303,16 +1406,16 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1303
1406
  providerExecuted: true,
1304
1407
  });
1305
1408
  } else if (value.item.type === 'computer_call') {
1409
+ const toolCallId = value.item.call_id ?? value.item.id;
1306
1410
  ongoingToolCalls[value.output_index] = {
1307
- toolName: toolNameMapping.toCustomToolName('computer_use'),
1308
- toolCallId: value.item.id,
1411
+ toolName: toolNameMapping.toCustomToolName('computer'),
1412
+ toolCallId,
1309
1413
  };
1310
1414
 
1311
1415
  controller.enqueue({
1312
1416
  type: 'tool-input-start',
1313
- id: value.item.id,
1314
- toolName: toolNameMapping.toCustomToolName('computer_use'),
1315
- providerExecuted: true,
1417
+ id: toolCallId,
1418
+ toolName: toolNameMapping.toCustomToolName('computer'),
1316
1419
  });
1317
1420
  } else if (value.item.type === 'code_interpreter_call') {
1318
1421
  ongoingToolCalls[value.output_index] = {
@@ -1553,26 +1656,54 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
1553
1656
  } else if (value.item.type === 'computer_call') {
1554
1657
  ongoingToolCalls[value.output_index] = undefined;
1555
1658
 
1659
+ if (value.item.call_id == null) {
1660
+ controller.enqueue({
1661
+ type: 'tool-input-end',
1662
+ id: value.item.id,
1663
+ });
1664
+ controller.enqueue({
1665
+ type: 'tool-call',
1666
+ toolCallId: value.item.id,
1667
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1668
+ input: '',
1669
+ providerExecuted: true,
1670
+ });
1671
+ controller.enqueue({
1672
+ type: 'tool-result',
1673
+ toolCallId: value.item.id,
1674
+ toolName: toolNameMapping.toCustomToolName('computer_use'),
1675
+ result: {
1676
+ type: 'computer_use_tool_result',
1677
+ status: value.item.status,
1678
+ },
1679
+ });
1680
+ return;
1681
+ }
1682
+
1683
+ hasFunctionCall = true;
1684
+ const toolName = toolNameMapping.toCustomToolName('computer');
1685
+ const input = JSON.stringify(mapComputerCallInput(value.item));
1686
+
1556
1687
  controller.enqueue({
1557
- type: 'tool-input-end',
1558
- id: value.item.id,
1688
+ type: 'tool-input-delta',
1689
+ id: value.item.call_id,
1690
+ delta: input,
1559
1691
  });
1560
1692
 
1561
1693
  controller.enqueue({
1562
- type: 'tool-call',
1563
- toolCallId: value.item.id,
1564
- toolName: toolNameMapping.toCustomToolName('computer_use'),
1565
- input: '',
1566
- providerExecuted: true,
1694
+ type: 'tool-input-end',
1695
+ id: value.item.call_id,
1567
1696
  });
1568
1697
 
1569
1698
  controller.enqueue({
1570
- type: 'tool-result',
1571
- toolCallId: value.item.id,
1572
- toolName: toolNameMapping.toCustomToolName('computer_use'),
1573
- result: {
1574
- type: 'computer_use_tool_result',
1575
- status: value.item.status || 'completed',
1699
+ type: 'tool-call',
1700
+ toolCallId: value.item.call_id,
1701
+ toolName,
1702
+ input,
1703
+ providerMetadata: {
1704
+ [providerOptionsName]: {
1705
+ itemId: value.item.id,
1706
+ },
1576
1707
  },
1577
1708
  });
1578
1709
  } else if (value.item.type === 'file_search_call') {
@@ -62,6 +62,7 @@ export async function prepareResponsesTools({
62
62
  | { type: 'mcp' }
63
63
  | { type: 'image_generation' }
64
64
  | { type: 'apply_patch' }
65
+ | { type: 'computer' }
65
66
  | {
66
67
  type: 'allowed_tools';
67
68
  mode: 'auto' | 'required';
@@ -171,6 +172,12 @@ export async function prepareResponsesTools({
171
172
  });
172
173
  break;
173
174
  }
175
+ case 'openai.computer': {
176
+ openaiTools.push({
177
+ type: 'computer',
178
+ });
179
+ break;
180
+ }
174
181
  case 'openai.web_search_preview': {
175
182
  const args = await validateTypes({
176
183
  value: tool.args,
@@ -374,7 +381,8 @@ export async function prepareResponsesTools({
374
381
  resolvedToolName === 'web_search_preview' ||
375
382
  resolvedToolName === 'web_search' ||
376
383
  resolvedToolName === 'mcp' ||
377
- resolvedToolName === 'apply_patch'
384
+ resolvedToolName === 'apply_patch' ||
385
+ resolvedToolName === 'computer'
378
386
  ? { type: resolvedToolName }
379
387
  : resolvedCustomProviderToolNames.has(resolvedToolName)
380
388
  ? { type: 'custom', name: resolvedToolName }
@@ -0,0 +1,147 @@
1
+ import {
2
+ createProviderDefinedToolFactoryWithOutputSchema,
3
+ lazySchema,
4
+ zodSchema,
5
+ } from '@ai-sdk/provider-utils';
6
+ import { z } from 'zod/v4';
7
+
8
+ const safetyCheckSchema = z.object({
9
+ id: z.string(),
10
+ code: z.string().optional(),
11
+ message: z.string().optional(),
12
+ });
13
+
14
+ const computerActionSchema = z.discriminatedUnion('type', [
15
+ z.object({
16
+ type: z.literal('click'),
17
+ button: z.enum(['left', 'right', 'wheel', 'back', 'forward']),
18
+ x: z.number(),
19
+ y: z.number(),
20
+ keys: z.array(z.string()).optional(),
21
+ }),
22
+ z.object({
23
+ type: z.literal('double_click'),
24
+ x: z.number(),
25
+ y: z.number(),
26
+ keys: z.array(z.string()).optional(),
27
+ }),
28
+ z.object({
29
+ type: z.literal('drag'),
30
+ path: z.array(z.object({ x: z.number(), y: z.number() })),
31
+ keys: z.array(z.string()).optional(),
32
+ }),
33
+ z.object({
34
+ type: z.literal('keypress'),
35
+ keys: z.array(z.string()),
36
+ }),
37
+ z.object({
38
+ type: z.literal('move'),
39
+ x: z.number(),
40
+ y: z.number(),
41
+ keys: z.array(z.string()).optional(),
42
+ }),
43
+ z.object({
44
+ type: z.literal('screenshot'),
45
+ }),
46
+ z.object({
47
+ type: z.literal('scroll'),
48
+ x: z.number(),
49
+ y: z.number(),
50
+ scrollX: z.number(),
51
+ scrollY: z.number(),
52
+ keys: z.array(z.string()).optional(),
53
+ }),
54
+ z.object({
55
+ type: z.literal('type'),
56
+ text: z.string(),
57
+ }),
58
+ z.object({
59
+ type: z.literal('wait'),
60
+ }),
61
+ ]);
62
+
63
+ export const computerInputSchema = lazySchema(() =>
64
+ zodSchema(
65
+ z.object({
66
+ actions: z.array(computerActionSchema),
67
+ pendingSafetyChecks: z.array(safetyCheckSchema),
68
+ status: z.enum(['in_progress', 'completed', 'incomplete']),
69
+ }),
70
+ ),
71
+ );
72
+
73
+ export const computerOutputSchema = lazySchema(() =>
74
+ zodSchema(
75
+ z.object({
76
+ output: z.union([
77
+ z.object({
78
+ type: z.literal('computer_screenshot'),
79
+ imageUrl: z.string(),
80
+ fileId: z.string().optional(),
81
+ detail: z.enum(['auto', 'low', 'high', 'original']).optional(),
82
+ }),
83
+ z.object({
84
+ type: z.literal('computer_screenshot'),
85
+ fileId: z.string(),
86
+ imageUrl: z.string().optional(),
87
+ detail: z.enum(['auto', 'low', 'high', 'original']).optional(),
88
+ }),
89
+ ]),
90
+ acknowledgedSafetyChecks: z.array(safetyCheckSchema).optional(),
91
+ }),
92
+ ),
93
+ );
94
+
95
+ export type OpenAIComputerAction = z.infer<typeof computerActionSchema>;
96
+ export type OpenAIComputerSafetyCheck = z.infer<typeof safetyCheckSchema>;
97
+
98
+ const computerToolFactory = createProviderDefinedToolFactoryWithOutputSchema<
99
+ {
100
+ /**
101
+ * Ordered UI actions to execute.
102
+ */
103
+ actions: OpenAIComputerAction[];
104
+
105
+ /**
106
+ * Safety checks that must be acknowledged before continuing.
107
+ */
108
+ pendingSafetyChecks: OpenAIComputerSafetyCheck[];
109
+
110
+ /**
111
+ * Status of the computer call.
112
+ */
113
+ status: 'in_progress' | 'completed' | 'incomplete';
114
+ },
115
+ {
116
+ /**
117
+ * The screenshot captured after executing all actions.
118
+ */
119
+ output:
120
+ | {
121
+ type: 'computer_screenshot';
122
+ imageUrl: string;
123
+ fileId?: string;
124
+ detail?: 'auto' | 'low' | 'high' | 'original';
125
+ }
126
+ | {
127
+ type: 'computer_screenshot';
128
+ fileId: string;
129
+ imageUrl?: string;
130
+ detail?: 'auto' | 'low' | 'high' | 'original';
131
+ };
132
+
133
+ /**
134
+ * Safety checks that the application has reviewed and acknowledged.
135
+ */
136
+ acknowledgedSafetyChecks?: OpenAIComputerSafetyCheck[];
137
+ },
138
+ {}
139
+ >({
140
+ id: 'openai.computer',
141
+ inputSchema: computerInputSchema,
142
+ outputSchema: computerOutputSchema,
143
+ });
144
+
145
+ export const computer = (
146
+ options: Parameters<typeof computerToolFactory>[0] = {},
147
+ ) => computerToolFactory(options);