@codehz/ai 0.2.4 → 0.4.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.
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { AdapterBase } from "../helpers/adapter-base.js";
14
- import { AIProviderError, AIRequestError, AIStreamError } from "../core/errors.js";
14
+ import { AIRequestError } from "../core/errors.js";
15
15
  import {
16
16
  textBlock,
17
17
  messageItem,
@@ -20,14 +20,17 @@ import {
20
20
  opaqueItem,
21
21
  replayFromOutput,
22
22
  mapStopReason,
23
- blockToText,
24
23
  contentBlocksToText,
25
24
  } from "../helpers/mapping.js";
26
- import { emitMalformedStreamWarning } from "../helpers/adapter-auxiliary.js";
27
- import { assertOpaqueReplayEnvelope, providerHttpError } from "../helpers/adapter-security.js";
25
+ import { assertOpaqueReplayEnvelope } from "../helpers/adapter-security.js";
28
26
  import { usageFromAnthropicMessages } from "../helpers/usage-mapping.js";
29
- import { NormalizedRequestMapper, splitSSEFrames, IncrementalStreamParser } from "../helpers/index.js";
30
- import type { ProviderProfile } from "../helpers/index.js";
27
+ import {
28
+ NormalizedRequestMapper,
29
+ createSseJsonParser,
30
+ openProviderJsonStream,
31
+ iterateProviderStreamBatches,
32
+ createCompletionGate,
33
+ } from "../helpers/index.js";
31
34
 
32
35
  import type { NormalizedRequest, AIStreamEvent, EventFactory, OutputItem, FetchFn } from "../index.js";
33
36
 
@@ -73,24 +76,7 @@ type MessagesAPITool = {
73
76
  input_schema: Record<string, unknown>;
74
77
  };
75
78
 
76
- // ── ProviderProfile & Mapper ────────────────────────────────────
77
-
78
- const profile: ProviderProfile = {
79
- kind: "messages",
80
- instructionsMode: "system_message",
81
- supportedBlockTypes: ["text", "json"] as const,
82
- reasoningBlockTypes: ["text"] as const,
83
- capabilities: {
84
- textStreaming: "native",
85
- reasoningStreaming: "native",
86
- toolCallStreaming: "synthetic",
87
- replay: "opaque",
88
- usage: "stream",
89
- toolResultOutcomes: ["success", "error"],
90
- },
91
- };
92
-
93
- const mapper = new NormalizedRequestMapper(profile);
79
+ const mapper = new NormalizedRequestMapper("messages");
94
80
 
95
81
  function isMessagesReplayContentBlock(value: unknown): value is MessagesAPIContentBlock {
96
82
  if (!value || typeof value !== "object" || !("type" in value)) return false;
@@ -177,10 +163,10 @@ function synthesizeItemId(kind: "msg" | "reason" | "reason-redacted", blockIndex
177
163
  return `${kind}-${blockIndex}-${responseId}`;
178
164
  }
179
165
 
180
- function parseToolUseInput(input: string): Record<string, unknown> {
166
+ function parseProviderToolUseInput(input: string): Record<string, unknown> {
181
167
  try {
182
- const parsed = JSON.parse(input);
183
- return parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {};
168
+ const parsed: unknown = JSON.parse(input);
169
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};
184
170
  } catch {
185
171
  return {};
186
172
  }
@@ -251,7 +237,7 @@ function buildStreamMetadata(options: {
251
237
 
252
238
  export class MessagesAdapter extends AdapterBase {
253
239
  readonly kind = "messages" as const;
254
- readonly capabilities = profile.capabilities;
240
+ readonly isSyntheticStream = false;
255
241
 
256
242
  private apiKey: string;
257
243
  private apiVersion: string;
@@ -302,7 +288,7 @@ export class MessagesAdapter extends AdapterBase {
302
288
  type: "tool_use",
303
289
  id: item.id,
304
290
  name: item.name,
305
- input: (item.argumentsJson as Record<string, unknown> | undefined) ?? parseToolUseInput(item.argumentsText),
291
+ input: mapper.parseToolArguments(item),
306
292
  };
307
293
 
308
294
  if (lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content !== "string") {
@@ -313,16 +299,12 @@ export class MessagesAdapter extends AdapterBase {
313
299
  break;
314
300
  }
315
301
  case "tool_result": {
316
- mapper.assertToolResultOutcome(item.outcome);
317
- const content = mapper
318
- .ensureTextBlocks(item.content, `tool_result ${item.callId} content`)
319
- .map(blockToText)
320
- .join("\n");
302
+ const content = mapper.textFromBlocks(item.content, `tool_result ${item.callId} content`);
321
303
  const block: MessagesAPIContentBlock = {
322
304
  type: "tool_result",
323
305
  tool_use_id: item.callId,
324
306
  content,
325
- is_error: item.outcome === "error",
307
+ is_error: item.outcome !== "success",
326
308
  };
327
309
  if (pendingToolResultMessage && typeof pendingToolResultMessage.content !== "string") {
328
310
  pendingToolResultMessage.content.push(block);
@@ -346,7 +328,7 @@ export class MessagesAdapter extends AdapterBase {
346
328
  }
347
329
  case "opaque": {
348
330
  // 尝试从 opaque replay item 中提取 assistant message
349
- if (item.purpose !== "replay") break;
331
+ if (item.source !== "messages" || item.purpose !== "replay") break;
350
332
  assertOpaqueReplayEnvelope(item.payload);
351
333
  const payload = item.payload as Record<string, unknown>;
352
334
  if (payload.role === "assistant" && "content" in payload) {
@@ -371,23 +353,20 @@ export class MessagesAdapter extends AdapterBase {
371
353
 
372
354
  if (systemPrompt) body.system = systemPrompt;
373
355
 
374
- if (request.tools && request.tools.length > 0) {
375
- body.tools = request.tools.map(
376
- (t): MessagesAPITool => ({
377
- name: t.name,
378
- description: t.description,
379
- input_schema: t.inputSchema,
380
- }),
381
- );
382
- }
383
-
384
- if (request.toolChoice) {
385
- if (request.toolChoice === "auto") body.tool_choice = { type: "auto" };
386
- else if (request.toolChoice === "none") body.tool_choice = { type: "none" };
387
- else if (request.toolChoice.type === "tool") {
388
- body.tool_choice = { type: "tool", name: request.toolChoice.name };
389
- }
390
- }
356
+ body.tools = mapper.mapToolsIfPresent(
357
+ request.tools,
358
+ (t): MessagesAPITool => ({
359
+ name: t.name,
360
+ description: t.description,
361
+ input_schema: t.inputSchema,
362
+ }),
363
+ );
364
+
365
+ body.tool_choice = mapper.mapToolChoice<Exclude<MessagesAPIRequest["tool_choice"], undefined>>(request.toolChoice, {
366
+ auto: { type: "auto" } as const,
367
+ none: { type: "none" } as const,
368
+ tool: (name) => ({ type: "tool" as const, name }),
369
+ });
391
370
 
392
371
  if (request.temperature !== undefined) body.temperature = request.temperature;
393
372
 
@@ -402,7 +381,7 @@ export class MessagesAdapter extends AdapterBase {
402
381
  request: NormalizedRequest,
403
382
  ): AsyncIterable<AIStreamEvent> {
404
383
  const auxiliary = this.createAuxiliaryState(request);
405
- let completedEmitted = false;
384
+ const gate = createCompletionGate();
406
385
 
407
386
  if (request.metadata) {
408
387
  yield factory.responseWarning(
@@ -411,53 +390,20 @@ export class MessagesAdapter extends AdapterBase {
411
390
  );
412
391
  }
413
392
 
414
- let response: Response;
415
-
416
- try {
417
- response = await this.fetchFn(`${this.baseUrl}/messages`, {
418
- method: "POST",
419
- headers: {
420
- "Content-Type": "application/json",
421
- "x-api-key": this.apiKey,
422
- "anthropic-version": this.apiVersion,
423
- },
424
- body: JSON.stringify(providerRequest),
425
- signal: request.signal,
426
- });
427
- } catch (err) {
428
- throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
429
- }
430
-
431
- if (!response.ok) {
432
- const errorBody = await response.text().catch(() => "");
433
- throw providerHttpError(response.status, errorBody);
434
- }
435
-
436
- const reader = response.body?.getReader();
437
- if (!reader) {
438
- throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
439
- }
440
-
441
- // 流累积状态
442
- const parser = new IncrementalStreamParser<MessagesSSEEvent>(splitSSEFrames, (frame: string) => {
443
- let eventType = "";
444
- let dataStr = "";
445
- for (const rawLine of frame.split("\n")) {
446
- const line = rawLine.trim();
447
- if (line.startsWith("event: ")) eventType = line.slice(7).trim();
448
- else if (line.startsWith("data: ")) dataStr += line.slice(6);
449
- }
450
- if (!eventType) return { status: "ignored" };
451
- try {
452
- const data = JSON.parse(dataStr);
453
- return { status: "parsed", value: { type: eventType, data } as MessagesSSEEvent };
454
- } catch {
455
- return { status: "malformed" };
456
- }
393
+ const { reader, headers } = await openProviderJsonStream({
394
+ fetchFn: this.fetchFn,
395
+ url: `${this.baseUrl}/messages`,
396
+ headers: {
397
+ "Content-Type": "application/json",
398
+ "x-api-key": this.apiKey,
399
+ "anthropic-version": this.apiVersion,
400
+ },
401
+ body: providerRequest,
402
+ signal: request.signal,
457
403
  });
458
404
 
405
+ const parser = createSseJsonParser<MessagesSSEEvent>();
459
406
  const output: OutputItem[] = [];
460
- let streamDone = false;
461
407
  let messageResponse: MessagesAPIMessageResponse | undefined;
462
408
  let currentContentBlockIndex = -1;
463
409
  let currentItemType: "message" | "reasoning" | "tool_call" | null = null;
@@ -465,215 +411,176 @@ export class MessagesAdapter extends AdapterBase {
465
411
  let currentToolName = "";
466
412
  let currentArgsText = "";
467
413
  let currentThinkingVisibility: "full" | "redacted" = "full";
468
- let hasStreamedReasoning = false;
469
414
  const rawReplayContent: MessagesAPIContentBlock[] = [];
470
415
 
471
- // 内容块累积缓冲
472
416
  let textBuffer = "";
473
417
  let thinkingBuffer = "";
474
418
  let argsBuffer = "";
475
419
 
476
- // 完成响应数据
477
420
  let stopReason: string | undefined;
478
421
  let stopSequence: string | null | undefined;
479
422
  let rawResponseId = "";
480
423
 
481
424
  if (request.include?.providerMetadata !== "off") {
482
- const headerMetadata = pickProviderHeaders(response.headers);
425
+ const headerMetadata = pickProviderHeaders(headers);
483
426
  auxiliary.recordProviderMetadata(
484
427
  "header",
485
428
  Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : undefined,
486
429
  );
487
430
  }
488
431
 
489
- try {
490
- while (true) {
491
- const readResult = await reader.read().catch((err: unknown) => {
492
- throw new AIStreamError(
493
- `Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`,
494
- "STREAM_ERROR",
495
- );
496
- });
497
- const { done, value } = readResult;
498
- const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value as Uint8Array);
499
-
500
- const malformedWarning = emitMalformedStreamWarning(factory, {
501
- count: malformedEvents,
502
- providerLabel: "Messages",
503
- transportLabel: "SSE event(s)",
504
- });
505
- if (malformedWarning) {
506
- yield malformedWarning;
507
- }
508
-
509
- for (const sseEvent of events) {
510
- switch (sseEvent.type) {
511
- case "ping":
512
- continue;
432
+ for await (const batch of iterateProviderStreamBatches({
433
+ reader,
434
+ parser,
435
+ factory,
436
+ providerLabel: "Messages",
437
+ transportLabel: "SSE event(s)",
438
+ incompleteMessage: "Stream ended with an incomplete Messages SSE frame",
439
+ })) {
440
+ for (const warning of batch.warnings) yield warning;
441
+
442
+ for (const sseEvent of batch.items) {
443
+ switch (sseEvent.type) {
444
+ case "ping":
445
+ continue;
446
+
447
+ case "error": {
448
+ const err = sseEvent.data.error;
449
+ yield factory.responseWarning(err.message, err.type);
450
+ continue;
451
+ }
513
452
 
514
- case "error": {
515
- const err = sseEvent.data.error;
516
- yield factory.responseWarning(err.message, err.type);
517
- continue;
518
- }
453
+ case "message_start": {
454
+ messageResponse = sseEvent.data.message;
455
+ rawResponseId = messageResponse.id;
456
+ continue;
457
+ }
519
458
 
520
- case "message_start": {
521
- messageResponse = sseEvent.data.message;
522
- rawResponseId = messageResponse.id;
523
- // 检查是否有 thinking 能力
524
- if (messageResponse.content.some((b) => b.type === "thinking" || b.type === "redacted_thinking")) {
525
- hasStreamedReasoning = true;
459
+ case "content_block_start": {
460
+ const block = sseEvent.data.content_block;
461
+ currentContentBlockIndex = sseEvent.data.index;
462
+
463
+ switch (block.type) {
464
+ case "text": {
465
+ currentItemType = "message";
466
+ currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
467
+ textBuffer = "";
468
+ yield factory.messageStarted(currentItemId);
469
+ break;
526
470
  }
527
- continue;
528
- }
529
-
530
- case "content_block_start": {
531
- const block = sseEvent.data.content_block;
532
- currentContentBlockIndex = sseEvent.data.index;
533
-
534
- switch (block.type) {
535
- case "text": {
536
- currentItemType = "message";
537
- currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
538
- textBuffer = "";
539
- yield factory.messageStarted(currentItemId);
540
- break;
541
- }
542
- case "thinking": {
543
- hasStreamedReasoning = true;
544
- currentItemType = "reasoning";
545
- currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
546
- currentThinkingVisibility = "full";
547
- thinkingBuffer = "";
548
- yield factory.reasoningStarted(currentItemId, "full");
549
- break;
550
- }
551
- case "redacted_thinking": {
552
- hasStreamedReasoning = true;
553
- currentItemType = "reasoning";
554
- currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
555
- currentThinkingVisibility = "redacted";
556
- const data = (block as unknown as { data: string }).data;
557
- yield factory.reasoningStarted(currentItemId, "redacted");
558
- yield factory.reasoningDelta(currentItemId, textBlock(data));
559
- const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
560
- yield factory.reasoningCompleted(currentItemId);
561
- output.push(redactedItem);
562
- rawReplayContent.push({ type: "redacted_thinking", data });
563
- currentItemType = null;
564
- break;
565
- }
566
- case "tool_use": {
567
- const tuBlock = block as unknown as { id: string; name: string };
568
- currentItemType = "tool_call";
569
- currentItemId = tuBlock.id;
570
- currentToolName = tuBlock.name;
571
- currentArgsText = "";
572
- argsBuffer = "";
573
- yield factory.toolCallStarted(currentItemId, currentToolName);
574
- break;
575
- }
471
+ case "thinking": {
472
+ currentItemType = "reasoning";
473
+ currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
474
+ currentThinkingVisibility = "full";
475
+ thinkingBuffer = "";
476
+ yield factory.reasoningStarted(currentItemId, "full");
477
+ break;
478
+ }
479
+ case "redacted_thinking": {
480
+ currentItemType = "reasoning";
481
+ currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
482
+ currentThinkingVisibility = "redacted";
483
+ const data = (block as unknown as { data: string }).data;
484
+ yield factory.reasoningStarted(currentItemId, "redacted");
485
+ yield factory.reasoningDelta(currentItemId, textBlock(data));
486
+ const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
487
+ yield factory.reasoningCompleted(currentItemId);
488
+ output.push(redactedItem);
489
+ rawReplayContent.push({ type: "redacted_thinking", data });
490
+ currentItemType = null;
491
+ break;
492
+ }
493
+ case "tool_use": {
494
+ const tuBlock = block as unknown as { id: string; name: string };
495
+ currentItemType = "tool_call";
496
+ currentItemId = tuBlock.id;
497
+ currentToolName = tuBlock.name;
498
+ currentArgsText = "";
499
+ argsBuffer = "";
500
+ yield factory.toolCallStarted(currentItemId, currentToolName);
501
+ break;
576
502
  }
577
- continue;
578
503
  }
504
+ continue;
505
+ }
506
+
507
+ case "content_block_delta": {
508
+ const delta = sseEvent.data.delta;
579
509
 
580
- case "content_block_delta": {
581
- const delta = sseEvent.data.delta;
582
-
583
- switch (delta.type) {
584
- case "text_delta": {
585
- if (currentItemType === "message" && currentItemId) {
586
- const txt = (delta as unknown as { text: string }).text;
587
- textBuffer += txt;
588
- yield factory.messageDelta(currentItemId, textBlock(txt));
589
- }
590
- break;
510
+ switch (delta.type) {
511
+ case "text_delta": {
512
+ if (currentItemType === "message" && currentItemId) {
513
+ const txt = (delta as unknown as { text: string }).text;
514
+ textBuffer += txt;
515
+ yield factory.messageDelta(currentItemId, textBlock(txt));
591
516
  }
592
- case "thinking_delta": {
593
- if (currentItemType === "reasoning" && currentItemId) {
594
- const txt = (delta as unknown as { thinking: string }).thinking;
595
- thinkingBuffer += txt;
596
- yield factory.reasoningDelta(currentItemId, textBlock(txt));
597
- }
598
- break;
517
+ break;
518
+ }
519
+ case "thinking_delta": {
520
+ if (currentItemType === "reasoning" && currentItemId) {
521
+ const txt = (delta as unknown as { thinking: string }).thinking;
522
+ thinkingBuffer += txt;
523
+ yield factory.reasoningDelta(currentItemId, textBlock(txt));
599
524
  }
600
- case "input_json_delta": {
601
- if (currentItemType === "tool_call" && currentItemId) {
602
- const partial = (delta as unknown as { partial_json: string }).partial_json;
603
- argsBuffer += partial;
604
- yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
605
- }
606
- break;
525
+ break;
526
+ }
527
+ case "input_json_delta": {
528
+ if (currentItemType === "tool_call" && currentItemId) {
529
+ const partial = (delta as unknown as { partial_json: string }).partial_json;
530
+ argsBuffer += partial;
531
+ yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
607
532
  }
533
+ break;
608
534
  }
609
- continue;
610
535
  }
536
+ continue;
537
+ }
611
538
 
612
- case "content_block_stop": {
613
- if (currentItemType === "message" && currentItemId) {
614
- yield factory.messageCompleted(currentItemId);
615
- output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
616
- rawReplayContent.push({ type: "text", text: textBuffer });
617
- } else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
618
- yield factory.reasoningCompleted(currentItemId);
619
- output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
620
- rawReplayContent.push({ type: "thinking", thinking: thinkingBuffer });
621
- } else if (currentItemType === "tool_call" && currentItemId) {
622
- const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
623
- yield factory.toolCallCompleted(currentItemId);
624
- output.push(tcItem);
625
- rawReplayContent.push({
626
- type: "tool_use",
627
- id: currentItemId,
628
- name: currentToolName,
629
- input: parseToolUseInput(currentArgsText || argsBuffer),
630
- });
631
- }
632
-
633
- currentItemType = null;
634
- currentItemId = "";
635
- continue;
539
+ case "content_block_stop": {
540
+ if (currentItemType === "message" && currentItemId) {
541
+ yield factory.messageCompleted(currentItemId);
542
+ output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
543
+ rawReplayContent.push({ type: "text", text: textBuffer });
544
+ } else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
545
+ yield factory.reasoningCompleted(currentItemId);
546
+ output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
547
+ rawReplayContent.push({ type: "thinking", thinking: thinkingBuffer });
548
+ } else if (currentItemType === "tool_call" && currentItemId) {
549
+ const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
550
+ yield factory.toolCallCompleted(currentItemId);
551
+ output.push(tcItem);
552
+ rawReplayContent.push({
553
+ type: "tool_use",
554
+ id: currentItemId,
555
+ name: currentToolName,
556
+ input: parseProviderToolUseInput(currentArgsText || argsBuffer),
557
+ });
636
558
  }
637
559
 
638
- case "message_delta": {
639
- stopReason = sseEvent.data.delta.stop_reason;
640
- stopSequence = sseEvent.data.delta.stop_sequence;
641
- const u = sseEvent.data.usage;
642
- if (u) {
643
- auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
644
- }
645
- continue;
646
- }
560
+ currentItemType = null;
561
+ currentItemId = "";
562
+ continue;
563
+ }
647
564
 
648
- case "message_stop": {
649
- // 流结束,构造 final response
650
- break;
565
+ case "message_delta": {
566
+ stopReason = sseEvent.data.delta.stop_reason;
567
+ stopSequence = sseEvent.data.delta.stop_sequence;
568
+ const u = sseEvent.data.usage;
569
+ if (u) {
570
+ auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
651
571
  }
572
+ continue;
652
573
  }
653
- }
654
574
 
655
- if (done) {
656
- streamDone = true;
657
- break;
575
+ case "message_stop": {
576
+ break;
577
+ }
658
578
  }
659
579
  }
660
- } finally {
661
- try {
662
- if (!streamDone) await reader.cancel().catch(() => undefined);
663
- } finally {
664
- reader.releaseLock();
665
- }
666
580
  }
667
581
 
668
- if (parser.getRemaining().trim().length > 0) {
669
- yield factory.responseWarning("Stream ended with an incomplete Messages SSE frame", "STREAM_ERROR");
670
- }
671
-
672
- // 构造 replay
673
582
  const replay = [...replayFromOutput(output)];
674
583
 
675
- // 附加 opaque replay item 用于续接
676
- // 保存 provider 原始 block 以实现高保真 replay
677
584
  if (messageResponse) {
678
585
  const replayContent = rawReplayContent.length > 0 ? rawReplayContent : messageResponse.content;
679
586
  replay.push(
@@ -699,41 +606,12 @@ export class MessagesAdapter extends AdapterBase {
699
606
  );
700
607
  }
701
608
 
702
- // 警告低 replay fidelity
703
- if (!hasStreamedReasoning) {
704
- // 没有 reasoning,replay fidelity 较低
705
- }
706
-
707
- const auxiliaryResult = await auxiliary.finalize(factory);
708
- for (const event of auxiliaryResult.events) {
709
- yield event;
710
- }
711
-
712
- if (!completedEmitted) {
713
- completedEmitted = true;
714
- const finalResponse = this.buildResponse(
715
- request,
716
- {
717
- output,
718
- replay,
719
- stopReason: stopReason ? mapStopReason(stopReason) : undefined,
720
- usage: auxiliaryResult.usage,
721
- billing: auxiliaryResult.billing,
722
- auxiliary: auxiliaryResult.auxiliary,
723
- warnings: auxiliaryResult.warnings,
724
- metadataSources: auxiliaryResult.metadataSources,
725
- rawResponseId,
726
- },
727
- factory,
728
- );
729
- yield factory.responseCompleted({
730
- replay: finalResponse.replay,
731
- stopReason: finalResponse.stopReason,
732
- trace: finalResponse.backend,
733
- usage: finalResponse.usage,
734
- billing: finalResponse.billing,
735
- auxiliary: finalResponse.auxiliary,
736
- warnings: finalResponse.warnings,
609
+ if (gate.tryComplete()) {
610
+ yield* this.emitStreamCompleted(factory, request, auxiliary, {
611
+ output,
612
+ replay,
613
+ stopReason: stopReason ? mapStopReason(stopReason) : undefined,
614
+ rawResponseId,
737
615
  });
738
616
  }
739
617
  }
@@ -120,7 +120,6 @@ export type MockToolCallStep = {
120
120
  id: string;
121
121
  name: string;
122
122
  argumentsText: string;
123
- argumentsJson?: unknown;
124
123
  streamArguments?: boolean;
125
124
  stream?: MockTextStreamOptions | false;
126
125
  };
@@ -266,14 +265,7 @@ export function assertMockRequest(
266
265
 
267
266
  export class MockAdapter extends AdapterBase {
268
267
  readonly kind = "mock" as const;
269
- readonly capabilities = {
270
- textStreaming: "synthetic",
271
- reasoningStreaming: "synthetic",
272
- toolCallStreaming: "synthetic",
273
- replay: "canonical",
274
- usage: "final",
275
- toolResultOutcomes: ["success", "error", "rejected"],
276
- } as const;
268
+ readonly isSyntheticStream = true;
277
269
 
278
270
  private readonly handler: MockHandler;
279
271
  private readonly providerMetadata?: Record<string, unknown>;
@@ -572,7 +564,6 @@ function createToolCallFromStep(step: MockToolCallStep): ToolCallItem {
572
564
  id: step.id,
573
565
  name: step.name,
574
566
  argumentsText: step.argumentsText,
575
- argumentsJson: step.argumentsJson,
576
567
  };
577
568
  }
578
569