@tanstack/openai-base 0.10.2 → 0.10.3
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/dist/esm/adapters/chat-completions-text.d.ts +6 -6
- package/dist/esm/adapters/chat-completions-text.js +7 -2
- package/dist/esm/adapters/chat-completions-text.js.map +1 -1
- package/dist/esm/adapters/responses-text.d.ts +6 -6
- package/dist/esm/adapters/responses-text.js +100 -99
- package/dist/esm/adapters/responses-text.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/chat-completions-text.ts +19 -12
- package/src/adapters/responses-text.ts +117 -122
|
@@ -27,8 +27,7 @@ import type {
|
|
|
27
27
|
DefaultMessageMetadataByModality,
|
|
28
28
|
Modality,
|
|
29
29
|
ModelMessage,
|
|
30
|
-
|
|
31
|
-
StreamChunk,
|
|
30
|
+
AdapterYieldChunk,
|
|
32
31
|
TextOptions,
|
|
33
32
|
} from '@tanstack/ai'
|
|
34
33
|
|
|
@@ -71,7 +70,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
71
70
|
|
|
72
71
|
async *chatStream(
|
|
73
72
|
options: TextOptions<TProviderOptions>,
|
|
74
|
-
): AsyncIterable<
|
|
73
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
75
74
|
// AG-UI lifecycle tracking (mutable state object for ESLint compatibility)
|
|
76
75
|
const aguiState = {
|
|
77
76
|
runId: generateId(this.name),
|
|
@@ -112,7 +111,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
112
111
|
options: TextOptions,
|
|
113
112
|
aguiState: ChatStreamState,
|
|
114
113
|
source: 'chatStream' | 'processStreamChunks',
|
|
115
|
-
): AsyncIterable<
|
|
114
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
116
115
|
// Narrow before logging: raw SDK errors can carry request metadata
|
|
117
116
|
// (including auth headers) which we must never surface to user loggers.
|
|
118
117
|
const errorPayload = toRunErrorPayload(
|
|
@@ -187,6 +186,8 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
187
186
|
|
|
188
187
|
yield {
|
|
189
188
|
type: EventType.RUN_ERROR,
|
|
189
|
+
runId: aguiState.runId,
|
|
190
|
+
threadId: aguiState.threadId,
|
|
190
191
|
model: options.model,
|
|
191
192
|
timestamp: Date.now(),
|
|
192
193
|
message: errorPayload.message,
|
|
@@ -322,7 +323,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
322
323
|
*/
|
|
323
324
|
async *structuredOutputStream(
|
|
324
325
|
options: StructuredOutputOptions<TProviderOptions>,
|
|
325
|
-
): AsyncIterable<
|
|
326
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
326
327
|
const { chatOptions, outputSchema } = options
|
|
327
328
|
const requestParams = this.mapOptionsToRequest(chatOptions)
|
|
328
329
|
|
|
@@ -351,7 +352,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
351
352
|
|
|
352
353
|
const closeReasoningLifecycle = function* (this: {
|
|
353
354
|
name: string
|
|
354
|
-
}): Generator<
|
|
355
|
+
}): Generator<AdapterYieldChunk> {
|
|
355
356
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
356
357
|
hasClosedReasoning = true
|
|
357
358
|
yield {
|
|
@@ -376,6 +377,9 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
376
377
|
content: accumulatedReasoning,
|
|
377
378
|
}
|
|
378
379
|
}
|
|
380
|
+
reasoningMessageId = undefined
|
|
381
|
+
stepId = undefined
|
|
382
|
+
hasClosedReasoning = false
|
|
379
383
|
}
|
|
380
384
|
}.bind(this)
|
|
381
385
|
|
|
@@ -705,7 +709,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
705
709
|
stream: AsyncIterable<ChatCompletionChunk>,
|
|
706
710
|
options: TextOptions,
|
|
707
711
|
aguiState: ChatStreamState,
|
|
708
|
-
): AsyncIterable<
|
|
712
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
709
713
|
const normalizeToolInput = createToolInputNormalizer(
|
|
710
714
|
options.tools,
|
|
711
715
|
(schema, required) =>
|
|
@@ -1140,7 +1144,7 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1140
1144
|
// tool results that would never arrive. OpenAI's legacy
|
|
1141
1145
|
// `function_call` value (from the v1 function-calling API) is
|
|
1142
1146
|
// normalized to `tool_calls` — semantically the same termination.
|
|
1143
|
-
const finishReason: NonNullable<
|
|
1147
|
+
const finishReason: NonNullable<AdapterYieldChunk['finishReason']> =
|
|
1144
1148
|
emittedAnyToolCallEnd
|
|
1145
1149
|
? 'tool_calls'
|
|
1146
1150
|
: pendingFinishReason === 'tool_calls'
|
|
@@ -1427,9 +1431,9 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1427
1431
|
* Handles backward compatibility with string content.
|
|
1428
1432
|
*/
|
|
1429
1433
|
protected normalizeContent(
|
|
1430
|
-
content: string | null | Array<ContentPart>,
|
|
1434
|
+
content: string | null | undefined | Array<ContentPart>,
|
|
1431
1435
|
): Array<ContentPart> {
|
|
1432
|
-
if (content === null) {
|
|
1436
|
+
if (content === null || content === undefined) {
|
|
1433
1437
|
return []
|
|
1434
1438
|
}
|
|
1435
1439
|
if (typeof content === 'string') {
|
|
@@ -1442,9 +1446,12 @@ export abstract class OpenAIBaseChatCompletionsTextAdapter<
|
|
|
1442
1446
|
* Extracts text content from a content value that may be string, null, or ContentPart array.
|
|
1443
1447
|
*/
|
|
1444
1448
|
protected extractTextContent(
|
|
1445
|
-
content: string | null | Array<ContentPart>,
|
|
1449
|
+
content: string | null | undefined | Array<ContentPart>,
|
|
1446
1450
|
): string {
|
|
1447
|
-
|
|
1451
|
+
// Tool-call-only assistant turns (e.g. an approval resume replaying the
|
|
1452
|
+
// pending call) carry no text and arrive as `null` or `undefined`; both
|
|
1453
|
+
// must collapse to '' rather than crash on `.filter`.
|
|
1454
|
+
if (content === null || content === undefined) {
|
|
1448
1455
|
return ''
|
|
1449
1456
|
}
|
|
1450
1457
|
if (typeof content === 'string') {
|
|
@@ -29,7 +29,7 @@ import type {
|
|
|
29
29
|
DefaultMessageMetadataByModality,
|
|
30
30
|
Modality,
|
|
31
31
|
ModelMessage,
|
|
32
|
-
|
|
32
|
+
AdapterYieldChunk,
|
|
33
33
|
TextOptions,
|
|
34
34
|
} from '@tanstack/ai'
|
|
35
35
|
|
|
@@ -91,7 +91,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
91
91
|
|
|
92
92
|
async *chatStream(
|
|
93
93
|
options: TextOptions<TProviderOptions>,
|
|
94
|
-
): AsyncIterable<
|
|
94
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
95
95
|
// Key streamed state by output item ID because argument deltas reference
|
|
96
96
|
// `item_id`. The state separately retains `call_id`, which is the public
|
|
97
97
|
// tool-call ID and the correlation key for function_call_output.
|
|
@@ -300,7 +300,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
300
300
|
*/
|
|
301
301
|
async *structuredOutputStream(
|
|
302
302
|
options: StructuredOutputOptions<TProviderOptions>,
|
|
303
|
-
): AsyncIterable<
|
|
303
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
304
304
|
const { chatOptions, outputSchema } = options
|
|
305
305
|
const requestParams = this.mapOptionsToRequest(chatOptions)
|
|
306
306
|
|
|
@@ -327,7 +327,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
327
327
|
|
|
328
328
|
const closeReasoning = function* (this: {
|
|
329
329
|
name: string
|
|
330
|
-
}): Generator<
|
|
330
|
+
}): Generator<AdapterYieldChunk> {
|
|
331
331
|
if (reasoningMessageId && !hasClosedReasoning) {
|
|
332
332
|
hasClosedReasoning = true
|
|
333
333
|
yield {
|
|
@@ -352,12 +352,15 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
352
352
|
content: accumulatedReasoning,
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
+
reasoningMessageId = undefined
|
|
356
|
+
stepId = undefined
|
|
357
|
+
hasClosedReasoning = false
|
|
355
358
|
}
|
|
356
359
|
}.bind(this)
|
|
357
360
|
|
|
358
361
|
const openReasoning = function* (this: {
|
|
359
362
|
name: string
|
|
360
|
-
}): Generator<
|
|
363
|
+
}): Generator<AdapterYieldChunk> {
|
|
361
364
|
if (reasoningMessageId) return
|
|
362
365
|
reasoningMessageId = generateId(this.name)
|
|
363
366
|
stepId = generateId(this.name)
|
|
@@ -798,7 +801,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
798
801
|
messageId: string
|
|
799
802
|
hasEmittedRunStarted: boolean
|
|
800
803
|
},
|
|
801
|
-
): AsyncIterable<
|
|
804
|
+
): AsyncIterable<AdapterYieldChunk> {
|
|
802
805
|
const normalizeToolInput = createToolInputNormalizer(
|
|
803
806
|
options.tools,
|
|
804
807
|
(schema, required) =>
|
|
@@ -817,12 +820,95 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
817
820
|
// AG-UI lifecycle tracking
|
|
818
821
|
let stepId: string | null = null
|
|
819
822
|
let hasEmittedTextMessageStart = false
|
|
820
|
-
let
|
|
823
|
+
let reasoningMessageId: string | undefined
|
|
824
|
+
let hasClosedReasoning = false
|
|
821
825
|
// Track whether we've emitted a terminal RUN_FINISHED so the
|
|
822
826
|
// end-of-stream fallback below knows to synthesise one when the upstream
|
|
823
827
|
// cuts off without a response.completed event.
|
|
824
828
|
let runFinishedEmitted = false
|
|
825
829
|
|
|
830
|
+
const adapterName = this.name
|
|
831
|
+
const emitModel = () => model || options.model
|
|
832
|
+
|
|
833
|
+
const openReasoning = function* (): Generator<AdapterYieldChunk> {
|
|
834
|
+
if (reasoningMessageId) return
|
|
835
|
+
reasoningMessageId = generateId(adapterName)
|
|
836
|
+
stepId = generateId(adapterName)
|
|
837
|
+
const timestamp = Date.now()
|
|
838
|
+
const currentModel = emitModel()
|
|
839
|
+
yield {
|
|
840
|
+
type: EventType.REASONING_START,
|
|
841
|
+
messageId: reasoningMessageId,
|
|
842
|
+
model: currentModel,
|
|
843
|
+
timestamp,
|
|
844
|
+
}
|
|
845
|
+
yield {
|
|
846
|
+
type: EventType.REASONING_MESSAGE_START,
|
|
847
|
+
messageId: reasoningMessageId,
|
|
848
|
+
role: 'reasoning' as const,
|
|
849
|
+
model: currentModel,
|
|
850
|
+
timestamp,
|
|
851
|
+
}
|
|
852
|
+
yield {
|
|
853
|
+
type: EventType.STEP_STARTED,
|
|
854
|
+
stepName: stepId,
|
|
855
|
+
stepId,
|
|
856
|
+
model: currentModel,
|
|
857
|
+
timestamp,
|
|
858
|
+
stepType: 'thinking',
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const closeReasoning = function* (): Generator<AdapterYieldChunk> {
|
|
863
|
+
if (!reasoningMessageId || hasClosedReasoning) return
|
|
864
|
+
hasClosedReasoning = true
|
|
865
|
+
const timestamp = Date.now()
|
|
866
|
+
const currentModel = emitModel()
|
|
867
|
+
yield {
|
|
868
|
+
type: EventType.REASONING_MESSAGE_END,
|
|
869
|
+
messageId: reasoningMessageId,
|
|
870
|
+
model: currentModel,
|
|
871
|
+
timestamp,
|
|
872
|
+
}
|
|
873
|
+
yield {
|
|
874
|
+
type: EventType.REASONING_END,
|
|
875
|
+
messageId: reasoningMessageId,
|
|
876
|
+
model: currentModel,
|
|
877
|
+
timestamp,
|
|
878
|
+
}
|
|
879
|
+
if (stepId) {
|
|
880
|
+
yield {
|
|
881
|
+
type: EventType.STEP_FINISHED,
|
|
882
|
+
stepName: stepId,
|
|
883
|
+
stepId,
|
|
884
|
+
model: currentModel,
|
|
885
|
+
timestamp,
|
|
886
|
+
content: accumulatedReasoning,
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
reasoningMessageId = undefined
|
|
890
|
+
stepId = null
|
|
891
|
+
hasClosedReasoning = false
|
|
892
|
+
accumulatedReasoning = ''
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
const emitReasoningDelta = function* (
|
|
896
|
+
text: string,
|
|
897
|
+
): Generator<AdapterYieldChunk> {
|
|
898
|
+
if (!text) return
|
|
899
|
+
yield* openReasoning()
|
|
900
|
+
if (!reasoningMessageId) return
|
|
901
|
+
accumulatedReasoning += text
|
|
902
|
+
hasStreamedReasoningDeltas = true
|
|
903
|
+
yield {
|
|
904
|
+
type: EventType.REASONING_MESSAGE_CONTENT,
|
|
905
|
+
messageId: reasoningMessageId,
|
|
906
|
+
delta: text,
|
|
907
|
+
model: emitModel(),
|
|
908
|
+
timestamp: Date.now(),
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
|
|
826
912
|
try {
|
|
827
913
|
for await (const chunk of stream) {
|
|
828
914
|
options.logger.provider(`provider=${this.name} type=${chunk.type}`, {
|
|
@@ -847,7 +933,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
847
933
|
type: string
|
|
848
934
|
text?: string
|
|
849
935
|
refusal?: string
|
|
850
|
-
}):
|
|
936
|
+
}): AdapterYieldChunk => {
|
|
851
937
|
if (contentPart.type === 'output_text') {
|
|
852
938
|
accumulatedContent += contentPart.text || ''
|
|
853
939
|
return {
|
|
@@ -860,25 +946,6 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
860
946
|
}
|
|
861
947
|
}
|
|
862
948
|
|
|
863
|
-
if (contentPart.type === 'reasoning_text') {
|
|
864
|
-
accumulatedReasoning += contentPart.text || ''
|
|
865
|
-
// Cache the fallback stepId rather than generating a fresh one
|
|
866
|
-
// on every call — otherwise multiple reasoning chunks arriving
|
|
867
|
-
// before STEP_STARTED was emitted (e.g. via response.content_part.done
|
|
868
|
-
// alone) would each get a different stepId and break correlation.
|
|
869
|
-
if (!stepId) {
|
|
870
|
-
stepId = generateId(this.name)
|
|
871
|
-
}
|
|
872
|
-
return {
|
|
873
|
-
type: EventType.STEP_FINISHED,
|
|
874
|
-
stepName: stepId,
|
|
875
|
-
stepId,
|
|
876
|
-
model: model || options.model,
|
|
877
|
-
timestamp: Date.now(),
|
|
878
|
-
delta: contentPart.text || '',
|
|
879
|
-
content: accumulatedReasoning,
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
949
|
// Either a real refusal or an unknown content_part type. Surface
|
|
883
950
|
// the part type in the error so unknown parts are debuggable
|
|
884
951
|
// instead of being misreported as "Unknown refusal".
|
|
@@ -913,7 +980,9 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
913
980
|
hasStreamedContentDeltas = false
|
|
914
981
|
hasStreamedReasoningDeltas = false
|
|
915
982
|
hasEmittedTextMessageStart = false
|
|
916
|
-
|
|
983
|
+
reasoningMessageId = undefined
|
|
984
|
+
hasClosedReasoning = false
|
|
985
|
+
stepId = null
|
|
917
986
|
accumulatedContent = ''
|
|
918
987
|
accumulatedReasoning = ''
|
|
919
988
|
}
|
|
@@ -928,6 +997,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
928
997
|
chunk.type === 'response.failed' ||
|
|
929
998
|
chunk.type === 'response.incomplete'
|
|
930
999
|
) {
|
|
1000
|
+
yield* closeReasoning()
|
|
931
1001
|
if (hasEmittedTextMessageStart) {
|
|
932
1002
|
yield {
|
|
933
1003
|
type: EventType.TEXT_MESSAGE_END,
|
|
@@ -983,6 +1053,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
983
1053
|
: ''
|
|
984
1054
|
|
|
985
1055
|
if (textDelta) {
|
|
1056
|
+
yield* closeReasoning()
|
|
986
1057
|
// Emit TEXT_MESSAGE_START on first text content
|
|
987
1058
|
if (!hasEmittedTextMessageStart) {
|
|
988
1059
|
hasEmittedTextMessageStart = true
|
|
@@ -1011,80 +1082,22 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1011
1082
|
// Handle reasoning deltas (token-by-token thinking/reasoning streaming)
|
|
1012
1083
|
// response.reasoning_text.delta provides incremental reasoning updates
|
|
1013
1084
|
if (chunk.type === 'response.reasoning_text.delta' && chunk.delta) {
|
|
1014
|
-
// Delta can be an array of strings or a single string
|
|
1015
1085
|
const reasoningDelta = Array.isArray(chunk.delta)
|
|
1016
1086
|
? chunk.delta.join('')
|
|
1017
1087
|
: typeof chunk.delta === 'string'
|
|
1018
1088
|
? chunk.delta
|
|
1019
1089
|
: ''
|
|
1020
|
-
|
|
1021
|
-
if (reasoningDelta) {
|
|
1022
|
-
// Emit STEP_STARTED on first reasoning content
|
|
1023
|
-
if (!hasEmittedStepStarted) {
|
|
1024
|
-
hasEmittedStepStarted = true
|
|
1025
|
-
stepId = generateId(this.name)
|
|
1026
|
-
yield {
|
|
1027
|
-
type: EventType.STEP_STARTED,
|
|
1028
|
-
stepName: stepId,
|
|
1029
|
-
stepId,
|
|
1030
|
-
model: model || options.model,
|
|
1031
|
-
timestamp: Date.now(),
|
|
1032
|
-
stepType: 'thinking',
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
accumulatedReasoning += reasoningDelta
|
|
1037
|
-
hasStreamedReasoningDeltas = true
|
|
1038
|
-
const fallbackStepId = stepId || generateId(this.name)
|
|
1039
|
-
yield {
|
|
1040
|
-
type: EventType.STEP_FINISHED,
|
|
1041
|
-
stepName: fallbackStepId,
|
|
1042
|
-
stepId: fallbackStepId,
|
|
1043
|
-
model: model || options.model,
|
|
1044
|
-
timestamp: Date.now(),
|
|
1045
|
-
delta: reasoningDelta,
|
|
1046
|
-
content: accumulatedReasoning,
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1090
|
+
yield* emitReasoningDelta(reasoningDelta)
|
|
1049
1091
|
}
|
|
1050
1092
|
|
|
1051
1093
|
// Handle reasoning summary deltas (when using reasoning.summary option)
|
|
1052
|
-
// response.reasoning_summary_text.delta provides incremental summary updates
|
|
1053
1094
|
if (
|
|
1054
1095
|
chunk.type === 'response.reasoning_summary_text.delta' &&
|
|
1055
1096
|
chunk.delta
|
|
1056
1097
|
) {
|
|
1057
1098
|
const summaryDelta =
|
|
1058
1099
|
typeof chunk.delta === 'string' ? chunk.delta : ''
|
|
1059
|
-
|
|
1060
|
-
if (summaryDelta) {
|
|
1061
|
-
// Emit STEP_STARTED on first reasoning content
|
|
1062
|
-
if (!hasEmittedStepStarted) {
|
|
1063
|
-
hasEmittedStepStarted = true
|
|
1064
|
-
stepId = generateId(this.name)
|
|
1065
|
-
yield {
|
|
1066
|
-
type: EventType.STEP_STARTED,
|
|
1067
|
-
stepName: stepId,
|
|
1068
|
-
stepId,
|
|
1069
|
-
model: model || options.model,
|
|
1070
|
-
timestamp: Date.now(),
|
|
1071
|
-
stepType: 'thinking',
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
accumulatedReasoning += summaryDelta
|
|
1076
|
-
hasStreamedReasoningDeltas = true
|
|
1077
|
-
const fallbackStepId = stepId || generateId(this.name)
|
|
1078
|
-
yield {
|
|
1079
|
-
type: EventType.STEP_FINISHED,
|
|
1080
|
-
stepName: fallbackStepId,
|
|
1081
|
-
stepId: fallbackStepId,
|
|
1082
|
-
model: model || options.model,
|
|
1083
|
-
timestamp: Date.now(),
|
|
1084
|
-
delta: summaryDelta,
|
|
1085
|
-
content: accumulatedReasoning,
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1100
|
+
yield* emitReasoningDelta(summaryDelta)
|
|
1088
1101
|
}
|
|
1089
1102
|
|
|
1090
1103
|
// handle content_part added events for text, reasoning and refusals
|
|
@@ -1101,11 +1114,15 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1101
1114
|
) {
|
|
1102
1115
|
continue
|
|
1103
1116
|
}
|
|
1104
|
-
|
|
1117
|
+
if (contentPart.type === 'reasoning_text') {
|
|
1118
|
+
yield* emitReasoningDelta(contentPart.text || '')
|
|
1119
|
+
continue
|
|
1120
|
+
}
|
|
1105
1121
|
if (
|
|
1106
1122
|
contentPart.type === 'output_text' &&
|
|
1107
1123
|
!hasEmittedTextMessageStart
|
|
1108
1124
|
) {
|
|
1125
|
+
yield* closeReasoning()
|
|
1109
1126
|
hasEmittedTextMessageStart = true
|
|
1110
1127
|
yield {
|
|
1111
1128
|
type: EventType.TEXT_MESSAGE_START,
|
|
@@ -1115,27 +1132,12 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1115
1132
|
role: 'assistant',
|
|
1116
1133
|
}
|
|
1117
1134
|
}
|
|
1118
|
-
// Emit STEP_STARTED if this is reasoning content
|
|
1119
|
-
if (contentPart.type === 'reasoning_text' && !hasEmittedStepStarted) {
|
|
1120
|
-
hasEmittedStepStarted = true
|
|
1121
|
-
stepId = generateId(this.name)
|
|
1122
|
-
yield {
|
|
1123
|
-
type: EventType.STEP_STARTED,
|
|
1124
|
-
stepName: stepId,
|
|
1125
|
-
stepId,
|
|
1126
|
-
model: model || options.model,
|
|
1127
|
-
timestamp: Date.now(),
|
|
1128
|
-
stepType: 'thinking',
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
1135
|
// Mark whichever stream we just emitted into so a subsequent
|
|
1132
1136
|
// `content_part.done` doesn't duplicate the same text. Without
|
|
1133
1137
|
// this flag, an `added` event carrying the full text followed by
|
|
1134
1138
|
// a matching `done` event would emit TEXT_MESSAGE_CONTENT twice.
|
|
1135
1139
|
if (contentPart.type === 'output_text') {
|
|
1136
1140
|
hasStreamedContentDeltas = true
|
|
1137
|
-
} else if (contentPart.type === 'reasoning_text') {
|
|
1138
|
-
hasStreamedReasoningDeltas = true
|
|
1139
1141
|
}
|
|
1140
1142
|
const partChunk = handleContentPart(contentPart)
|
|
1141
1143
|
yield partChunk
|
|
@@ -1172,10 +1174,15 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1172
1174
|
// without a start and never see an end. Emit the lifecycle opener
|
|
1173
1175
|
// for whichever stream this content_part belongs to before yielding
|
|
1174
1176
|
// the CONTENT chunk; the post-loop block emits the matching END.
|
|
1177
|
+
if (contentPart.type === 'reasoning_text') {
|
|
1178
|
+
yield* emitReasoningDelta(contentPart.text || '')
|
|
1179
|
+
continue
|
|
1180
|
+
}
|
|
1175
1181
|
if (
|
|
1176
1182
|
contentPart.type === 'output_text' &&
|
|
1177
1183
|
!hasEmittedTextMessageStart
|
|
1178
1184
|
) {
|
|
1185
|
+
yield* closeReasoning()
|
|
1179
1186
|
hasEmittedTextMessageStart = true
|
|
1180
1187
|
yield {
|
|
1181
1188
|
type: EventType.TEXT_MESSAGE_START,
|
|
@@ -1184,20 +1191,6 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1184
1191
|
timestamp: Date.now(),
|
|
1185
1192
|
role: 'assistant',
|
|
1186
1193
|
}
|
|
1187
|
-
} else if (
|
|
1188
|
-
contentPart.type === 'reasoning_text' &&
|
|
1189
|
-
!hasEmittedStepStarted
|
|
1190
|
-
) {
|
|
1191
|
-
hasEmittedStepStarted = true
|
|
1192
|
-
stepId = generateId(this.name)
|
|
1193
|
-
yield {
|
|
1194
|
-
type: EventType.STEP_STARTED,
|
|
1195
|
-
stepName: stepId,
|
|
1196
|
-
stepId,
|
|
1197
|
-
model: model || options.model,
|
|
1198
|
-
timestamp: Date.now(),
|
|
1199
|
-
stepType: 'thinking',
|
|
1200
|
-
}
|
|
1201
1194
|
}
|
|
1202
1195
|
|
|
1203
1196
|
// Only emit if we haven't been streaming deltas (e.g., for non-streaming responses)
|
|
@@ -1573,6 +1566,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1573
1566
|
}
|
|
1574
1567
|
}
|
|
1575
1568
|
|
|
1569
|
+
yield* closeReasoning()
|
|
1576
1570
|
// Emit TEXT_MESSAGE_END if we had text content
|
|
1577
1571
|
if (hasEmittedTextMessageStart) {
|
|
1578
1572
|
yield {
|
|
@@ -1655,6 +1649,7 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
1655
1649
|
// mirrors the chat-completions adapter's behavior so consumers always
|
|
1656
1650
|
// see a terminal event for every started run.
|
|
1657
1651
|
if (!runFinishedEmitted && aguiState.hasEmittedRunStarted) {
|
|
1652
|
+
yield* closeReasoning()
|
|
1658
1653
|
if (hasEmittedTextMessageStart) {
|
|
1659
1654
|
yield {
|
|
1660
1655
|
type: EventType.TEXT_MESSAGE_END,
|
|
@@ -2035,9 +2030,9 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
2035
2030
|
* Handles backward compatibility with string content.
|
|
2036
2031
|
*/
|
|
2037
2032
|
protected normalizeContent(
|
|
2038
|
-
content: string | null | Array<ContentPart>,
|
|
2033
|
+
content: string | null | undefined | Array<ContentPart>,
|
|
2039
2034
|
): Array<ContentPart> {
|
|
2040
|
-
if (content === null) {
|
|
2035
|
+
if (content === null || content === undefined) {
|
|
2041
2036
|
return []
|
|
2042
2037
|
}
|
|
2043
2038
|
if (typeof content === 'string') {
|
|
@@ -2050,9 +2045,9 @@ export abstract class OpenAIBaseResponsesTextAdapter<
|
|
|
2050
2045
|
* Extracts text content from a content value that may be string, null, or ContentPart array.
|
|
2051
2046
|
*/
|
|
2052
2047
|
protected extractTextContent(
|
|
2053
|
-
content: string | null | Array<ContentPart>,
|
|
2048
|
+
content: string | null | undefined | Array<ContentPart>,
|
|
2054
2049
|
): string {
|
|
2055
|
-
if (content === null) {
|
|
2050
|
+
if (content === null || content === undefined) {
|
|
2056
2051
|
return ''
|
|
2057
2052
|
}
|
|
2058
2053
|
if (typeof content === 'string') {
|