@codehz/ai 0.4.2 → 0.4.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/index.mjs +131 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/adapters/responses.ts +208 -7
package/package.json
CHANGED
|
@@ -80,10 +80,17 @@ const mapper = new NormalizedRequestMapper("responses");
|
|
|
80
80
|
|
|
81
81
|
type ResponsesSSEEvent =
|
|
82
82
|
| { type: "response.output_item.added"; data: { item: { id: string; type: string; [key: string]: unknown } } }
|
|
83
|
+
| { type: "response.output_item.done"; data: { item: { id: string; type: string; [key: string]: unknown } } }
|
|
83
84
|
| { type: "response.output_text.delta"; data: { item_id: string; delta: string } }
|
|
84
85
|
| { type: "response.output_text.done"; data: { item_id: string; text: string } }
|
|
85
86
|
| { type: "response.reasoning.delta"; data: { item_id: string; delta: string } }
|
|
86
87
|
| { type: "response.reasoning.done"; data: { item_id: string; text: string } }
|
|
88
|
+
| { type: "response.reasoning_summary_part.added"; data: { item_id: string; summary_index: number; part?: unknown } }
|
|
89
|
+
| { type: "response.reasoning_summary_part.done"; data: { item_id: string; summary_index: number; part?: unknown } }
|
|
90
|
+
| { type: "response.reasoning_summary_text.delta"; data: { item_id: string; delta: string; summary_index?: number } }
|
|
91
|
+
| { type: "response.reasoning_summary_text.done"; data: { item_id: string; text: string; summary_index?: number } }
|
|
92
|
+
| { type: "response.reasoning_text.delta"; data: { item_id: string; delta: string; content_index?: number } }
|
|
93
|
+
| { type: "response.reasoning_text.done"; data: { item_id: string; text: string; content_index?: number } }
|
|
87
94
|
| { type: "response.function_call_arguments.delta"; data: { item_id: string; delta: string } }
|
|
88
95
|
| { type: "response.function_call_arguments.done"; data: { item_id: string; arguments: string } }
|
|
89
96
|
| { type: "response.completed"; data: { response: ResponsesAPIResponse } }
|
|
@@ -98,8 +105,16 @@ const KNOWN_RESPONSES_SSE_TYPES = new Set([
|
|
|
98
105
|
"response.output_item.done",
|
|
99
106
|
"response.output_text.delta",
|
|
100
107
|
"response.output_text.done",
|
|
108
|
+
// legacy aliases retained for fixtures / older gateways
|
|
101
109
|
"response.reasoning.delta",
|
|
102
110
|
"response.reasoning.done",
|
|
111
|
+
// current OpenAI reasoning summary + full reasoning text events
|
|
112
|
+
"response.reasoning_summary_part.added",
|
|
113
|
+
"response.reasoning_summary_part.done",
|
|
114
|
+
"response.reasoning_summary_text.delta",
|
|
115
|
+
"response.reasoning_summary_text.done",
|
|
116
|
+
"response.reasoning_text.delta",
|
|
117
|
+
"response.reasoning_text.done",
|
|
103
118
|
"response.function_call_arguments.delta",
|
|
104
119
|
"response.function_call_arguments.done",
|
|
105
120
|
"response.content_part.added",
|
|
@@ -108,12 +123,71 @@ const KNOWN_RESPONSES_SSE_TYPES = new Set([
|
|
|
108
123
|
"response.refusal.done",
|
|
109
124
|
"response.in_progress",
|
|
110
125
|
"response.created",
|
|
126
|
+
"response.queued",
|
|
111
127
|
"response.completed",
|
|
112
128
|
"response.failed",
|
|
113
129
|
"response.incomplete",
|
|
114
130
|
"error",
|
|
115
131
|
]);
|
|
116
132
|
|
|
133
|
+
type ReasoningVisibility = import("../index.js").ReasoningItem["visibility"];
|
|
134
|
+
|
|
135
|
+
type ReasoningStreamState = {
|
|
136
|
+
visibility: ReasoningVisibility;
|
|
137
|
+
hasDelta: boolean;
|
|
138
|
+
/** Accumulated final texts from *.done events when deltas were skipped. */
|
|
139
|
+
doneTexts: string[];
|
|
140
|
+
completed: boolean;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
function createReasoningState(visibility: ReasoningVisibility = "summary"): ReasoningStreamState {
|
|
144
|
+
return { visibility, hasDelta: false, doneTexts: [], completed: false };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function extractReasoningFromOutputItem(item: Record<string, unknown>): {
|
|
148
|
+
text: string;
|
|
149
|
+
visibility: ReasoningVisibility;
|
|
150
|
+
} {
|
|
151
|
+
const contentTexts: string[] = [];
|
|
152
|
+
if (Array.isArray(item.content)) {
|
|
153
|
+
for (const part of item.content) {
|
|
154
|
+
if (
|
|
155
|
+
part &&
|
|
156
|
+
typeof part === "object" &&
|
|
157
|
+
(part as { type?: unknown }).type === "reasoning_text" &&
|
|
158
|
+
typeof (part as { text?: unknown }).text === "string"
|
|
159
|
+
) {
|
|
160
|
+
contentTexts.push((part as { text: string }).text);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const summaryTexts: string[] = [];
|
|
166
|
+
if (Array.isArray(item.summary)) {
|
|
167
|
+
for (const part of item.summary) {
|
|
168
|
+
if (
|
|
169
|
+
part &&
|
|
170
|
+
typeof part === "object" &&
|
|
171
|
+
(part as { type?: unknown }).type === "summary_text" &&
|
|
172
|
+
typeof (part as { text?: unknown }).text === "string"
|
|
173
|
+
) {
|
|
174
|
+
summaryTexts.push((part as { text: string }).text);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (contentTexts.length > 0) {
|
|
180
|
+
return { text: contentTexts.join("\n"), visibility: "full" };
|
|
181
|
+
}
|
|
182
|
+
if (summaryTexts.length > 0) {
|
|
183
|
+
return { text: summaryTexts.join("\n"), visibility: "summary" };
|
|
184
|
+
}
|
|
185
|
+
if (typeof item.encrypted_content === "string" && item.encrypted_content.length > 0) {
|
|
186
|
+
return { text: "", visibility: "opaque" };
|
|
187
|
+
}
|
|
188
|
+
return { text: "", visibility: "summary" };
|
|
189
|
+
}
|
|
190
|
+
|
|
117
191
|
type ResponsesAPIResponse = {
|
|
118
192
|
id: string;
|
|
119
193
|
model: string;
|
|
@@ -133,12 +207,15 @@ type ResponsesAPIResponse = {
|
|
|
133
207
|
|
|
134
208
|
type ResponsesAPIOutputItem = {
|
|
135
209
|
id: string;
|
|
136
|
-
type: "message" | "reasoning" | "function_call";
|
|
210
|
+
type: "message" | "reasoning" | "function_call" | string;
|
|
137
211
|
role?: string;
|
|
138
|
-
content?: ResponsesContentBlock[];
|
|
212
|
+
content?: ResponsesContentBlock[] | Array<{ type: string; text?: string; [key: string]: unknown }>;
|
|
213
|
+
summary?: Array<{ type: string; text?: string; [key: string]: unknown }>;
|
|
214
|
+
encrypted_content?: string | null;
|
|
139
215
|
name?: string;
|
|
140
216
|
arguments?: string;
|
|
141
217
|
status?: string;
|
|
218
|
+
[key: string]: unknown;
|
|
142
219
|
};
|
|
143
220
|
|
|
144
221
|
function isReplayCanonicalInput(item: ResponsesInputItem): boolean {
|
|
@@ -316,6 +393,29 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
316
393
|
let unknownEventsWarned = false;
|
|
317
394
|
const messageItemsWithDelta = new Set<string>();
|
|
318
395
|
const toolCallNames = new Map<string, string>();
|
|
396
|
+
const reasoningStates = new Map<string, ReasoningStreamState>();
|
|
397
|
+
|
|
398
|
+
const ensureReasoningState = (itemId: string, visibility: ReasoningVisibility = "summary"): ReasoningStreamState => {
|
|
399
|
+
let state = reasoningStates.get(itemId);
|
|
400
|
+
if (!state) {
|
|
401
|
+
state = createReasoningState(visibility);
|
|
402
|
+
reasoningStates.set(itemId, state);
|
|
403
|
+
}
|
|
404
|
+
return state;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const completeReasoning = function* (
|
|
408
|
+
itemId: string,
|
|
409
|
+
text: string,
|
|
410
|
+
visibility: ReasoningVisibility,
|
|
411
|
+
): Generator<AIStreamEvent, void, undefined> {
|
|
412
|
+
const state = ensureReasoningState(itemId, visibility);
|
|
413
|
+
if (state.completed) return;
|
|
414
|
+
state.completed = true;
|
|
415
|
+
state.visibility = visibility;
|
|
416
|
+
yield factory.reasoningCompleted(itemId);
|
|
417
|
+
output.push(reasoningItem(text ? [textBlock(text)] : [], visibility, itemId));
|
|
418
|
+
};
|
|
319
419
|
|
|
320
420
|
for await (const batch of iterateProviderStreamBatches({
|
|
321
421
|
reader,
|
|
@@ -340,9 +440,13 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
340
440
|
case "message":
|
|
341
441
|
yield factory.messageStarted(item.id);
|
|
342
442
|
break;
|
|
343
|
-
case "reasoning":
|
|
344
|
-
|
|
443
|
+
case "reasoning": {
|
|
444
|
+
// OpenAI 公开流默认是 summary;full/opaque 在后续事件中再收紧。
|
|
445
|
+
const visibility = extractReasoningFromOutputItem(item).visibility;
|
|
446
|
+
ensureReasoningState(item.id, visibility);
|
|
447
|
+
yield factory.reasoningStarted(item.id, visibility);
|
|
345
448
|
break;
|
|
449
|
+
}
|
|
346
450
|
case "function_call": {
|
|
347
451
|
const name = typeof item.name === "string" ? item.name : "unknown";
|
|
348
452
|
toolCallNames.set(item.id, name);
|
|
@@ -353,6 +457,19 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
353
457
|
continue;
|
|
354
458
|
}
|
|
355
459
|
|
|
460
|
+
if (sseEvent.type === "response.output_item.done") {
|
|
461
|
+
const item = (sseEvent.data as { item: { id: string; type: string; [key: string]: unknown } }).item;
|
|
462
|
+
if (item.type === "reasoning") {
|
|
463
|
+
const extracted = extractReasoningFromOutputItem(item);
|
|
464
|
+
const state = ensureReasoningState(item.id, extracted.visibility);
|
|
465
|
+
const text =
|
|
466
|
+
extracted.text ||
|
|
467
|
+
(state.doneTexts.length > 0 ? state.doneTexts.join("\n") : "");
|
|
468
|
+
yield* completeReasoning(item.id, text, extracted.visibility || state.visibility);
|
|
469
|
+
}
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
|
|
356
473
|
if (sseEvent.type === "response.output_text.delta") {
|
|
357
474
|
const data = sseEvent.data as { item_id: string; delta: string };
|
|
358
475
|
yield factory.messageDelta(data.item_id, textBlock(data.delta));
|
|
@@ -370,16 +487,83 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
370
487
|
continue;
|
|
371
488
|
}
|
|
372
489
|
|
|
490
|
+
// Modern OpenAI reasoning summary text (publicly streamed for o-series / gpt-5)
|
|
491
|
+
if (sseEvent.type === "response.reasoning_summary_text.delta") {
|
|
492
|
+
const data = sseEvent.data as { item_id: string; delta: string };
|
|
493
|
+
const state = ensureReasoningState(data.item_id, "summary");
|
|
494
|
+
if (state.visibility === "opaque") state.visibility = "summary";
|
|
495
|
+
if (data.delta) {
|
|
496
|
+
state.hasDelta = true;
|
|
497
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
|
|
498
|
+
}
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (sseEvent.type === "response.reasoning_summary_text.done") {
|
|
503
|
+
const data = sseEvent.data as { item_id: string; text: string };
|
|
504
|
+
const state = ensureReasoningState(data.item_id, "summary");
|
|
505
|
+
if (state.visibility === "opaque") state.visibility = "summary";
|
|
506
|
+
if (!state.hasDelta && data.text) {
|
|
507
|
+
state.doneTexts.push(data.text);
|
|
508
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.text));
|
|
509
|
+
} else if (data.text) {
|
|
510
|
+
state.doneTexts.push(data.text);
|
|
511
|
+
}
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Full reasoning text (opt-in via include); upgrade visibility to full
|
|
516
|
+
if (sseEvent.type === "response.reasoning_text.delta") {
|
|
517
|
+
const data = sseEvent.data as { item_id: string; delta: string };
|
|
518
|
+
const state = ensureReasoningState(data.item_id, "full");
|
|
519
|
+
state.visibility = "full";
|
|
520
|
+
if (data.delta) {
|
|
521
|
+
state.hasDelta = true;
|
|
522
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
|
|
523
|
+
}
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (sseEvent.type === "response.reasoning_text.done") {
|
|
528
|
+
const data = sseEvent.data as { item_id: string; text: string };
|
|
529
|
+
const state = ensureReasoningState(data.item_id, "full");
|
|
530
|
+
state.visibility = "full";
|
|
531
|
+
if (!state.hasDelta && data.text) {
|
|
532
|
+
state.doneTexts.push(data.text);
|
|
533
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.text));
|
|
534
|
+
} else if (data.text) {
|
|
535
|
+
state.doneTexts.push(data.text);
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Structural summary part events — known & ignored (text is handled above)
|
|
541
|
+
if (
|
|
542
|
+
sseEvent.type === "response.reasoning_summary_part.added" ||
|
|
543
|
+
sseEvent.type === "response.reasoning_summary_part.done"
|
|
544
|
+
) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Legacy aliases kept for fixtures / older gateways
|
|
373
549
|
if (sseEvent.type === "response.reasoning.delta") {
|
|
374
550
|
const data = sseEvent.data as { item_id: string; delta: string };
|
|
375
|
-
|
|
551
|
+
const state = ensureReasoningState(data.item_id, "full");
|
|
552
|
+
state.visibility = "full";
|
|
553
|
+
if (data.delta) {
|
|
554
|
+
state.hasDelta = true;
|
|
555
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
|
|
556
|
+
}
|
|
376
557
|
continue;
|
|
377
558
|
}
|
|
378
559
|
|
|
379
560
|
if (sseEvent.type === "response.reasoning.done") {
|
|
380
561
|
const data = sseEvent.data as { item_id: string; text: string };
|
|
381
|
-
|
|
382
|
-
|
|
562
|
+
const state = ensureReasoningState(data.item_id, "full");
|
|
563
|
+
if (!state.hasDelta && data.text) {
|
|
564
|
+
yield factory.reasoningDelta(data.item_id, textBlock(data.text));
|
|
565
|
+
}
|
|
566
|
+
yield* completeReasoning(data.item_id, data.text ?? state.doneTexts.join("\n"), "full");
|
|
383
567
|
continue;
|
|
384
568
|
}
|
|
385
569
|
|
|
@@ -410,6 +594,23 @@ export class ResponsesAdapter extends AdapterBase {
|
|
|
410
594
|
|
|
411
595
|
completedResponse = data.response;
|
|
412
596
|
|
|
597
|
+
// Safety net: finalize any still-open reasoning items from the final response payload.
|
|
598
|
+
if (Array.isArray(data.response.output)) {
|
|
599
|
+
for (const item of data.response.output) {
|
|
600
|
+
if (item?.type !== "reasoning" || !item.id) continue;
|
|
601
|
+
const state = reasoningStates.get(item.id);
|
|
602
|
+
if (state?.completed) continue;
|
|
603
|
+
const extracted = extractReasoningFromOutputItem(item);
|
|
604
|
+
const text =
|
|
605
|
+
extracted.text ||
|
|
606
|
+
(state && state.doneTexts.length > 0 ? state.doneTexts.join("\n") : "");
|
|
607
|
+
// Only complete if we already started this item (otherwise aggregator has no active item).
|
|
608
|
+
if (state || reasoningStates.has(item.id)) {
|
|
609
|
+
yield* completeReasoning(item.id, text, extracted.visibility);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
413
614
|
if (sseEvent.type === "response.failed") {
|
|
414
615
|
yield factory.responseWarning(
|
|
415
616
|
`Response failed: ${extractFailureMessage(data.response)}`,
|