@ai-sdk/openai-compatible 2.0.73 → 2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/openai-compatible",
3
- "version": "2.0.73",
3
+ "version": "2.0.75",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -36,8 +36,8 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@ai-sdk/provider": "3.0.15",
40
- "@ai-sdk/provider-utils": "4.0.49"
39
+ "@ai-sdk/provider": "3.0.16",
40
+ "@ai-sdk/provider-utils": "4.0.51"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -299,13 +299,16 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
299
299
  });
300
300
 
301
301
  const choice = responseBody.choices[0];
302
+ if (choice == null) {
303
+ throw new InvalidResponseDataError({
304
+ data: rawResponse,
305
+ message: 'Response did not contain any choices.',
306
+ });
307
+ }
308
+
302
309
  const content: Array<LanguageModelV3Content> = [];
303
310
 
304
- // text content:
305
- const text = choice.message.content;
306
- if (text != null && text.length > 0) {
307
- content.push({ type: 'text', text });
308
- }
311
+ content.push(...convertOpenAICompatibleContent(choice.message.content));
309
312
 
310
313
  // reasoning content:
311
314
  const reasoning =
@@ -508,9 +511,12 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
508
511
 
509
512
  const delta = choice.delta;
510
513
 
511
- // enqueue reasoning before text deltas:
512
- const reasoningContent = delta.reasoning_content ?? delta.reasoning;
513
- if (reasoningContent) {
514
+ const enqueueReasoningDelta = (reasoningDelta: string) => {
515
+ if (isActiveText) {
516
+ controller.enqueue({ type: 'text-end', id: 'txt-0' });
517
+ isActiveText = false;
518
+ }
519
+
514
520
  if (!isActiveReasoning) {
515
521
  controller.enqueue({
516
522
  type: 'reasoning-start',
@@ -522,12 +528,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
522
528
  controller.enqueue({
523
529
  type: 'reasoning-delta',
524
530
  id: 'reasoning-0',
525
- delta: reasoningContent,
531
+ delta: reasoningDelta,
526
532
  });
527
- }
533
+ };
528
534
 
529
- if (delta.content) {
530
- // end active reasoning block before text starts
535
+ const enqueueTextDelta = (textDelta: string) => {
531
536
  if (isActiveReasoning) {
532
537
  controller.enqueue({
533
538
  type: 'reasoning-end',
@@ -544,11 +549,27 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
544
549
  controller.enqueue({
545
550
  type: 'text-delta',
546
551
  id: 'txt-0',
547
- delta: delta.content,
552
+ delta: textDelta,
548
553
  });
554
+ };
555
+
556
+ // enqueue reasoning before text deltas:
557
+ const reasoningContent = delta.reasoning_content ?? delta.reasoning;
558
+ if (reasoningContent) {
559
+ enqueueReasoningDelta(reasoningContent);
549
560
  }
550
561
 
551
- if (delta.tool_calls != null) {
562
+ for (const contentPart of convertOpenAICompatibleContent(
563
+ delta.content,
564
+ )) {
565
+ if (contentPart.type === 'reasoning') {
566
+ enqueueReasoningDelta(contentPart.text);
567
+ } else {
568
+ enqueueTextDelta(contentPart.text);
569
+ }
570
+ }
571
+
572
+ if (delta.tool_calls != null && delta.tool_calls.length > 0) {
552
573
  // end active reasoning block before tool calls start
553
574
  if (isActiveReasoning) {
554
575
  controller.enqueue({
@@ -815,6 +836,60 @@ const openaiCompatibleTokenUsageSchema = z
815
836
  })
816
837
  .nullish();
817
838
 
839
+ const openAICompatibleContentSchema = z
840
+ .union([
841
+ z.string(),
842
+ z.array(
843
+ z.looseObject({
844
+ type: z.string(),
845
+ }),
846
+ ),
847
+ ])
848
+ .nullish();
849
+
850
+ function convertOpenAICompatibleContent(
851
+ content: z.infer<typeof openAICompatibleContentSchema>,
852
+ ): Array<Extract<LanguageModelV3Content, { type: 'text' | 'reasoning' }>> {
853
+ if (content == null) {
854
+ return [];
855
+ }
856
+
857
+ if (typeof content === 'string') {
858
+ return content.length > 0 ? [{ type: 'text', text: content }] : [];
859
+ }
860
+
861
+ const result: Array<
862
+ Extract<LanguageModelV3Content, { type: 'text' | 'reasoning' }>
863
+ > = [];
864
+
865
+ for (const part of content) {
866
+ if (part.type === 'text' && typeof part.text === 'string') {
867
+ if (part.text.length > 0) {
868
+ result.push({ type: 'text', text: part.text });
869
+ }
870
+ } else if (part.type === 'thinking' && Array.isArray(part.thinking)) {
871
+ const reasoningText = part.thinking
872
+ .filter(
873
+ chunk =>
874
+ chunk != null &&
875
+ typeof chunk === 'object' &&
876
+ 'type' in chunk &&
877
+ chunk.type === 'text' &&
878
+ 'text' in chunk &&
879
+ typeof chunk.text === 'string',
880
+ )
881
+ .map(chunk => chunk.text)
882
+ .join('');
883
+
884
+ if (reasoningText.length > 0) {
885
+ result.push({ type: 'reasoning', text: reasoningText });
886
+ }
887
+ }
888
+ }
889
+
890
+ return result;
891
+ }
892
+
818
893
  // limited version of the schema, focussed on what is needed for the implementation
819
894
  // this approach limits breakages when the API changes and increases efficiency
820
895
  const OpenAICompatibleChatResponseSchema = z.looseObject({
@@ -825,7 +900,7 @@ const OpenAICompatibleChatResponseSchema = z.looseObject({
825
900
  z.object({
826
901
  message: z.object({
827
902
  role: z.literal('assistant').nullish(),
828
- content: z.string().nullish(),
903
+ content: openAICompatibleContentSchema,
829
904
  reasoning_content: z.string().nullish(),
830
905
  reasoning: z.string().nullish(),
831
906
  tool_calls: z
@@ -865,7 +940,7 @@ const chunkBaseSchema = z.looseObject({
865
940
  delta: z
866
941
  .object({
867
942
  role: z.enum(['assistant', '']).nullish(),
868
- content: z.string().nullish(),
943
+ content: openAICompatibleContentSchema,
869
944
  // Most openai-compatible models set `reasoning_content`, but some
870
945
  // providers serving `gpt-oss` set `reasoning`. See #7866
871
946
  reasoning_content: z.string().nullish(),