@ai-sdk/openai 3.0.136 → 3.0.138

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.
@@ -681,6 +681,8 @@ const openaiResponsesModeledChunkTypes = new Set([
681
681
  'response.output_item.done',
682
682
  'response.output_text.annotation.added',
683
683
  'response.output_text.delta',
684
+ 'response.refusal.delta',
685
+ 'response.refusal.done',
684
686
  'response.reasoning_summary_part.added',
685
687
  'response.reasoning_summary_part.done',
686
688
  'response.reasoning_summary_text.delta',
@@ -756,6 +758,20 @@ export const openaiResponsesChunkSchema = lazySchema(() =>
756
758
  )
757
759
  .nullish(),
758
760
  }),
761
+ z.object({
762
+ type: z.literal('response.refusal.delta'),
763
+ item_id: z.string(),
764
+ output_index: z.number().nullish(),
765
+ content_index: z.number().nullish(),
766
+ delta: z.string(),
767
+ }),
768
+ z.object({
769
+ type: z.literal('response.refusal.done'),
770
+ item_id: z.string(),
771
+ output_index: z.number().nullish(),
772
+ content_index: z.number().nullish(),
773
+ refusal: z.string(),
774
+ }),
759
775
  z.object({
760
776
  type: z.enum(['response.completed', 'response.incomplete']),
761
777
  response: z.object({
@@ -1330,55 +1346,62 @@ export const openaiResponsesResponseSchema = lazySchema(() =>
1330
1346
  role: z.literal('assistant'),
1331
1347
  id: z.string(),
1332
1348
  phase: z.enum(['commentary', 'final_answer']).nullish(),
1349
+ // the API returns either text or a refusal as content parts:
1333
1350
  content: z.array(
1334
- z.object({
1335
- type: z.literal('output_text'),
1336
- text: z.string(),
1337
- logprobs: z
1338
- .array(
1339
- z.object({
1340
- token: z.string(),
1341
- logprob: z.number(),
1342
- top_logprobs: z.array(
1343
- z.object({
1344
- token: z.string(),
1345
- logprob: z.number(),
1346
- }),
1347
- ),
1348
- }),
1349
- )
1350
- .nullish(),
1351
- annotations: z.array(
1352
- z.discriminatedUnion('type', [
1353
- z.object({
1354
- type: z.literal('url_citation'),
1355
- start_index: z.number(),
1356
- end_index: z.number(),
1357
- url: z.string(),
1358
- title: z.string(),
1359
- }),
1360
- z.object({
1361
- type: z.literal('file_citation'),
1362
- file_id: z.string(),
1363
- filename: z.string(),
1364
- index: z.number(),
1365
- }),
1366
- z.object({
1367
- type: z.literal('container_file_citation'),
1368
- container_id: z.string(),
1369
- file_id: z.string(),
1370
- filename: z.string(),
1371
- start_index: z.number(),
1372
- end_index: z.number(),
1373
- }),
1374
- z.object({
1375
- type: z.literal('file_path'),
1376
- file_id: z.string(),
1377
- index: z.number(),
1378
- }),
1379
- ]),
1380
- ),
1381
- }),
1351
+ z.discriminatedUnion('type', [
1352
+ z.object({
1353
+ type: z.literal('output_text'),
1354
+ text: z.string(),
1355
+ logprobs: z
1356
+ .array(
1357
+ z.object({
1358
+ token: z.string(),
1359
+ logprob: z.number(),
1360
+ top_logprobs: z.array(
1361
+ z.object({
1362
+ token: z.string(),
1363
+ logprob: z.number(),
1364
+ }),
1365
+ ),
1366
+ }),
1367
+ )
1368
+ .nullish(),
1369
+ annotations: z.array(
1370
+ z.discriminatedUnion('type', [
1371
+ z.object({
1372
+ type: z.literal('url_citation'),
1373
+ start_index: z.number(),
1374
+ end_index: z.number(),
1375
+ url: z.string(),
1376
+ title: z.string(),
1377
+ }),
1378
+ z.object({
1379
+ type: z.literal('file_citation'),
1380
+ file_id: z.string(),
1381
+ filename: z.string(),
1382
+ index: z.number(),
1383
+ }),
1384
+ z.object({
1385
+ type: z.literal('container_file_citation'),
1386
+ container_id: z.string(),
1387
+ file_id: z.string(),
1388
+ filename: z.string(),
1389
+ start_index: z.number(),
1390
+ end_index: z.number(),
1391
+ }),
1392
+ z.object({
1393
+ type: z.literal('file_path'),
1394
+ file_id: z.string(),
1395
+ index: z.number(),
1396
+ }),
1397
+ ]),
1398
+ ),
1399
+ }),
1400
+ z.object({
1401
+ type: z.literal('refusal'),
1402
+ refusal: z.string(),
1403
+ }),
1404
+ ]),
1382
1405
  ),
1383
1406
  }),
1384
1407
  z.object({
@@ -686,6 +686,9 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
686
686
  let hasFunctionCall = false;
687
687
  const hostedToolSearchCallIds: string[] = [];
688
688
 
689
+ // flag that checks if the model refused to answer:
690
+ let hasRefusal = false;
691
+
689
692
  // map response content to content array (defined when there is no error)
690
693
  for (const part of response.output!) {
691
694
  switch (part.type) {
@@ -842,6 +845,23 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
842
845
 
843
846
  case 'message': {
844
847
  for (const contentPart of part.content) {
848
+ if (contentPart.type === 'refusal') {
849
+ hasRefusal = true;
850
+
851
+ content.push({
852
+ type: 'text',
853
+ text: contentPart.refusal,
854
+ providerMetadata: {
855
+ [providerOptionsName]: {
856
+ itemId: part.id,
857
+ ...(part.phase != null && { phase: part.phase }),
858
+ } satisfies ResponsesTextProviderMetadata,
859
+ },
860
+ });
861
+
862
+ continue;
863
+ }
864
+
845
865
  if (
846
866
  options.providerOptions?.[providerOptionsName]?.logprobs &&
847
867
  contentPart.logprobs
@@ -1200,6 +1220,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
1200
1220
  unified: mapOpenAIResponseFinishReason({
1201
1221
  finishReason: response.incomplete_details?.reason,
1202
1222
  hasFunctionCall,
1223
+ hasRefusal,
1203
1224
  }),
1204
1225
  raw: response.incomplete_details?.reason ?? undefined,
1205
1226
  },
@@ -1320,6 +1341,9 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
1320
1341
  // flag that checks if there have been client-side tool calls (not executed by openai)
1321
1342
  let hasFunctionCall = false;
1322
1343
 
1344
+ // flag that checks if the model refused to answer:
1345
+ let hasRefusal = false;
1346
+
1323
1347
  const activeReasoning: Record<
1324
1348
  string,
1325
1349
  {
@@ -2265,6 +2289,16 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
2265
2289
  ) {
2266
2290
  logprobs.push(value.logprobs);
2267
2291
  }
2292
+ } else if (isRefusalDeltaChunk(value)) {
2293
+ hasRefusal = true;
2294
+
2295
+ controller.enqueue({
2296
+ type: 'text-delta',
2297
+ id: value.item_id,
2298
+ delta: value.delta,
2299
+ });
2300
+ } else if (isRefusalDoneChunk(value)) {
2301
+ hasRefusal = true;
2268
2302
  } else if (value.type === 'response.reasoning_summary_part.added') {
2269
2303
  // the first reasoning start is pushed in isResponseOutputItemAddedReasoningChunk
2270
2304
  if (value.summary_index > 0) {
@@ -2350,6 +2384,7 @@ export class OpenAIResponsesLanguageModel implements LanguageModelV3 {
2350
2384
  unified: mapOpenAIResponseFinishReason({
2351
2385
  finishReason: value.response.incomplete_details?.reason,
2352
2386
  hasFunctionCall,
2387
+ hasRefusal,
2353
2388
  }),
2354
2389
  raw: value.response.incomplete_details?.reason ?? undefined,
2355
2390
  };
@@ -2525,6 +2560,18 @@ function isTextDeltaChunk(
2525
2560
  return chunk.type === 'response.output_text.delta';
2526
2561
  }
2527
2562
 
2563
+ function isRefusalDeltaChunk(
2564
+ chunk: OpenAIResponsesChunk,
2565
+ ): chunk is OpenAIResponsesChunk & { type: 'response.refusal.delta' } {
2566
+ return chunk.type === 'response.refusal.delta';
2567
+ }
2568
+
2569
+ function isRefusalDoneChunk(
2570
+ chunk: OpenAIResponsesChunk,
2571
+ ): chunk is OpenAIResponsesChunk & { type: 'response.refusal.done' } {
2572
+ return chunk.type === 'response.refusal.done';
2573
+ }
2574
+
2528
2575
  function isOpenAIChatCompletionChunk(value: unknown): boolean {
2529
2576
  const chunk = asRecord(value);
2530
2577