@codehz/ai 0.2.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codehz/ai",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "module": "dist/index.mjs",
6
6
  "exports": {
@@ -458,9 +458,7 @@ export class ChatCompletionsAdapter extends AdapterBase {
458
458
  if (hasMessageStarted) {
459
459
  const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
460
460
  events.push(factory.messageCompleted(currentMessageId));
461
- if (accumulatedContent) {
462
- output.push(message);
463
- }
461
+ output.push(message);
464
462
  }
465
463
 
466
464
  for (const pending of finalizedToolCalls) {
@@ -599,12 +597,6 @@ export class ChatCompletionsAdapter extends AdapterBase {
599
597
  accumulatedContent = "";
600
598
  };
601
599
 
602
- // 处理 role: assistant(首块标识;不要求 content)
603
- if (delta.role === "assistant" && !hasMessageStarted) {
604
- ensureMessageStarted();
605
- yield factory.messageStarted(currentMessageId);
606
- }
607
-
608
600
  // 处理 third-party reasoning delta
609
601
  if (reasoningDeltas.length > 0) {
610
602
  if (!hasReasoningStarted) {
@@ -78,6 +78,8 @@ export interface AggregatorState {
78
78
  completed: boolean;
79
79
  nextSequence?: number;
80
80
  activeItems: Map<string, ActiveItem>;
81
+ itemOrder: string[];
82
+ completedItems: Map<string, OutputItem>;
81
83
 
82
84
  /** adapter 在 response.completed 中提供的 replay */
83
85
  replayFromAdapter?: import("../types/index.js").ReplayItem[];
@@ -96,6 +98,8 @@ export function createAggregatorState(): AggregatorState {
96
98
  started: false,
97
99
  completed: false,
98
100
  activeItems: new Map(),
101
+ itemOrder: [],
102
+ completedItems: new Map(),
99
103
  };
100
104
  }
101
105
 
@@ -112,12 +116,25 @@ function getActiveItem(state: AggregatorState, itemId: string, expectedType: Act
112
116
  return item;
113
117
  }
114
118
 
119
+ function coalesceContentBlocks(blocks: readonly ContentBlock[]): ContentBlock[] {
120
+ const result: ContentBlock[] = [];
121
+ for (const block of blocks) {
122
+ const previous = result[result.length - 1];
123
+ if (block.type === "text" && previous?.type === "text") {
124
+ previous.text += block.text;
125
+ } else {
126
+ result.push({ ...block });
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+
115
132
  function finalizeMessage(active: ActiveMessage): MessageItem {
116
133
  return {
117
134
  type: "message",
118
135
  id: active.id,
119
136
  role: active.role,
120
- content: active.content,
137
+ content: coalesceContentBlocks(active.content),
121
138
  };
122
139
  }
123
140
 
@@ -126,7 +143,7 @@ function finalizeReasoning(active: ActiveReasoning): import("../types/index.js")
126
143
  type: "reasoning",
127
144
  id: active.id,
128
145
  visibility: active.visibility,
129
- content: active.content,
146
+ content: coalesceContentBlocks(active.content),
130
147
  };
131
148
  }
132
149
 
@@ -178,6 +195,7 @@ function handleMessageStarted(state: AggregatorState, event: AIStreamEvent & { t
178
195
  role: event.item.role,
179
196
  content: [],
180
197
  });
198
+ state.itemOrder.push(id);
181
199
  }
182
200
 
183
201
  function handleMessageDelta(state: AggregatorState, event: AIStreamEvent & { type: "message.delta" }): void {
@@ -189,7 +207,7 @@ function handleMessageCompleted(state: AggregatorState, event: AIStreamEvent & {
189
207
  const active = getActiveItem(state, event.itemId, "message") as ActiveMessage;
190
208
  state.activeItems.delete(event.itemId);
191
209
  const item = finalizeMessage(active);
192
- state.output.push(item);
210
+ state.completedItems.set(event.itemId, item);
193
211
  pushMessageText(state, item);
194
212
  }
195
213
 
@@ -204,6 +222,7 @@ function handleReasoningStarted(state: AggregatorState, event: AIStreamEvent & {
204
222
  visibility: event.item.visibility,
205
223
  content: [],
206
224
  });
225
+ state.itemOrder.push(id);
207
226
  }
208
227
 
209
228
  function handleReasoningDelta(state: AggregatorState, event: AIStreamEvent & { type: "reasoning.delta" }): void {
@@ -217,7 +236,7 @@ function handleReasoningCompleted(
217
236
  ): void {
218
237
  const active = getActiveItem(state, event.itemId, "reasoning") as ActiveReasoning;
219
238
  state.activeItems.delete(event.itemId);
220
- state.output.push(finalizeReasoning(active));
239
+ state.completedItems.set(event.itemId, finalizeReasoning(active));
221
240
  }
222
241
 
223
242
  function handleToolCallStarted(state: AggregatorState, event: AIStreamEvent & { type: "tool_call.started" }): void {
@@ -231,6 +250,7 @@ function handleToolCallStarted(state: AggregatorState, event: AIStreamEvent & {
231
250
  name: event.item.name,
232
251
  argumentsText: "",
233
252
  });
253
+ state.itemOrder.push(id);
234
254
  }
235
255
 
236
256
  function handleToolCallDelta(state: AggregatorState, event: AIStreamEvent & { type: "tool_call.delta" }): void {
@@ -244,7 +264,7 @@ function handleToolCallCompleted(state: AggregatorState, event: AIStreamEvent &
244
264
  const active = getActiveItem(state, event.itemId, "tool_call") as ActiveToolCall;
245
265
  state.activeItems.delete(event.itemId);
246
266
  const item = finalizeToolCall(active);
247
- state.output.push(item);
267
+ state.completedItems.set(event.itemId, item);
248
268
  state.toolCalls.push(item);
249
269
  }
250
270
 
@@ -275,10 +295,17 @@ function buildResponse(state: AggregatorState): AIResponse {
275
295
  metadataSources: backendFromCompleted?.metadataSources,
276
296
  warnings: backendFromCompleted?.warnings,
277
297
  };
298
+ const orderedOutput = state.itemOrder.map((id) => {
299
+ const item = state.completedItems.get(id);
300
+ if (!item) {
301
+ throw streamProtocolError(`Item ${id} was started but not completed`);
302
+ }
303
+ return item;
304
+ });
278
305
 
279
306
  return {
280
307
  id: state.responseId,
281
- output: state.output,
308
+ output: [...orderedOutput, ...state.output],
282
309
  replay: state.replayFromAdapter ?? [],
283
310
  text: state.textParts.join(""),
284
311
  toolCalls: state.toolCalls,