@ai-sdk/openai 4.0.74 → 4.0.75

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/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export { OpenAIRealtimeModel as Experimental_OpenAIRealtimeModel } from './realt
14
14
  export type { OpenAIRealtimeModelConfig as Experimental_OpenAIRealtimeModelConfig } from './realtime/openai-realtime-model';
15
15
  export type {
16
16
  OpenAILanguageModelResponsesOptions,
17
+ OpenAIResponsesSystemMessageOptions,
17
18
  /** @deprecated Use `OpenAILanguageModelResponsesOptions` instead. */
18
19
  OpenAILanguageModelResponsesOptions as OpenAIResponsesProviderOptions,
19
20
  } from './responses/openai-responses-language-model-options';
@@ -19,6 +19,7 @@ import {
19
19
  type ToolNameMapping,
20
20
  } from '@ai-sdk/provider-utils';
21
21
  import { z } from 'zod/v4';
22
+ import { openaiResponsesSystemMessageOptionsSchema } from './openai-responses-language-model-options';
22
23
  import {
23
24
  applyPatchInputSchema,
24
25
  applyPatchOutputSchema,
@@ -380,6 +381,7 @@ export async function convertToOpenAIResponsesInput({
380
381
  toolSearchToolName,
381
382
  customProviderToolNames,
382
383
  outputSchemaToolNames,
384
+ configurationUpdateUnsupportedReason,
383
385
  }: {
384
386
  prompt: LanguageModelV4Prompt;
385
387
  toolNameMapping: ToolNameMapping;
@@ -399,6 +401,7 @@ export async function convertToOpenAIResponsesInput({
399
401
  toolSearchToolName?: string;
400
402
  customProviderToolNames?: Set<string>;
401
403
  outputSchemaToolNames?: Set<string>;
404
+ configurationUpdateUnsupportedReason?: string;
402
405
  }): Promise<{
403
406
  input: OpenAIResponsesInput;
404
407
  warnings: Array<SharedV4Warning>;
@@ -420,6 +423,42 @@ export async function convertToOpenAIResponsesInput({
420
423
  for (const { role, content, providerOptions } of prompt) {
421
424
  switch (role) {
422
425
  case 'system': {
426
+ // Keep effort updates at their original positions so they apply to
427
+ // the same parts of the conversation when the history is sent again.
428
+ let options = await parseProviderOptions({
429
+ provider: providerOptionsName,
430
+ providerOptions,
431
+ schema: openaiResponsesSystemMessageOptionsSchema,
432
+ });
433
+ if (options == null && providerOptionsName !== 'openai') {
434
+ options = await parseProviderOptions({
435
+ provider: 'openai',
436
+ providerOptions,
437
+ schema: openaiResponsesSystemMessageOptionsSchema,
438
+ });
439
+ }
440
+ const effort = options?.reasoningEffortUpdate;
441
+ if (effort != null) {
442
+ const unsupportedReason =
443
+ content !== ''
444
+ ? 'Message-level reasoningEffortUpdate requires empty system message content.'
445
+ : configurationUpdateUnsupportedReason;
446
+
447
+ if (unsupportedReason != null) {
448
+ throw new UnsupportedFunctionalityError({
449
+ functionality: 'Message-level reasoningEffortUpdate',
450
+ message: unsupportedReason,
451
+ });
452
+ }
453
+
454
+ input.push({
455
+ type: 'configuration_update',
456
+ reasoning: { effort },
457
+ });
458
+ // The control is independent of systemMessageMode's text handling.
459
+ break;
460
+ }
461
+
423
462
  switch (systemMessageMode) {
424
463
  case 'system': {
425
464
  const promptCacheBreakpoint = getPromptCacheBreakpoint(
@@ -432,3 +432,25 @@ export const openaiLanguageModelResponsesOptionsSchema = lazySchema(() =>
432
432
  export type OpenAILanguageModelResponsesOptions = InferSchema<
433
433
  typeof openaiLanguageModelResponsesOptionsSchema
434
434
  >;
435
+
436
+ export const openaiResponsesSystemMessageOptionsSchema = lazySchema(() =>
437
+ zodSchema(
438
+ z.object({
439
+ /**
440
+ * Emit a configuration update at this position in Responses history.
441
+ * Requires empty system message content and the same supported
442
+ * configuration as the request-level reasoningEffortUpdate option.
443
+ * Unsupported historical updates throw instead of being omitted.
444
+ *
445
+ * @see https://developers.openai.com/api/docs/guides/reasoning#change-reasoning-mid-conversation
446
+ */
447
+ reasoningEffortUpdate: z
448
+ .enum(['low', 'medium', 'high', 'xhigh', 'max'])
449
+ .optional(),
450
+ }),
451
+ ),
452
+ );
453
+
454
+ export type OpenAIResponsesSystemMessageOptions = InferSchema<
455
+ typeof openaiResponsesSystemMessageOptionsSchema
456
+ >;
@@ -1,13 +1,14 @@
1
1
  import {
2
2
  APICallError,
3
+ UnsupportedFunctionalityError,
3
4
  type JSONValue,
4
5
  type LanguageModelV4,
5
- type LanguageModelV4Prompt,
6
6
  type LanguageModelV4CallOptions,
7
7
  type LanguageModelV4Content,
8
8
  type LanguageModelV4FinishReason,
9
9
  type LanguageModelV4FunctionTool,
10
10
  type LanguageModelV4GenerateResult,
11
+ type LanguageModelV4Prompt,
11
12
  type LanguageModelV4ProviderTool,
12
13
  type LanguageModelV4StreamPart,
13
14
  type LanguageModelV4StreamResult,
@@ -30,6 +31,7 @@ import {
30
31
  type InferSchema,
31
32
  type ParseResult,
32
33
  } from '@ai-sdk/provider-utils';
34
+ import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
33
35
  import {
34
36
  prepareOpenAIConfigForWorkflowDeserialize,
35
37
  type OpenAIConfig,
@@ -50,15 +52,15 @@ import type { fileSearchOutputSchema } from '../tool/file-search';
50
52
  import type { imageGenerationOutputSchema } from '../tool/image-generation';
51
53
  import type { localShellInputSchema } from '../tool/local-shell';
52
54
  import type { mcpOutputSchema } from '../tool/mcp';
55
+ import type {
56
+ programmaticToolCallingInputSchema,
57
+ programmaticToolCallingOutputSchema,
58
+ } from '../tool/programmatic-tool-calling';
53
59
  import type { shellInputSchema, shellOutputSchema } from '../tool/shell';
54
60
  import type {
55
61
  toolSearchInputSchema,
56
62
  toolSearchOutputSchema,
57
63
  } from '../tool/tool-search';
58
- import type {
59
- programmaticToolCallingInputSchema,
60
- programmaticToolCallingOutputSchema,
61
- } from '../tool/programmatic-tool-calling';
62
64
  import type { webSearchOutputSchema } from '../tool/web-search';
63
65
  import {
64
66
  convertOpenAIResponsesUsage,
@@ -70,22 +72,22 @@ import {
70
72
  isUndeclaredParallelToolCall,
71
73
  } from './expand-parallel-tool-call';
72
74
  import { mapOpenAIResponseFinishReason } from './map-openai-responses-finish-reason';
73
- import { normalizeOpenAIJsonSchema } from '../normalize-openai-json-schema';
74
75
  import {
75
76
  openaiResponsesChunkSchema,
76
77
  openaiResponsesResponseSchema,
78
+ type OpenAIResponsesApplyPatchOperationDiffDeltaChunk,
79
+ type OpenAIResponsesApplyPatchOperationDiffDoneChunk,
77
80
  type OpenAIResponsesChunk,
81
+ type OpenAIResponsesComputerAction,
78
82
  type OpenAIResponsesIncludeOptions,
79
83
  type OpenAIResponsesIncludeValue,
80
84
  type OpenAIResponsesLogprobs,
81
85
  type OpenAIResponsesWebSearchAction,
82
- type OpenAIResponsesApplyPatchOperationDiffDeltaChunk,
83
- type OpenAIResponsesApplyPatchOperationDiffDoneChunk,
84
- type OpenAIResponsesComputerAction,
85
86
  } from './openai-responses-api';
86
87
  import {
87
88
  openaiLanguageModelResponsesOptionsSchema,
88
89
  TOP_LOGPROBS_MAX,
90
+ type OpenAILanguageModelResponsesOptions,
89
91
  type OpenAIResponsesModelId,
90
92
  } from './openai-responses-language-model-options';
91
93
  import { prepareResponsesTools } from './openai-responses-prepare-tools';
@@ -209,6 +211,34 @@ export const openaiResponsesSupportedUrls: Record<string, RegExp[]> = {
209
211
  'application/pdf': [/^https?:\/\/.*$/],
210
212
  };
211
213
 
214
+ /**
215
+ * Enforces OpenAI's configuration update restrictions for reasoningEffortUpdate,
216
+ * returning a string describing the unsupported reason if any.
217
+ *
218
+ * @see https://developers.openai.com/api/docs/guides/reasoning#change-reasoning-mid-conversation
219
+ */
220
+ function getConfigurationUpdateUnsupportedReason({
221
+ modelCapabilities,
222
+ options,
223
+ }: {
224
+ modelCapabilities: { supportsConfigurationUpdate: boolean };
225
+ options: OpenAILanguageModelResponsesOptions | undefined;
226
+ }): string | undefined {
227
+ if (!modelCapabilities.supportsConfigurationUpdate) {
228
+ return 'reasoningEffortUpdate is only supported by GPT-6 and later models';
229
+ }
230
+
231
+ if (
232
+ options?.reasoningMode === 'pro' ||
233
+ options?.contextManagement != null ||
234
+ options?.truncation === 'auto'
235
+ ) {
236
+ return 'reasoningEffortUpdate requires standard reasoning mode without automatic compaction or automatic truncation';
237
+ }
238
+
239
+ return undefined;
240
+ }
241
+
212
242
  export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
213
243
  readonly specificationVersion = 'v4';
214
244
 
@@ -382,6 +412,12 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
382
412
  supportsAsyncToolCalling: modelCapabilities.supportsAsyncToolCalling,
383
413
  });
384
414
 
415
+ const configurationUpdateUnsupportedReason =
416
+ getConfigurationUpdateUnsupportedReason({
417
+ modelCapabilities,
418
+ options: openaiOptions,
419
+ });
420
+
385
421
  const { input, warnings: inputWarnings } =
386
422
  await convertToOpenAIResponsesInput({
387
423
  prompt,
@@ -392,6 +428,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
392
428
  ? 'developer'
393
429
  : modelCapabilities.systemMessageMode),
394
430
  providerOptionsName,
431
+ configurationUpdateUnsupportedReason,
395
432
  explicitMessageItemType: config.explicitMessageItemType,
396
433
  fileIdPrefixes: config.fileIdPrefixes,
397
434
  passThroughUnsupportedFiles:
@@ -415,26 +452,41 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV4 {
415
452
  warnings.push(...inputWarnings);
416
453
 
417
454
  const reasoningEffortUpdate = openaiOptions?.reasoningEffortUpdate;
418
- const configurationUpdateIsSupported =
419
- reasoningEffortUpdate == null ||
420
- (modelCapabilities.supportsConfigurationUpdate &&
421
- openaiOptions?.reasoningMode !== 'pro' &&
422
- openaiOptions?.contextManagement == null &&
423
- openaiOptions?.truncation !== 'auto');
424
-
425
- if (reasoningEffortUpdate != null && !configurationUpdateIsSupported) {
455
+ if (
456
+ reasoningEffortUpdate != null &&
457
+ configurationUpdateUnsupportedReason != null
458
+ ) {
426
459
  warnings.push({
427
460
  type: 'unsupported',
428
461
  feature: 'reasoningEffortUpdate',
429
- details: !modelCapabilities.supportsConfigurationUpdate
430
- ? 'reasoningEffortUpdate is only supported by GPT-6 and later models'
431
- : 'reasoningEffortUpdate requires standard reasoning mode without automatic compaction or automatic truncation',
462
+ details: configurationUpdateUnsupportedReason,
432
463
  });
433
464
  } else if (reasoningEffortUpdate != null) {
434
- input.unshift({
435
- type: 'configuration_update',
436
- reasoning: { effort: reasoningEffortUpdate },
437
- });
465
+ const firstItem = input[0];
466
+ // If the first item already sets this effort, prepending another update
467
+ // would create an adjacent pair that OpenAI rejects.
468
+ if (
469
+ firstItem?.type !== 'configuration_update' ||
470
+ firstItem.reasoning.effort !== reasoningEffortUpdate
471
+ ) {
472
+ input.unshift({
473
+ type: 'configuration_update',
474
+ reasoning: { effort: reasoningEffortUpdate },
475
+ });
476
+ }
477
+ }
478
+
479
+ // Conversion and prepending can make updates adjacent, so check afterward
480
+ // to catch combinations that OpenAI would reject.
481
+ for (let i = 1; i < input.length; i++) {
482
+ if (
483
+ input[i - 1].type === 'configuration_update' &&
484
+ input[i].type === 'configuration_update'
485
+ ) {
486
+ throw new UnsupportedFunctionalityError({
487
+ functionality: 'Adjacent reasoning effort configuration updates',
488
+ });
489
+ }
438
490
  }
439
491
 
440
492
  // A compaction trigger is a request control, not conversation history.