@code-yeongyu/senpi-ai 2026.9.7 → 2026.9.9-2

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 (76) hide show
  1. package/dist/api/anthropic-messages.d.ts.map +1 -1
  2. package/dist/api/anthropic-messages.js +70 -183
  3. package/dist/api/anthropic-messages.js.map +1 -1
  4. package/dist/api/anthropic-tool-references.d.ts +3 -0
  5. package/dist/api/anthropic-tool-references.d.ts.map +1 -0
  6. package/dist/api/anthropic-tool-references.js +260 -0
  7. package/dist/api/anthropic-tool-references.js.map +1 -0
  8. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  9. package/dist/api/openai-codex-responses.js +1 -0
  10. package/dist/api/openai-codex-responses.js.map +1 -1
  11. package/dist/api/openai-images-edit.d.ts +4 -0
  12. package/dist/api/openai-images-edit.d.ts.map +1 -0
  13. package/dist/api/openai-images-edit.js +11 -0
  14. package/dist/api/openai-images-edit.js.map +1 -0
  15. package/dist/api/openai-images-params.d.ts +25 -0
  16. package/dist/api/openai-images-params.d.ts.map +1 -0
  17. package/dist/api/openai-images-params.js +58 -0
  18. package/dist/api/openai-images-params.js.map +1 -0
  19. package/dist/api/openai-images.d.ts +3 -6
  20. package/dist/api/openai-images.d.ts.map +1 -1
  21. package/dist/api/openai-images.js +15 -33
  22. package/dist/api/openai-images.js.map +1 -1
  23. package/dist/api/openai-responses.d.ts.map +1 -1
  24. package/dist/api/openai-responses.js +1 -0
  25. package/dist/api/openai-responses.js.map +1 -1
  26. package/dist/auth/oauth/anthropic-callback-listener.d.ts +40 -0
  27. package/dist/auth/oauth/anthropic-callback-listener.d.ts.map +1 -0
  28. package/dist/auth/oauth/anthropic-callback-listener.js +138 -0
  29. package/dist/auth/oauth/anthropic-callback-listener.js.map +1 -0
  30. package/dist/auth/oauth/anthropic.d.ts +2 -3
  31. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  32. package/dist/auth/oauth/anthropic.js +42 -139
  33. package/dist/auth/oauth/anthropic.js.map +1 -1
  34. package/dist/auth/oauth/authorization-input.d.ts +6 -0
  35. package/dist/auth/oauth/authorization-input.d.ts.map +1 -0
  36. package/dist/auth/oauth/authorization-input.js +29 -0
  37. package/dist/auth/oauth/authorization-input.js.map +1 -0
  38. package/dist/auth/oauth/error-details.d.ts +3 -0
  39. package/dist/auth/oauth/error-details.d.ts.map +1 -0
  40. package/dist/auth/oauth/error-details.js +20 -0
  41. package/dist/auth/oauth/error-details.js.map +1 -0
  42. package/dist/image-models.generated.d.ts +61 -1
  43. package/dist/image-models.generated.d.ts.map +1 -1
  44. package/dist/image-models.generated.js +61 -1
  45. package/dist/image-models.generated.js.map +1 -1
  46. package/dist/images.d.ts +1 -0
  47. package/dist/images.d.ts.map +1 -1
  48. package/dist/images.js +1 -0
  49. package/dist/images.js.map +1 -1
  50. package/dist/providers/data/.manifest.json +1 -1
  51. package/dist/providers/data/amazon-bedrock.json +1 -1
  52. package/dist/providers/data/azure-openai-responses.json +1 -1
  53. package/dist/providers/data/fireworks.json +1 -1
  54. package/dist/providers/data/github-copilot.json +1 -1
  55. package/dist/providers/data/nvidia.json +1 -1
  56. package/dist/providers/data/openai-codex.json +1 -1
  57. package/dist/providers/data/openai.json +1 -1
  58. package/dist/providers/data/opencode.json +1 -1
  59. package/dist/providers/data/opengateway.json +1 -1
  60. package/dist/providers/data/openrouter.json +1 -1
  61. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  62. package/dist/tool-call-middleware/recovery-stream-terminal.d.ts.map +1 -1
  63. package/dist/tool-call-middleware/recovery-stream-terminal.js +16 -1
  64. package/dist/tool-call-middleware/recovery-stream-terminal.js.map +1 -1
  65. package/dist/types.d.ts +7 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/types.js.map +1 -1
  68. package/dist/utils/overflow.d.ts +1 -1
  69. package/dist/utils/overflow.d.ts.map +1 -1
  70. package/dist/utils/overflow.js +14 -3
  71. package/dist/utils/overflow.js.map +1 -1
  72. package/dist/utils/server-fallback-receipt.d.ts +2 -1
  73. package/dist/utils/server-fallback-receipt.d.ts.map +1 -1
  74. package/dist/utils/server-fallback-receipt.js +16 -0
  75. package/dist/utils/server-fallback-receipt.js.map +1 -1
  76. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAMX,+BAA+B,EAK/B,MAAM,uDAAuD,CAAC;AAG/D,OAAO,KAAK,EACX,wBAAwB,EAKxB,OAAO,EAGP,KAAK,EAGL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAqCrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AA6JlE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA+FhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAo4BD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CA2czE,CAAC;AAyFF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA+ClF,CAAC;AA6IF,wBAAgB,mCAAmC,CAClD,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,gBAAgB,GACxB,+BAA+B,CAkBjC"}
1
+ {"version":3,"file":"anthropic-messages.d.ts","sourceRoot":"","sources":["../../src/api/anthropic-messages.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAC1C,OAAO,KAAK,EAMX,+BAA+B,EAK/B,MAAM,uDAAuD,CAAC;AAG/D,OAAO,KAAK,EACX,wBAAwB,EAKxB,OAAO,EAGP,KAAK,EAGL,mBAAmB,EAEnB,cAAc,EACd,aAAa,EAOb,MAAM,aAAa,CAAC;AAsCrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AA6JlE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1E,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG,SAAS,CAAC;AA+FhE,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACtD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,wBAAwB,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;CACnB;AAmvBD,eAAO,MAAM,MAAM,EAAE,cAAc,CAAC,oBAAoB,EAAE,gBAAgB,CAudzE,CAAC;AAyFF,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,oBAAoB,EAAE,mBAAmB,CA+ClF,CAAC;AA6IF,wBAAgB,mCAAmC,CAClD,KAAK,EAAE,KAAK,CAAC,oBAAoB,CAAC,EAClC,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,gBAAgB,GACxB,+BAA+B,CAkBjC"}
@@ -15,12 +15,12 @@ import { retryProviderRequest } from "../utils/provider-retry.js";
15
15
  import { appendRetryAfterMsMarker, extract429RetryAfterMs } from "../utils/retry-hint.js";
16
16
  import { normalizeAnthropicRetryFailure } from "../utils/retry-profile/failure.js";
17
17
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
18
- import { applyServerFallbackAbort, parseServerFallbackReceipt, parseStickyFallbackReceipt, } from "../utils/server-fallback-receipt.js";
18
+ import { applyServerFallbackAbort, applyServerFallbackContinuation, parseServerFallbackReceipt, parseStickyFallbackReceipt, } from "../utils/server-fallback-receipt.js";
19
19
  import { normalizeToolCallId } from "../utils/tool-call-id.js";
20
20
  import { isForcedToolChoiceUnsupportedError, omitToolChoiceParam } from "../utils/tool-choice-fallback.js";
21
21
  import { resolveRootObjectSchema } from "../utils/tool-schema-compat.js";
22
- import { demotedToolCallText, demotedToolResultText } from "../utils/unavailable-tool-text.js";
23
22
  import { sanitizeAnthropicToolPairs } from "./anthropic-tool-pairs.js";
23
+ import { demoteUnavailableToolReferences } from "./anthropic-tool-references.js";
24
24
  import { resolveCloudflareBaseUrl } from "./cloudflare.js";
25
25
  import { getJsonSchemaToolParameters, resolveJsonSchemaStrictSampling } from "./constrained-sampling.js";
26
26
  import { buildCopilotDynamicHeaders, hasCopilotVisionInput } from "./github-copilot-headers.js";
@@ -571,157 +571,12 @@ function sanitizeUnsupportedNativeTools(model, params) {
571
571
  }
572
572
  return changed ? sanitized : params;
573
573
  }
574
- /**
575
- * Anthropic validates that every tool referenced by the message history is
576
- * available in the same request — defined in `tools` or discovered through a
577
- * `tool_reference` block — and rejects the whole request otherwise
578
- * ("Tool reference '<name>' not found in available tools"). Sessions outlive
579
- * their tools: an MCP server can be absent after a resume, an extension can
580
- * stop registering a tool, or a payload hook can strip a definition while the
581
- * history still carries the call. Demote those references to plain text so
582
- * the turn can proceed; the matching tool_result is demoted in lockstep so no
583
- * orphan pairing error replaces the original one.
584
- */
585
- function demoteUnavailableToolReferences(params) {
586
- const messages = params.messages;
587
- if (!Array.isArray(messages) || messages.length === 0)
588
- return params;
589
- const definedNames = new Set();
590
- if (Array.isArray(params.tools)) {
591
- for (const tool of params.tools) {
592
- if (isRecord(tool) && typeof tool.name === "string")
593
- definedNames.add(tool.name);
594
- }
595
- }
596
- // `tool_reference` blocks — emitted for deferred tools or replayed from a
597
- // server-side tool search — make their targets available without a
598
- // non-deferred definition.
599
- const discoveredNames = new Set();
600
- collectToolReferenceNames(messages, discoveredNames);
601
- const demotedCallNames = new Map();
602
- for (const message of messages) {
603
- if (message.role !== "assistant" || !Array.isArray(message.content))
604
- continue;
605
- for (const block of message.content) {
606
- if (isRecord(block) &&
607
- block.type === "tool_use" &&
608
- typeof block.name === "string" &&
609
- !definedNames.has(block.name) &&
610
- !discoveredNames.has(block.name)) {
611
- demotedCallNames.set(block.id, block.name);
612
- }
613
- }
614
- }
615
- // A `tool_reference` without its definition 400s the same way.
616
- const danglingReferenceNames = new Set();
617
- for (const name of discoveredNames) {
618
- if (!definedNames.has(name))
619
- danglingReferenceNames.add(name);
620
- }
621
- if (demotedCallNames.size === 0 && danglingReferenceNames.size === 0)
622
- return params;
623
- let changed = false;
624
- const availableToolNames = [...definedNames];
625
- const seenDemotedCallNames = new Set();
626
- const rewrittenMessages = [];
627
- for (const message of messages) {
628
- if (!Array.isArray(message.content)) {
629
- rewrittenMessages.push(message);
630
- continue;
631
- }
632
- let messageChanged = false;
633
- const content = [];
634
- for (const block of message.content) {
635
- if (message.role === "assistant" && isRecord(block) && block.type === "tool_use") {
636
- const demotedName = demotedCallNames.get(block.id);
637
- if (demotedName !== undefined) {
638
- messageChanged = true;
639
- const firstOccurrence = !seenDemotedCallNames.has(demotedName);
640
- seenDemotedCallNames.add(demotedName);
641
- content.push({
642
- type: "text",
643
- text: demotedToolCallText(demotedName, availableToolNames, firstOccurrence),
644
- });
645
- continue;
646
- }
647
- }
648
- if (isRecord(block) && block.type === "tool_result") {
649
- const demotedName = demotedCallNames.get(block.tool_use_id);
650
- if (demotedName !== undefined) {
651
- messageChanged = true;
652
- content.push({ type: "text", text: demotedToolResultText(demotedName, toolResultText(block.content)) });
653
- continue;
654
- }
655
- if (danglingReferenceNames.size > 0 && Array.isArray(block.content)) {
656
- const kept = [];
657
- const omitted = [];
658
- for (const item of block.content) {
659
- if (isRecord(item) &&
660
- item.type === "tool_reference" &&
661
- typeof item.tool_name === "string" &&
662
- danglingReferenceNames.has(item.tool_name)) {
663
- omitted.push(item.tool_name);
664
- continue;
665
- }
666
- kept.push(item);
667
- }
668
- if (omitted.length > 0) {
669
- messageChanged = true;
670
- const nextContent = kept.length > 0
671
- ? kept
672
- : [{ type: "text", text: `Tool reference unavailable: ${[...new Set(omitted)].join(", ")}` }];
673
- content.push({ ...block, content: nextContent });
674
- continue;
675
- }
676
- }
677
- }
678
- content.push(block);
679
- }
680
- if (content.length === 0) {
681
- changed = true;
682
- continue;
683
- }
684
- if (messageChanged) {
685
- changed = true;
686
- rewrittenMessages.push({ ...message, content });
687
- continue;
688
- }
689
- rewrittenMessages.push(message);
690
- }
691
- if (!changed)
692
- return params;
693
- return { ...params, messages: rewrittenMessages };
694
- }
695
- function collectToolReferenceNames(value, names) {
696
- if (Array.isArray(value)) {
697
- for (const item of value)
698
- collectToolReferenceNames(item, names);
699
- return;
700
- }
701
- if (!isRecord(value))
702
- return;
703
- if (value.type === "tool_reference" && typeof value.tool_name === "string")
704
- names.add(value.tool_name);
705
- for (const nested of Object.values(value))
706
- collectToolReferenceNames(nested, names);
707
- }
708
- function toolResultText(content) {
709
- if (typeof content === "string")
710
- return content;
711
- if (Array.isArray(content)) {
712
- const parts = [];
713
- for (const item of content) {
714
- if (!isRecord(item))
715
- continue;
716
- if (item.type === "text" && typeof item.text === "string")
717
- parts.push(item.text);
718
- else if (typeof item.type === "string")
719
- parts.push(`[${item.type}]`);
720
- }
721
- if (parts.length > 0)
722
- return parts.join("\n");
723
- }
724
- return "Tool output unavailable.";
574
+ /** Numeric HTTP status carried by an SDK error (Anthropic APIError.status), if any. */
575
+ function httpStatusOfError(error) {
576
+ if (!isRecord(error))
577
+ return undefined;
578
+ const status = error.status;
579
+ return typeof status === "number" && Number.isInteger(status) && status >= 100 && status < 600 ? status : undefined;
725
580
  }
726
581
  function sanitizeAdaptiveThinkingPayload(model, params, options) {
727
582
  if (!supportsAdaptiveThinking(model)) {
@@ -1046,24 +901,40 @@ export const stream = (model, context, options) => {
1046
901
  throw error;
1047
902
  }
1048
903
  };
1049
- const { params: sentParams, response } = await retryProviderRequest(async () => {
1050
- try {
1051
- return await createRequest();
1052
- }
1053
- catch (error) {
1054
- if (unsignedThinkingReplay !== "text" && isInvalidUnsignedThinkingSignatureError(error)) {
1055
- unsignedThinkingReplay = "text";
1056
- if (fallbackKey)
1057
- unsignedThinkingTextReplayFallbacks.add(fallbackKey);
1058
- return createRequest();
904
+ let requestOutcome;
905
+ try {
906
+ requestOutcome = await retryProviderRequest(async () => {
907
+ try {
908
+ return await createRequest();
1059
909
  }
1060
- throw error;
910
+ catch (error) {
911
+ if (unsignedThinkingReplay !== "text" && isInvalidUnsignedThinkingSignatureError(error)) {
912
+ unsignedThinkingReplay = "text";
913
+ if (fallbackKey)
914
+ unsignedThinkingTextReplayFallbacks.add(fallbackKey);
915
+ return createRequest();
916
+ }
917
+ throw error;
918
+ }
919
+ }, {
920
+ maxRetries: options?.maxRetries,
921
+ maxRetryDelayMs: options?.maxRetryDelayMs,
922
+ signal: requestSignal,
923
+ });
924
+ }
925
+ catch (error) {
926
+ // The SDK rejects HTTP failures instead of returning a Response, so a
927
+ // rejected request never reached onResponse. Deliver the numeric status
928
+ // once, after every internal retry, before the caller handles the error;
929
+ // errors without a status (network, aborts) report nothing rather than
930
+ // a fabricated code.
931
+ const status = httpStatusOfError(error);
932
+ if (status !== undefined) {
933
+ await options?.onResponse?.({ status, headers: {} }, model);
1061
934
  }
1062
- }, {
1063
- maxRetries: options?.maxRetries,
1064
- maxRetryDelayMs: options?.maxRetryDelayMs,
1065
- signal: requestSignal,
1066
- });
935
+ throw error;
936
+ }
937
+ const { params: sentParams, response } = requestOutcome;
1067
938
  await options?.onResponse?.({ status: response.status, headers: headersToRecord(response.headers) }, model);
1068
939
  stream.push({ type: "start", partial: output });
1069
940
  const blocks = output.content;
@@ -1102,16 +973,14 @@ export const stream = (model, context, options) => {
1102
973
  }
1103
974
  }
1104
975
  else if (event.type === "content_block_start") {
1105
- if (event.content_block.type === "fallback" && output.content.length > 0) {
1106
- throw new Error("Anthropic performed an unsupported mid-output model fallback");
1107
- }
1108
- const receipt = options?.abortServerSideFallback === true
1109
- ? parseServerFallbackReceipt(event.content_block)
1110
- : undefined;
976
+ const receipt = parseServerFallbackReceipt(event.content_block);
1111
977
  if (receipt !== undefined) {
1112
- serverFallbackReceipt = receipt;
1113
- serverFallbackAbort.abort();
1114
- break;
978
+ if (options?.abortServerSideFallback === true) {
979
+ serverFallbackReceipt = receipt;
980
+ serverFallbackAbort.abort();
981
+ break;
982
+ }
983
+ usageModel = applyServerFallbackContinuation(output, model, receipt);
1115
984
  }
1116
985
  if (event.content_block.type === "text") {
1117
986
  const block = {
@@ -1749,11 +1618,19 @@ function buildParams(model, context, isOAuthToken, options, unsignedThinkingRepl
1749
1618
  disableThinkingForRequest(params, model, compat);
1750
1619
  }
1751
1620
  }
1752
- // Anthropic rejects a thinking-enabled request whose final assistant turn
1753
- // contains tool_use but does not begin with a thinking block. Cross-model
1754
- // histories lose their signed thinking blocks (demoted to text or dropped),
1755
- // so degrade thinking for this request instead of failing every turn.
1756
- if (params.thinking && params.thinking.type !== "disabled" && finalAssistantTurnStartsWithToolUse(params.messages))
1621
+ // A budget-thinking request whose final assistant turn came from another API
1622
+ // and starts with tool_use (its thinking demoted to text or dropped) is the
1623
+ // history shape Anthropic has rejected with "final assistant message must
1624
+ // start with a thinking block", so that request degrades thinking instead of
1625
+ // failing every turn. Nothing else degrades: adaptive families accept a
1626
+ // tool_use-first final turn (verified live 2026-09-09 on claude-opus-5,
1627
+ // claude-opus-4-6 and claude-fable-5), the model itself skips thinking before
1628
+ // trivial tool calls, and every change to `thinking` re-keys the prompt
1629
+ // cache - degrading here re-wrote the whole cached prefix on each tool
1630
+ // continuation (the "cache misses every second prompt" report).
1631
+ if (params.thinking?.type === "enabled" &&
1632
+ finalAssistantTurnIsForeign(context.messages) &&
1633
+ finalAssistantTurnStartsWithToolUse(params.messages))
1757
1634
  disableThinkingForRequest(params, model, compat);
1758
1635
  if (options?.metadata) {
1759
1636
  const userId = options.metadata.user_id;
@@ -1794,6 +1671,16 @@ function applyExtraBodyToAnthropicParams(params, extraBody) {
1794
1671
  * thinking/redacted_thinking block. Anthropic requires thinking-enabled
1795
1672
  * requests to start that turn with a thinking block.
1796
1673
  */
1674
+ /** True when the last assistant message was produced through a different wire API. */
1675
+ function finalAssistantTurnIsForeign(messages) {
1676
+ for (let index = messages.length - 1; index >= 0; index--) {
1677
+ const message = messages[index];
1678
+ if (message.role !== "assistant")
1679
+ continue;
1680
+ return message.api !== "anthropic-messages";
1681
+ }
1682
+ return false;
1683
+ }
1797
1684
  function finalAssistantTurnStartsWithToolUse(messages) {
1798
1685
  for (let index = messages.length - 1; index >= 0; index--) {
1799
1686
  const message = messages[index];