@ai-sdk/openai-compatible 2.0.73 → 2.0.74

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.74",
4
4
  "license": "Apache-2.0",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@ai-sdk/provider": "3.0.15",
40
- "@ai-sdk/provider-utils": "4.0.49"
40
+ "@ai-sdk/provider-utils": "4.0.50"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "20.17.24",
@@ -301,11 +301,7 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
301
301
  const choice = responseBody.choices[0];
302
302
  const content: Array<LanguageModelV3Content> = [];
303
303
 
304
- // text content:
305
- const text = choice.message.content;
306
- if (text != null && text.length > 0) {
307
- content.push({ type: 'text', text });
308
- }
304
+ content.push(...convertOpenAICompatibleContent(choice.message.content));
309
305
 
310
306
  // reasoning content:
311
307
  const reasoning =
@@ -508,9 +504,12 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
508
504
 
509
505
  const delta = choice.delta;
510
506
 
511
- // enqueue reasoning before text deltas:
512
- const reasoningContent = delta.reasoning_content ?? delta.reasoning;
513
- if (reasoningContent) {
507
+ const enqueueReasoningDelta = (reasoningDelta: string) => {
508
+ if (isActiveText) {
509
+ controller.enqueue({ type: 'text-end', id: 'txt-0' });
510
+ isActiveText = false;
511
+ }
512
+
514
513
  if (!isActiveReasoning) {
515
514
  controller.enqueue({
516
515
  type: 'reasoning-start',
@@ -522,12 +521,11 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
522
521
  controller.enqueue({
523
522
  type: 'reasoning-delta',
524
523
  id: 'reasoning-0',
525
- delta: reasoningContent,
524
+ delta: reasoningDelta,
526
525
  });
527
- }
526
+ };
528
527
 
529
- if (delta.content) {
530
- // end active reasoning block before text starts
528
+ const enqueueTextDelta = (textDelta: string) => {
531
529
  if (isActiveReasoning) {
532
530
  controller.enqueue({
533
531
  type: 'reasoning-end',
@@ -544,8 +542,24 @@ export class OpenAICompatibleChatLanguageModel implements LanguageModelV3 {
544
542
  controller.enqueue({
545
543
  type: 'text-delta',
546
544
  id: 'txt-0',
547
- delta: delta.content,
545
+ delta: textDelta,
548
546
  });
547
+ };
548
+
549
+ // enqueue reasoning before text deltas:
550
+ const reasoningContent = delta.reasoning_content ?? delta.reasoning;
551
+ if (reasoningContent) {
552
+ enqueueReasoningDelta(reasoningContent);
553
+ }
554
+
555
+ for (const contentPart of convertOpenAICompatibleContent(
556
+ delta.content,
557
+ )) {
558
+ if (contentPart.type === 'reasoning') {
559
+ enqueueReasoningDelta(contentPart.text);
560
+ } else {
561
+ enqueueTextDelta(contentPart.text);
562
+ }
549
563
  }
550
564
 
551
565
  if (delta.tool_calls != null) {
@@ -815,6 +829,60 @@ const openaiCompatibleTokenUsageSchema = z
815
829
  })
816
830
  .nullish();
817
831
 
832
+ const openAICompatibleContentSchema = z
833
+ .union([
834
+ z.string(),
835
+ z.array(
836
+ z.looseObject({
837
+ type: z.string(),
838
+ }),
839
+ ),
840
+ ])
841
+ .nullish();
842
+
843
+ function convertOpenAICompatibleContent(
844
+ content: z.infer<typeof openAICompatibleContentSchema>,
845
+ ): Array<Extract<LanguageModelV3Content, { type: 'text' | 'reasoning' }>> {
846
+ if (content == null) {
847
+ return [];
848
+ }
849
+
850
+ if (typeof content === 'string') {
851
+ return content.length > 0 ? [{ type: 'text', text: content }] : [];
852
+ }
853
+
854
+ const result: Array<
855
+ Extract<LanguageModelV3Content, { type: 'text' | 'reasoning' }>
856
+ > = [];
857
+
858
+ for (const part of content) {
859
+ if (part.type === 'text' && typeof part.text === 'string') {
860
+ if (part.text.length > 0) {
861
+ result.push({ type: 'text', text: part.text });
862
+ }
863
+ } else if (part.type === 'thinking' && Array.isArray(part.thinking)) {
864
+ const reasoningText = part.thinking
865
+ .filter(
866
+ chunk =>
867
+ chunk != null &&
868
+ typeof chunk === 'object' &&
869
+ 'type' in chunk &&
870
+ chunk.type === 'text' &&
871
+ 'text' in chunk &&
872
+ typeof chunk.text === 'string',
873
+ )
874
+ .map(chunk => chunk.text)
875
+ .join('');
876
+
877
+ if (reasoningText.length > 0) {
878
+ result.push({ type: 'reasoning', text: reasoningText });
879
+ }
880
+ }
881
+ }
882
+
883
+ return result;
884
+ }
885
+
818
886
  // limited version of the schema, focussed on what is needed for the implementation
819
887
  // this approach limits breakages when the API changes and increases efficiency
820
888
  const OpenAICompatibleChatResponseSchema = z.looseObject({
@@ -825,7 +893,7 @@ const OpenAICompatibleChatResponseSchema = z.looseObject({
825
893
  z.object({
826
894
  message: z.object({
827
895
  role: z.literal('assistant').nullish(),
828
- content: z.string().nullish(),
896
+ content: openAICompatibleContentSchema,
829
897
  reasoning_content: z.string().nullish(),
830
898
  reasoning: z.string().nullish(),
831
899
  tool_calls: z
@@ -865,7 +933,7 @@ const chunkBaseSchema = z.looseObject({
865
933
  delta: z
866
934
  .object({
867
935
  role: z.enum(['assistant', '']).nullish(),
868
- content: z.string().nullish(),
936
+ content: openAICompatibleContentSchema,
869
937
  // Most openai-compatible models set `reasoning_content`, but some
870
938
  // providers serving `gpt-oss` set `reasoning`. See #7866
871
939
  reasoning_content: z.string().nullish(),