@codehz/ai 0.4.5 → 0.7.0

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.
Files changed (46) hide show
  1. package/README.md +223 -77
  2. package/dist/index.d.mts +664 -525
  3. package/dist/index.mjs +3632 -2205
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +19 -8
  6. package/.github/workflows/publish.yml +0 -56
  7. package/.oxfmtrc.json +0 -12
  8. package/.oxlintrc.json +0 -34
  9. package/AGENTS.md +0 -37
  10. package/src/adapters/chat-completions.ts +0 -624
  11. package/src/adapters/index.ts +0 -44
  12. package/src/adapters/messages.ts +0 -635
  13. package/src/adapters/mock.ts +0 -934
  14. package/src/adapters/ollama.ts +0 -526
  15. package/src/adapters/responses.ts +0 -818
  16. package/src/core/aggregator.ts +0 -428
  17. package/src/core/client.ts +0 -36
  18. package/src/core/collect-stream.ts +0 -19
  19. package/src/core/errors.ts +0 -105
  20. package/src/core/event-factory.ts +0 -151
  21. package/src/core/index.ts +0 -18
  22. package/src/core/merge-auxiliary.ts +0 -22
  23. package/src/core/normalize.ts +0 -65
  24. package/src/core/validation.ts +0 -404
  25. package/src/helpers/adapter-auxiliary.ts +0 -155
  26. package/src/helpers/adapter-base.ts +0 -218
  27. package/src/helpers/adapter-security.ts +0 -126
  28. package/src/helpers/auxiliary-collector.ts +0 -166
  29. package/src/helpers/incremental-stream-parser.ts +0 -142
  30. package/src/helpers/index.ts +0 -87
  31. package/src/helpers/mapping.ts +0 -192
  32. package/src/helpers/provider-request-options.ts +0 -25
  33. package/src/helpers/provider-stream.ts +0 -147
  34. package/src/helpers/reasoning-level.ts +0 -85
  35. package/src/helpers/request-mapper.ts +0 -94
  36. package/src/helpers/synthetic-stream.ts +0 -188
  37. package/src/helpers/usage-mapping.ts +0 -110
  38. package/src/index.ts +0 -17
  39. package/src/types/adapter.ts +0 -42
  40. package/src/types/content.ts +0 -15
  41. package/src/types/events.ts +0 -138
  42. package/src/types/index.ts +0 -49
  43. package/src/types/items.ts +0 -57
  44. package/src/types/request.ts +0 -52
  45. package/src/types/response.ts +0 -68
  46. package/tsdown.config.ts +0 -10
@@ -1,635 +0,0 @@
1
- /**
2
- * Messages Adapter
3
- *
4
- * 接入 Anthropic Messages API (messages 端点)。
5
- * 支持:
6
- * - 文本消息流 (text content block)
7
- * - 思维链流 (thinking content block)
8
- * - 工具调用流 (tool_use content block)
9
- * - 高保真 replay(含 opaque continuation)
10
- * - 能力降级 warning
11
- */
12
-
13
- import { AdapterBase } from "../helpers/adapter-base.js";
14
- import { AIRequestError } from "../core/errors.js";
15
- import {
16
- textBlock,
17
- messageItem,
18
- reasoningItem,
19
- toolCallItem,
20
- opaqueItem,
21
- replayFromOutput,
22
- mapStopReason,
23
- contentBlocksToText,
24
- } from "../helpers/mapping.js";
25
- import { assertOpaqueReplayEnvelope } from "../helpers/adapter-security.js";
26
- import { usageFromAnthropicMessages } from "../helpers/usage-mapping.js";
27
- import {
28
- NormalizedRequestMapper,
29
- createSseJsonParser,
30
- openProviderJsonStream,
31
- iterateProviderStreamBatches,
32
- createCompletionGate,
33
- mergeProviderHeaders,
34
- applyExtraBody,
35
- mapMessagesThinking,
36
- } from "../helpers/index.js";
37
-
38
- import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
39
-
40
- // ── 类型 ──────────────────────────────────────────────────────
41
-
42
- export type MessagesAdapterOptions = {
43
- apiKey: string;
44
- apiVersion?: string;
45
- baseUrl?: string;
46
- /** 可注入自定义 fetch 实现(用于测试/代理) */
47
- fetch?: FetchFn;
48
- /** 额外请求头;后写覆盖内置 x-api-key / Content-Type / anthropic-version */
49
- headers?: Record<string, string>;
50
- /** 额外 body 顶层字段;浅层合并,同名键可覆盖 */
51
- extraBody?: Record<string, unknown>;
52
- };
53
-
54
- // ── Messages API 请求类型 ────────────────────────────────────
55
-
56
- type MessagesAPIRequest = {
57
- model: string;
58
- max_tokens: number;
59
- messages: MessagesAPIMessage[];
60
- system?: string;
61
- tools?: MessagesAPITool[];
62
- tool_choice?: { type: "auto" | "none" } | { type: "tool"; name: string };
63
- temperature?: number;
64
- thinking?: { type: "enabled"; budget_tokens: number } | { type: "disabled" };
65
- stream: true;
66
- };
67
-
68
- type MessagesAPIMessage = {
69
- role: "user" | "assistant";
70
- content: string | MessagesAPIContentBlock[];
71
- };
72
-
73
- type MessagesAPIContentBlock =
74
- | { type: "text"; text: string }
75
- | { type: "thinking"; thinking: string; signature?: string }
76
- | { type: "redacted_thinking"; data: string }
77
- | { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
78
- | { type: "tool_result"; tool_use_id: string; content: string | MessagesAPIContentBlock[]; is_error?: boolean };
79
-
80
- type MessagesAPITool = {
81
- name: string;
82
- description?: string;
83
- input_schema: Record<string, unknown>;
84
- };
85
-
86
- const mapper = new NormalizedRequestMapper("messages");
87
-
88
- function isMessagesReplayContentBlock(value: unknown): value is MessagesAPIContentBlock {
89
- if (!value || typeof value !== "object" || !("type" in value)) return false;
90
- const block = value as Record<string, unknown>;
91
- switch (block.type) {
92
- case "text":
93
- return typeof block.text === "string";
94
- case "thinking":
95
- return (
96
- typeof block.thinking === "string" && (block.signature === undefined || typeof block.signature === "string")
97
- );
98
- case "redacted_thinking":
99
- return typeof block.data === "string";
100
- case "tool_use":
101
- return (
102
- typeof block.id === "string" &&
103
- typeof block.name === "string" &&
104
- !!block.input &&
105
- typeof block.input === "object" &&
106
- !Array.isArray(block.input)
107
- );
108
- case "tool_result": {
109
- if (typeof block.tool_use_id !== "string") return false;
110
- if (block.is_error !== undefined && typeof block.is_error !== "boolean") return false;
111
- if (typeof block.content === "string") return true;
112
- if (!Array.isArray(block.content)) return false;
113
- return block.content.every(isMessagesReplayContentBlock);
114
- }
115
- default:
116
- return false;
117
- }
118
- }
119
-
120
- function assertMessagesReplayContent(content: unknown): asserts content is MessagesAPIContentBlock[] {
121
- if (!Array.isArray(content)) {
122
- throw new AIRequestError("Invalid opaque replay payload: content must be an array", "INVALID_OPAQUE_REPLAY");
123
- }
124
- for (let i = 0; i < content.length; i++) {
125
- if (!isMessagesReplayContentBlock(content[i])) {
126
- throw new AIRequestError(
127
- `Invalid opaque replay payload: content[${i}] is not a valid Messages content block`,
128
- "INVALID_OPAQUE_REPLAY",
129
- );
130
- }
131
- }
132
- }
133
-
134
- // ── SSE 事件类型 ──────────────────────────────────────────────
135
-
136
- type MessagesSSEEvent =
137
- | { type: "message_start"; data: { message: MessagesAPIMessageResponse } }
138
- | { type: "content_block_start"; data: { index: number; content_block: { type: string; [key: string]: unknown } } }
139
- | { type: "content_block_delta"; data: { index: number; delta: { type: string; [key: string]: unknown } } }
140
- | { type: "content_block_stop"; data: { index: number } }
141
- | {
142
- type: "message_delta";
143
- data: {
144
- delta: { stop_reason?: string; stop_sequence?: string | null };
145
- usage: {
146
- input_tokens: number;
147
- output_tokens: number;
148
- cache_creation_input_tokens?: number;
149
- cache_read_input_tokens?: number;
150
- };
151
- };
152
- }
153
- | { type: "message_stop"; data: Record<string, never> }
154
- | { type: "ping"; data: Record<string, never> }
155
- | { type: "error"; data: { error: { type: string; message: string } } };
156
-
157
- type MessagesAPIMessageResponse = {
158
- id: string;
159
- type: string;
160
- role: "assistant";
161
- model: string;
162
- content: MessagesAPIContentBlock[];
163
- stop_reason?: "end_turn" | "max_tokens" | "tool_use" | string;
164
- stop_sequence?: string | null;
165
- usage: { input_tokens: number; output_tokens: number };
166
- };
167
-
168
- /** 用 response 级别的命名空间合成 content block 的 item ID,避免多轮工具循环 ID 碰撞 */
169
- function synthesizeItemId(kind: "msg" | "reason" | "reason-redacted", blockIndex: number, responseId: string): string {
170
- return `${kind}-${blockIndex}-${responseId}`;
171
- }
172
-
173
- function parseProviderToolUseInput(input: string): Record<string, unknown> {
174
- try {
175
- const parsed: unknown = JSON.parse(input);
176
- return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
177
- } catch {
178
- return {};
179
- }
180
- }
181
-
182
- // ── Content block 映射 ─────────────────────────────────────────
183
-
184
- function canonicalToMessagesBlock(b: import("../index.js").ContentBlock): MessagesAPIContentBlock {
185
- if (b.type === "text") return { type: "text", text: b.text };
186
- if (b.type === "json") return { type: "text", text: JSON.stringify(b.json) };
187
- throw new AIRequestError(
188
- `messages does not support content block type "${b.type}" in canonical mapping`,
189
- "UNSUPPORTED_CONTENT_BLOCK",
190
- );
191
- }
192
-
193
- function pickProviderHeaders(headers: Headers): Record<string, string> {
194
- const metadata: Record<string, string> = {};
195
-
196
- headers.forEach((value, key) => {
197
- const normalizedKey = key.toLowerCase();
198
- if (
199
- normalizedKey === "request-id" ||
200
- normalizedKey === "x-request-id" ||
201
- normalizedKey === "anthropic-organization-id" ||
202
- normalizedKey === "anthropic-beta" ||
203
- normalizedKey === "retry-after" ||
204
- normalizedKey.startsWith("anthropic-ratelimit-")
205
- ) {
206
- metadata[normalizedKey] = value;
207
- }
208
- });
209
-
210
- return metadata;
211
- }
212
-
213
- function buildStreamMetadata(options: {
214
- apiVersion: string;
215
- message?: MessagesAPIMessageResponse;
216
- stopReason?: string;
217
- stopSequence?: string | null;
218
- }): Record<string, unknown> {
219
- const { apiVersion, message, stopReason, stopSequence } = options;
220
- const metadata: Record<string, unknown> = {
221
- apiVersion,
222
- };
223
-
224
- if (message) {
225
- metadata.message = {
226
- id: message.id,
227
- type: message.type,
228
- role: message.role,
229
- model: message.model,
230
- };
231
- }
232
-
233
- if (stopReason !== undefined || stopSequence !== undefined) {
234
- metadata.stop = {
235
- reason: stopReason,
236
- sequence: stopSequence,
237
- };
238
- }
239
-
240
- return metadata;
241
- }
242
-
243
- // ── Adapter ───────────────────────────────────────────────────
244
-
245
- export class MessagesAdapter extends AdapterBase {
246
- readonly kind = "messages" as const;
247
- readonly isSyntheticStream = false;
248
-
249
- private apiKey: string;
250
- private apiVersion: string;
251
- private baseUrl: string;
252
- private fetchFn: FetchFn;
253
- private headers: Record<string, string> | undefined;
254
- private extraBody: Record<string, unknown> | undefined;
255
-
256
- constructor(options: MessagesAdapterOptions) {
257
- super();
258
- this.apiKey = options.apiKey;
259
- this.apiVersion = options.apiVersion ?? "2023-06-01";
260
- this.baseUrl = options.baseUrl ?? "https://api.anthropic.com/v1";
261
- this.fetchFn = options.fetch ?? globalThis.fetch;
262
- this.headers = options.headers;
263
- this.extraBody = options.extraBody;
264
- }
265
-
266
- // ── buildRequest ──────────────────────────────────────────
267
-
268
- protected buildRequest(request: NormalizedRequest): MessagesAPIRequest {
269
- const messages: MessagesAPIMessage[] = [];
270
- let systemPrompt: string | undefined;
271
- let pendingToolResultMessage: MessagesAPIMessage | undefined;
272
-
273
- // 处理 instructions → system prompt
274
- if (request.instructions) {
275
- systemPrompt = mapper.mapInstructions(request.instructions);
276
- }
277
-
278
- // 处理 input items
279
- for (const item of request.input) {
280
- if (item.type !== "tool_result") {
281
- pendingToolResultMessage = undefined;
282
- }
283
-
284
- switch (item.type) {
285
- case "message": {
286
- const role = item.role === "user" ? "user" : "assistant";
287
- const supportedContent = mapper.ensureTextBlocks(item.content, `input message (${item.role}) content`);
288
- if (supportedContent.length === 1 && supportedContent[0]?.type === "text") {
289
- messages.push({ role, content: supportedContent[0].text });
290
- } else {
291
- messages.push({ role, content: supportedContent.map(canonicalToMessagesBlock) });
292
- }
293
- break;
294
- }
295
- case "tool_call": {
296
- // Anthropic 使用 tool_use block 在 assistant message 中
297
- const lastMsg = messages[messages.length - 1];
298
- const toolBlock: MessagesAPIContentBlock = {
299
- type: "tool_use",
300
- id: item.id,
301
- name: item.name,
302
- input: mapper.parseToolArguments(item),
303
- };
304
-
305
- if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
306
- lastMsg.content.push(toolBlock);
307
- } else {
308
- messages.push({ role: "assistant", content: [toolBlock] });
309
- }
310
- break;
311
- }
312
- case "tool_result": {
313
- const content = mapper.textFromBlocks(item.content, `tool_result ${item.callId} content`);
314
- const block: MessagesAPIContentBlock = {
315
- type: "tool_result",
316
- tool_use_id: item.callId,
317
- content,
318
- is_error: item.outcome !== "success",
319
- };
320
- if (pendingToolResultMessage && typeof pendingToolResultMessage.content !== "string") {
321
- pendingToolResultMessage.content.push(block);
322
- } else {
323
- pendingToolResultMessage = { role: "user", content: [block] };
324
- messages.push(pendingToolResultMessage);
325
- }
326
- break;
327
- }
328
- case "reasoning": {
329
- // 将 reasoning item 转为 thinking block 在 assistant message 中
330
- const text = contentBlocksToText(mapper.ensureReasoningBlocks(item.content, "reasoning content"));
331
- const block: MessagesAPIContentBlock = { type: "thinking", thinking: text };
332
- const lastMsg = messages[messages.length - 1];
333
- if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
334
- lastMsg.content.push(block);
335
- } else {
336
- messages.push({ role: "assistant", content: [block] });
337
- }
338
- break;
339
- }
340
- case "opaque": {
341
- // 尝试从 opaque replay item 中提取 assistant message
342
- if (item.source !== "messages" || item.purpose !== "replay") break;
343
- assertOpaqueReplayEnvelope(item.payload);
344
- const payload = item.payload as Record<string, unknown>;
345
- if (payload.role === "assistant" && "content" in payload) {
346
- assertMessagesReplayContent(payload.content);
347
- mapper.rollbackTrailingAssistantMessages(messages);
348
- messages.push({
349
- role: "assistant",
350
- content: payload.content,
351
- });
352
- }
353
- break;
354
- }
355
- }
356
- }
357
-
358
- const body: MessagesAPIRequest = {
359
- model: request.model,
360
- max_tokens: request.maxOutputTokens ?? 4096,
361
- messages,
362
- stream: true,
363
- };
364
-
365
- if (systemPrompt) body.system = systemPrompt;
366
-
367
- body.tools = mapper.mapToolsIfPresent(
368
- request.tools,
369
- (t): MessagesAPITool => ({
370
- name: t.name,
371
- description: t.description,
372
- input_schema: t.inputSchema,
373
- }),
374
- );
375
-
376
- body.tool_choice = mapper.mapToolChoice<Exclude<MessagesAPIRequest["tool_choice"], undefined>>(request.toolChoice, {
377
- auto: { type: "auto" } as const,
378
- none: { type: "none" } as const,
379
- tool: (name) => ({ type: "tool" as const, name }),
380
- });
381
-
382
- if (request.temperature !== undefined) body.temperature = request.temperature;
383
- if (request.reasoningLevel !== undefined) {
384
- body.thinking = mapMessagesThinking(request.reasoningLevel, body.max_tokens);
385
- }
386
-
387
- return applyExtraBody(body, this.extraBody);
388
- }
389
-
390
- // ── runStream ─────────────────────────────────────────────
391
-
392
- protected async *runStream(
393
- providerRequest: MessagesAPIRequest,
394
- factory: EventFactory,
395
- request: NormalizedRequest,
396
- ): AsyncIterable<AIStreamEvent> {
397
- const auxiliary = this.createAuxiliaryState(request);
398
- const gate = createCompletionGate();
399
-
400
- if (request.metadata) {
401
- yield factory.responseWarning(
402
- "Request metadata is not supported by the Messages adapter",
403
- "UNSUPPORTED_METADATA",
404
- );
405
- }
406
-
407
- const { reader, headers } = await openProviderJsonStream({
408
- fetchFn: this.fetchFn,
409
- url: `${this.baseUrl}/messages`,
410
- headers: mergeProviderHeaders(
411
- {
412
- "Content-Type": "application/json",
413
- "x-api-key": this.apiKey,
414
- "anthropic-version": this.apiVersion,
415
- },
416
- this.headers,
417
- ),
418
- body: providerRequest,
419
- signal: request.signal,
420
- });
421
-
422
- const parser = createSseJsonParser<MessagesSSEEvent>();
423
- const output: OutputItem[] = [];
424
- let messageResponse: MessagesAPIMessageResponse | undefined;
425
- let currentContentBlockIndex = -1;
426
- let currentItemType: "message" | "reasoning" | "tool_call" | null = null;
427
- let currentItemId = "";
428
- let currentToolName = "";
429
- let currentArgsText = "";
430
- let currentThinkingVisibility: "full" | "redacted" = "full";
431
- const rawReplayContent: MessagesAPIContentBlock[] = [];
432
-
433
- let textBuffer = "";
434
- let thinkingBuffer = "";
435
- let argsBuffer = "";
436
-
437
- let stopReason: string | undefined;
438
- let stopSequence: string | null | undefined;
439
- let rawResponseId = "";
440
-
441
- if (request.include?.providerMetadata !== "off") {
442
- const headerMetadata = pickProviderHeaders(headers);
443
- auxiliary.recordProviderMetadata(
444
- "header",
445
- Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : undefined,
446
- );
447
- }
448
-
449
- for await (const batch of iterateProviderStreamBatches({
450
- reader,
451
- parser,
452
- factory,
453
- providerLabel: "Messages",
454
- transportLabel: "SSE event(s)",
455
- incompleteMessage: "Stream ended with an incomplete Messages SSE frame",
456
- })) {
457
- for (const warning of batch.warnings) yield warning;
458
-
459
- for (const sseEvent of batch.items) {
460
- switch (sseEvent.type) {
461
- case "ping":
462
- continue;
463
-
464
- case "error": {
465
- const err = sseEvent.data.error;
466
- yield factory.responseWarning(err.message, err.type);
467
- continue;
468
- }
469
-
470
- case "message_start": {
471
- messageResponse = sseEvent.data.message;
472
- rawResponseId = messageResponse.id;
473
- continue;
474
- }
475
-
476
- case "content_block_start": {
477
- const block = sseEvent.data.content_block;
478
- currentContentBlockIndex = sseEvent.data.index;
479
-
480
- switch (block.type) {
481
- case "text": {
482
- currentItemType = "message";
483
- currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
484
- textBuffer = "";
485
- yield factory.messageStarted(currentItemId);
486
- break;
487
- }
488
- case "thinking": {
489
- currentItemType = "reasoning";
490
- currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
491
- currentThinkingVisibility = "full";
492
- thinkingBuffer = "";
493
- yield factory.reasoningStarted(currentItemId, "full");
494
- break;
495
- }
496
- case "redacted_thinking": {
497
- currentItemType = "reasoning";
498
- currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
499
- currentThinkingVisibility = "redacted";
500
- const data = (block as unknown as { data: string }).data;
501
- yield factory.reasoningStarted(currentItemId, "redacted");
502
- yield factory.reasoningDelta(currentItemId, textBlock(data));
503
- const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
504
- yield factory.reasoningCompleted(currentItemId);
505
- output.push(redactedItem);
506
- rawReplayContent.push({ type: "redacted_thinking", data });
507
- currentItemType = null;
508
- break;
509
- }
510
- case "tool_use": {
511
- const tuBlock = block as unknown as { id: string; name: string };
512
- currentItemType = "tool_call";
513
- currentItemId = tuBlock.id;
514
- currentToolName = tuBlock.name;
515
- currentArgsText = "";
516
- argsBuffer = "";
517
- yield factory.toolCallStarted(currentItemId, currentToolName);
518
- break;
519
- }
520
- }
521
- continue;
522
- }
523
-
524
- case "content_block_delta": {
525
- const delta = sseEvent.data.delta;
526
-
527
- switch (delta.type) {
528
- case "text_delta": {
529
- if (currentItemType === "message" && currentItemId) {
530
- const txt = (delta as unknown as { text: string }).text;
531
- textBuffer += txt;
532
- yield factory.messageDelta(currentItemId, textBlock(txt));
533
- }
534
- break;
535
- }
536
- case "thinking_delta": {
537
- if (currentItemType === "reasoning" && currentItemId) {
538
- const txt = (delta as unknown as { thinking: string }).thinking;
539
- thinkingBuffer += txt;
540
- yield factory.reasoningDelta(currentItemId, textBlock(txt));
541
- }
542
- break;
543
- }
544
- case "input_json_delta": {
545
- if (currentItemType === "tool_call" && currentItemId) {
546
- const partial = (delta as unknown as { partial_json: string }).partial_json;
547
- argsBuffer += partial;
548
- yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
549
- }
550
- break;
551
- }
552
- }
553
- continue;
554
- }
555
-
556
- case "content_block_stop": {
557
- if (currentItemType === "message" && currentItemId) {
558
- yield factory.messageCompleted(currentItemId);
559
- output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
560
- rawReplayContent.push({ type: "text", text: textBuffer });
561
- } else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
562
- yield factory.reasoningCompleted(currentItemId);
563
- output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
564
- rawReplayContent.push({ type: "thinking", thinking: thinkingBuffer });
565
- } else if (currentItemType === "tool_call" && currentItemId) {
566
- const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
567
- yield factory.toolCallCompleted(currentItemId);
568
- output.push(tcItem);
569
- rawReplayContent.push({
570
- type: "tool_use",
571
- id: currentItemId,
572
- name: currentToolName,
573
- input: parseProviderToolUseInput(currentArgsText || argsBuffer),
574
- });
575
- }
576
-
577
- currentItemType = null;
578
- currentItemId = "";
579
- continue;
580
- }
581
-
582
- case "message_delta": {
583
- stopReason = sseEvent.data.delta.stop_reason;
584
- stopSequence = sseEvent.data.delta.stop_sequence;
585
- const u = sseEvent.data.usage;
586
- if (u) {
587
- auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
588
- }
589
- continue;
590
- }
591
-
592
- case "message_stop": {
593
- break;
594
- }
595
- }
596
- }
597
- }
598
-
599
- const replay = [...replayFromOutput(output)];
600
-
601
- if (messageResponse) {
602
- const replayContent = rawReplayContent.length > 0 ? rawReplayContent : messageResponse.content;
603
- replay.push(
604
- opaqueItem("messages", "replay", {
605
- replaceCanonical: true,
606
- role: messageResponse.role,
607
- content: replayContent,
608
- messageId: messageResponse.id,
609
- stopReason: stopReason ?? messageResponse.stop_reason,
610
- }),
611
- );
612
- }
613
-
614
- if (request.include?.providerMetadata !== "off") {
615
- auxiliary.recordProviderMetadata(
616
- "stream",
617
- buildStreamMetadata({
618
- apiVersion: this.apiVersion,
619
- message: messageResponse,
620
- stopReason,
621
- stopSequence,
622
- }),
623
- );
624
- }
625
-
626
- if (gate.tryComplete()) {
627
- yield* this.emitStreamCompleted(factory, request, auxiliary, {
628
- output,
629
- replay,
630
- stopReason: stopReason ? mapStopReason(stopReason) : undefined,
631
- rawResponseId,
632
- });
633
- }
634
- }
635
- }